You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1403 lines
54 KiB

14 years ago
8 months ago
9 months ago
9 months ago
17 years ago
17 years ago
5 years ago
5 years ago
17 years ago
9 months ago
14 years ago
5 years ago
5 years ago
5 years ago
5 years ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
14 years ago
14 years ago
17 years ago
9 months ago
8 months ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
Clean up arc/circle polygonization. 1) For a while now we've been using a calculated seg count from a given maxError, and a correction factor to push the radius out so that all the error is outside the arc/circle. However, the second calculation (which pre-dates the first) is pretty much just the inverse of the first (and yields nothing more than maxError back). This is particularly sub-optimal given the cost of trig functions. 2) There are a lot of old optimizations to reduce segcounts in certain situations, someting that our error-based calculation compensates for anyway. (Smaller radii need fewer segments to meet the maxError condition.) But perhaps more importantly we now surface maxError in the UI and we don't really want to call it "Max deviation except when it's not". 3) We were also clamping the segCount twice: once in the calculation routine and once in most of it's callers. Furthermore, the caller clamping was inconsistent (both in being done and in the clamping value). We now clamp only in the calculation routine. 4) There's no reason to use the correction factors in the 3Dviewer; it's just a visualization and whether the polygonization error is inside or outside the shape isn't really material. 5) The arc-correction-disabling stuff (used for solder mask layer) was somewhat fragile in that it depended on the caller to turn it back on afterwards. It's now only exposed as a RAII object which automatically cleans up when it goes out of scope. 6) There were also bugs in a couple of the polygonization routines where we'd accumulate round-off error in adding up the segments and end up with an overly long last segment (which of course would voilate the error max). This was the cause of the linked bug and also some issues with vias that we had fudged in the past with extra clearance. Fixes https://gitlab.com/kicad/code/kicad/issues/5567
5 years ago
5 years ago
5 years ago
8 months ago
8 months ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <wx/log.h>
  24. #include <eda_item.h>
  25. #include <layer_ids.h>
  26. #include <lset.h>
  27. #include <geometry/geometry_utils.h>
  28. #include <geometry/shape_segment.h>
  29. #include <pcb_base_frame.h>
  30. #include <math/util.h> // for KiROUND
  31. #include <board.h>
  32. #include <footprint.h>
  33. #include <pcb_track.h>
  34. #include <pad.h>
  35. #include <zone.h>
  36. #include <pcb_shape.h>
  37. #include <pcb_target.h>
  38. #include <pcb_dimension.h>
  39. #include <pcbplot.h>
  40. #include <plotters/plotter.h>
  41. #include <plotters/plotter_dxf.h>
  42. #include <plotters/plotter_gerber.h>
  43. #include <plotters/plotters_pslike.h>
  44. #include <pcb_painter.h>
  45. #include <gbr_metadata.h>
  46. #include <advanced_config.h>
  47. void GenerateLayerPoly( SHAPE_POLY_SET* aResult, BOARD *aBoard, PCB_LAYER_ID aLayer,
  48. bool aPlotFPText, bool aPlotReferences, bool aPlotValues );
  49. void PlotLayer( BOARD* aBoard, PLOTTER* aPlotter, const LSET& layerMask,
  50. const PCB_PLOT_PARAMS& plotOpts )
  51. {
  52. // PlotLayerOutlines() is designed only for DXF plotters.
  53. if( plotOpts.GetFormat() == PLOT_FORMAT::DXF && plotOpts.GetDXFPlotPolygonMode() )
  54. PlotLayerOutlines( aBoard, aPlotter, layerMask, plotOpts );
  55. else
  56. PlotStandardLayer( aBoard, aPlotter, layerMask, plotOpts );
  57. };
  58. void PlotPolySet( BOARD* aBoard, PLOTTER* aPlotter, const PCB_PLOT_PARAMS& aPlotOpt,
  59. SHAPE_POLY_SET* aPolySet, PCB_LAYER_ID aLayer )
  60. {
  61. BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
  62. LSET layers = { aLayer };
  63. itemplotter.SetLayerSet( layers );
  64. // To avoid a lot of code, use a ZONE to handle and plot polygons, because our polygons look
  65. // exactly like filled areas in zones.
  66. // Note, also this code is not optimized: it creates a lot of copy/duplicate data.
  67. // However it is not complex, and fast enough for plot purposes (copy/convert data is only a
  68. // very small calculation time for these calculations).
  69. ZONE zone( aBoard );
  70. zone.SetMinThickness( 0 );
  71. zone.SetLayer( aLayer );
  72. aPolySet->Fracture();
  73. itemplotter.PlotZone( &zone, aLayer, *aPolySet );
  74. }
  75. /**
  76. * Plot a solder mask layer.
  77. *
  78. * Solder mask layers have a minimum thickness value and cannot be drawn like standard layers,
  79. * unless the minimum thickness is 0.
  80. */
  81. void PlotSolderMaskLayer( BOARD* aBoard, PLOTTER* aPlotter, const LSET& aLayerMask,
  82. const PCB_PLOT_PARAMS& aPlotOpt )
  83. {
  84. if( aBoard->GetDesignSettings().m_SolderMaskMinWidth == 0 )
  85. {
  86. PlotLayer( aBoard, aPlotter, aLayerMask, aPlotOpt );
  87. return;
  88. }
  89. SHAPE_POLY_SET solderMask;
  90. PCB_LAYER_ID layer = aLayerMask[B_Mask] ? B_Mask : F_Mask;
  91. GenerateLayerPoly( &solderMask, aBoard, layer, aPlotOpt.GetPlotFPText(),
  92. aPlotOpt.GetPlotReference(), aPlotOpt.GetPlotValue() );
  93. PlotPolySet( aBoard, aPlotter, aPlotOpt, &solderMask, layer );
  94. }
  95. void PlotClippedSilkLayer( BOARD* aBoard, PLOTTER* aPlotter, const LSET& aLayerMask,
  96. const PCB_PLOT_PARAMS& aPlotOpt )
  97. {
  98. SHAPE_POLY_SET silkscreen, solderMask;
  99. PCB_LAYER_ID silkLayer = aLayerMask[F_SilkS] ? F_SilkS : B_SilkS;
  100. PCB_LAYER_ID maskLayer = aLayerMask[F_SilkS] ? F_Mask : B_Mask;
  101. GenerateLayerPoly( &silkscreen, aBoard, silkLayer, aPlotOpt.GetPlotFPText(),
  102. aPlotOpt.GetPlotReference(), aPlotOpt.GetPlotValue() );
  103. GenerateLayerPoly( &solderMask, aBoard, maskLayer, aPlotOpt.GetPlotFPText(),
  104. aPlotOpt.GetPlotReference(), aPlotOpt.GetPlotValue() );
  105. silkscreen.BooleanSubtract( solderMask );
  106. PlotPolySet( aBoard, aPlotter, aPlotOpt, &silkscreen, silkLayer );
  107. }
  108. void PlotBoardLayers( BOARD* aBoard, PLOTTER* aPlotter, const LSEQ& aLayers,
  109. const PCB_PLOT_PARAMS& aPlotOptions )
  110. {
  111. if( !aBoard || !aPlotter || aLayers.empty() )
  112. return;
  113. for( PCB_LAYER_ID layer : aLayers )
  114. PlotOneBoardLayer( aBoard, aPlotter, layer, aPlotOptions, layer == aLayers[0] );
  115. // Drill marks are plotted in white to knockout the pad if any layers of the pad are
  116. // being plotted, and in black if the pad is not being plotted. For the former, this
  117. // must happen after all other layers are plotted.
  118. if( aPlotOptions.GetDrillMarksType() != DRILL_MARKS::NO_DRILL_SHAPE )
  119. {
  120. BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOptions );
  121. itemplotter.SetLayerSet( aLayers );
  122. itemplotter.PlotDrillMarks();
  123. }
  124. }
  125. void PlotInteractiveLayer( BOARD* aBoard, PLOTTER* aPlotter, const PCB_PLOT_PARAMS& aPlotOpt )
  126. {
  127. for( const FOOTPRINT* fp : aBoard->Footprints() )
  128. {
  129. if( fp->GetLayer() == F_Cu && !aPlotOpt.m_PDFFrontFPPropertyPopups )
  130. continue;
  131. if( fp->GetLayer() == B_Cu && !aPlotOpt.m_PDFBackFPPropertyPopups )
  132. continue;
  133. std::vector<wxString> properties;
  134. properties.emplace_back( wxString::Format( wxT( "!%s = %s" ),
  135. _( "Reference designator" ),
  136. fp->Reference().GetShownText( false ) ) );
  137. properties.emplace_back( wxString::Format( wxT( "!%s = %s" ),
  138. _( "Value" ),
  139. fp->Value().GetShownText( false ) ) );
  140. properties.emplace_back( wxString::Format( wxT( "!%s = %s" ),
  141. _( "Footprint" ),
  142. fp->GetFPID().GetUniStringLibItemName() ) );
  143. for( const PCB_FIELD* field : fp->GetFields() )
  144. {
  145. if( field->IsReference() || field->IsValue() )
  146. continue;
  147. if( field->GetText().IsEmpty() )
  148. continue;
  149. properties.emplace_back( wxString::Format( wxT( "!%s = %s" ),
  150. field->GetName(),
  151. field->GetText() ) );
  152. }
  153. // These 2 properties are not very useful in a plot file (like a PDF)
  154. #if 0
  155. properties.emplace_back( wxString::Format( wxT( "!%s = %s" ), _( "Library Description" ),
  156. fp->GetLibDescription() ) );
  157. properties.emplace_back( wxString::Format( wxT( "!%s = %s" ), _( "Keywords" ),
  158. fp->GetKeywords() ) );
  159. #endif
  160. // Draw items are plotted with a position offset. So we need to move
  161. // our boxes (which are not plotted) by the same offset.
  162. VECTOR2I offset = -aPlotter->GetPlotOffsetUserUnits();
  163. // Use a footprint bbox without texts to create the hyperlink area
  164. BOX2I bbox = fp->GetBoundingBox( false );
  165. bbox.Move( offset );
  166. aPlotter->HyperlinkMenu( bbox, properties );
  167. // Use a footprint bbox with visible texts only to create the bookmark area
  168. // which is the area to zoom on ft selection
  169. // However the bbox need to be inflated for a better look.
  170. bbox = fp->GetBoundingBox( true );
  171. bbox.Move( offset );
  172. bbox.Inflate( bbox.GetWidth() /2, bbox.GetHeight() /2 );
  173. aPlotter->Bookmark( bbox, fp->GetReference(), _( "Footprints" ) );
  174. }
  175. }
  176. void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
  177. const PCB_PLOT_PARAMS& aPlotOpt, bool isPrimaryLayer )
  178. {
  179. PCB_PLOT_PARAMS plotOpt = aPlotOpt;
  180. // Set a default color and the text mode for this layer
  181. aPlotter->SetColor( BLACK );
  182. aPlotter->SetTextMode( aPlotOpt.GetTextMode() );
  183. // Specify that the contents of the "Edges Pcb" layer are to be plotted in addition to the
  184. // contents of the currently specified layer.
  185. LSET layer_mask( { aLayer } );
  186. if( IsCopperLayer( aLayer ) )
  187. {
  188. // Skip NPTH pads on copper layers ( only if hole size == pad size ):
  189. // Drill mark will be plotted if drill mark is SMALL_DRILL_SHAPE or FULL_DRILL_SHAPE
  190. if( plotOpt.GetFormat() == PLOT_FORMAT::DXF )
  191. plotOpt.SetDXFPlotPolygonMode( true );
  192. else
  193. plotOpt.SetSkipPlotNPTH_Pads( true );
  194. PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
  195. }
  196. else
  197. {
  198. switch( aLayer )
  199. {
  200. case B_Mask:
  201. case F_Mask:
  202. // Use outline mode for DXF
  203. plotOpt.SetDXFPlotPolygonMode( true );
  204. // Plot solder mask:
  205. PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt );
  206. break;
  207. case B_Adhes:
  208. case F_Adhes:
  209. case B_Paste:
  210. case F_Paste:
  211. // Disable plot pad holes
  212. plotOpt.SetDrillMarksType( DRILL_MARKS::NO_DRILL_SHAPE );
  213. // Use outline mode for DXF
  214. plotOpt.SetDXFPlotPolygonMode( true );
  215. PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
  216. break;
  217. case F_SilkS:
  218. case B_SilkS:
  219. if( plotOpt.GetSubtractMaskFromSilk() )
  220. {
  221. if( aPlotter->GetPlotterType() == PLOT_FORMAT::GERBER && isPrimaryLayer )
  222. {
  223. // Use old-school, positive/negative mask plotting which preserves utilization
  224. // of Gerber aperture masks. This method can only be used when the given silk
  225. // layer is the primary layer as the negative mask will also knockout any other
  226. // (non-silk) layers that were plotted before the silk layer.
  227. PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
  228. // Create the mask to subtract by creating a negative layer polarity
  229. aPlotter->SetLayerPolarity( false );
  230. // Disable plot pad holes
  231. plotOpt.SetDrillMarksType( DRILL_MARKS::NO_DRILL_SHAPE );
  232. // Plot the mask
  233. layer_mask = ( aLayer == F_SilkS ) ? LSET( { F_Mask } ) : LSET( { B_Mask } );
  234. PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt );
  235. // Disable the negative polarity
  236. aPlotter->SetLayerPolarity( true );
  237. }
  238. else
  239. {
  240. PlotClippedSilkLayer( aBoard, aPlotter, layer_mask, plotOpt );
  241. }
  242. break;
  243. }
  244. PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
  245. break;
  246. case Dwgs_User:
  247. case Cmts_User:
  248. case Eco1_User:
  249. case Eco2_User:
  250. case Edge_Cuts:
  251. case Margin:
  252. case F_CrtYd:
  253. case B_CrtYd:
  254. case F_Fab:
  255. case B_Fab:
  256. default:
  257. PlotLayer( aBoard, aPlotter, layer_mask, plotOpt );
  258. break;
  259. }
  260. }
  261. }
  262. /**
  263. * Plot any layer EXCEPT a solder-mask with an enforced minimum width.
  264. */
  265. void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, const LSET& aLayerMask,
  266. const PCB_PLOT_PARAMS& aPlotOpt )
  267. {
  268. BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
  269. int maxError = aBoard->GetDesignSettings().m_MaxError;
  270. itemplotter.SetLayerSet( aLayerMask );
  271. bool onCopperLayer = ( LSET::AllCuMask() & aLayerMask ).any();
  272. bool onSolderMaskLayer = ( LSET( { F_Mask, B_Mask } ) & aLayerMask ).any();
  273. bool onSolderPasteLayer = ( LSET( { F_Paste, B_Paste } ) & aLayerMask ).any();
  274. bool onFrontFab = ( LSET( { F_Fab } ) & aLayerMask ).any();
  275. bool onBackFab = ( LSET( { B_Fab } ) & aLayerMask ).any();
  276. bool sketchPads = ( onFrontFab || onBackFab ) && aPlotOpt.GetSketchPadsOnFabLayers();
  277. // Plot edge layer and graphic items
  278. for( const BOARD_ITEM* item : aBoard->Drawings() )
  279. itemplotter.PlotBoardGraphicItem( item );
  280. // Draw footprint texts:
  281. for( const FOOTPRINT* footprint : aBoard->Footprints() )
  282. itemplotter.PlotFootprintTextItems( footprint );
  283. // Draw footprint other graphic items:
  284. for( const FOOTPRINT* footprint : aBoard->Footprints() )
  285. itemplotter.PlotFootprintGraphicItems( footprint );
  286. // Plot footprint pads
  287. for( FOOTPRINT* footprint : aBoard->Footprints() )
  288. {
  289. aPlotter->StartBlock( nullptr );
  290. for( PAD* pad : footprint->Pads() )
  291. {
  292. bool doSketchPads = false;
  293. if( !( pad->GetLayerSet() & aLayerMask ).any() )
  294. {
  295. if( sketchPads && ( ( onFrontFab && pad->GetLayerSet().Contains( F_Cu ) )
  296. || ( onBackFab && pad->GetLayerSet().Contains( B_Cu ) ) ) )
  297. {
  298. doSketchPads = true;
  299. }
  300. else
  301. {
  302. continue;
  303. }
  304. }
  305. if( onCopperLayer && !pad->IsOnCopperLayer() )
  306. continue;
  307. /// pads not connected to copper are optionally not drawn
  308. if( onCopperLayer && !pad->FlashLayer( aLayerMask ) )
  309. continue;
  310. // TODO(JE) padstacks - different behavior for single layer or multilayer
  311. COLOR4D color = COLOR4D::BLACK;
  312. // If we're plotting a single layer, the color for that layer can be used directly.
  313. if( aLayerMask.count() == 1 )
  314. {
  315. color = aPlotOpt.ColorSettings()->GetColor( aLayerMask.Seq()[0] );
  316. }
  317. else
  318. {
  319. if( ( pad->GetLayerSet() & aLayerMask )[B_Cu] )
  320. color = aPlotOpt.ColorSettings()->GetColor( B_Cu );
  321. if( ( pad->GetLayerSet() & aLayerMask )[F_Cu] )
  322. color = color.LegacyMix( aPlotOpt.ColorSettings()->GetColor( F_Cu ) );
  323. if( sketchPads && aLayerMask[F_Fab] )
  324. color = aPlotOpt.ColorSettings()->GetColor( F_Fab );
  325. else if( sketchPads && aLayerMask[B_Fab] )
  326. color = aPlotOpt.ColorSettings()->GetColor( B_Fab );
  327. }
  328. if( sketchPads && ( ( onFrontFab && pad->GetLayerSet().Contains( F_Cu ) )
  329. || ( onBackFab && pad->GetLayerSet().Contains( B_Cu ) ) ) )
  330. {
  331. if( aPlotOpt.GetPlotPadNumbers() )
  332. itemplotter.PlotPadNumber( pad, color );
  333. }
  334. auto plotPadLayer =
  335. [&]( PCB_LAYER_ID aLayer )
  336. {
  337. VECTOR2I margin;
  338. int width_adj = 0;
  339. if( onCopperLayer )
  340. width_adj = itemplotter.getFineWidthAdj();
  341. if( onSolderMaskLayer )
  342. margin.x = margin.y = pad->GetSolderMaskExpansion( aLayer );
  343. if( onSolderPasteLayer )
  344. margin = pad->GetSolderPasteMargin( aLayer );
  345. // not all shapes can have a different margin for x and y axis
  346. // in fact only oval and rect shapes can have different values.
  347. // Round shape have always the same x,y margin
  348. // so define a unique value for other shapes that do not support different values
  349. int mask_clearance = margin.x;
  350. // Now offset the pad size by margin + width_adj
  351. VECTOR2I padPlotsSize = pad->GetSize( aLayer ) + margin * 2 + VECTOR2I( width_adj, width_adj );
  352. // Store these parameters that can be modified to plot inflated/deflated pads shape
  353. PAD_SHAPE padShape = pad->GetShape( aLayer );
  354. VECTOR2I padSize = pad->GetSize( aLayer );
  355. VECTOR2I padDelta = pad->GetDelta( aLayer ); // has meaning only for trapezoidal pads
  356. // CornerRadius and CornerRadiusRatio can be modified
  357. // the radius is built from the ratio, so saving/restoring the ratio is enough
  358. double padCornerRadiusRatio = pad->GetRoundRectRadiusRatio( aLayer );
  359. // Don't draw a 0 sized pad.
  360. // Note: a custom pad can have its pad anchor with size = 0
  361. if( padShape != PAD_SHAPE::CUSTOM
  362. && ( padPlotsSize.x <= 0 || padPlotsSize.y <= 0 ) )
  363. {
  364. return;
  365. }
  366. switch( padShape )
  367. {
  368. case PAD_SHAPE::CIRCLE:
  369. case PAD_SHAPE::OVAL:
  370. pad->SetSize( aLayer, padPlotsSize );
  371. if( aPlotOpt.GetSkipPlotNPTH_Pads() &&
  372. ( aPlotOpt.GetDrillMarksType() == DRILL_MARKS::NO_DRILL_SHAPE ) &&
  373. ( pad->GetSize(aLayer ) == pad->GetDrillSize() ) &&
  374. ( pad->GetAttribute() == PAD_ATTRIB::NPTH ) )
  375. {
  376. break;
  377. }
  378. itemplotter.PlotPad( pad, aLayer, color, doSketchPads );
  379. break;
  380. case PAD_SHAPE::RECTANGLE:
  381. pad->SetSize( aLayer, padPlotsSize );
  382. if( mask_clearance > 0 )
  383. {
  384. pad->SetShape( aLayer, PAD_SHAPE::ROUNDRECT );
  385. pad->SetRoundRectCornerRadius( aLayer, mask_clearance );
  386. }
  387. itemplotter.PlotPad( pad, aLayer, color, doSketchPads );
  388. break;
  389. case PAD_SHAPE::TRAPEZOID:
  390. // inflate/deflate a trapezoid is a bit complex.
  391. // so if the margin is not null, build a similar polygonal pad shape,
  392. // and inflate/deflate the polygonal shape
  393. // because inflating/deflating using different values for y and y
  394. // we are using only margin.x as inflate/deflate value
  395. if( mask_clearance == 0 )
  396. {
  397. itemplotter.PlotPad( pad, aLayer, color, doSketchPads );
  398. }
  399. else
  400. {
  401. PAD dummy( *pad );
  402. dummy.SetAnchorPadShape( aLayer, PAD_SHAPE::CIRCLE );
  403. dummy.SetShape( aLayer, PAD_SHAPE::CUSTOM );
  404. SHAPE_POLY_SET outline;
  405. outline.NewOutline();
  406. int dx = padSize.x / 2;
  407. int dy = padSize.y / 2;
  408. int ddx = padDelta.x / 2;
  409. int ddy = padDelta.y / 2;
  410. outline.Append( -dx - ddy, dy + ddx );
  411. outline.Append( dx + ddy, dy - ddx );
  412. outline.Append( dx - ddy, -dy + ddx );
  413. outline.Append( -dx + ddy, -dy - ddx );
  414. // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
  415. // which can create bad shapes if margin.x is < 0
  416. outline.InflateWithLinkedHoles( mask_clearance, CORNER_STRATEGY::ROUND_ALL_CORNERS,
  417. maxError );
  418. dummy.DeletePrimitivesList();
  419. dummy.AddPrimitivePoly( aLayer, outline, 0, true );
  420. // Be sure the anchor pad is not bigger than the deflated shape because this
  421. // anchor will be added to the pad shape when plotting the pad. So now the
  422. // polygonal shape is built, we can clamp the anchor size
  423. dummy.SetSize( aLayer, VECTOR2I( 0, 0 ) );
  424. itemplotter.PlotPad( &dummy, aLayer, color, doSketchPads );
  425. }
  426. break;
  427. case PAD_SHAPE::ROUNDRECT:
  428. {
  429. // rounding is stored as a percent, but we have to update this ratio
  430. // to force recalculation of other values after size changing (we do not
  431. // really change the rounding percent value)
  432. double radius_ratio = pad->GetRoundRectRadiusRatio( aLayer );
  433. pad->SetSize( aLayer, padPlotsSize );
  434. pad->SetRoundRectRadiusRatio( aLayer, radius_ratio );
  435. itemplotter.PlotPad( pad, aLayer, color, doSketchPads );
  436. break;
  437. }
  438. case PAD_SHAPE::CHAMFERED_RECT:
  439. if( mask_clearance == 0 )
  440. {
  441. // the size can be slightly inflated by width_adj (PS/PDF only)
  442. pad->SetSize( aLayer, padPlotsSize );
  443. itemplotter.PlotPad( pad, aLayer, color, doSketchPads );
  444. }
  445. else
  446. {
  447. // Due to the polygonal shape of a CHAMFERED_RECT pad, the best way is to
  448. // convert the pad shape to a full polygon, inflate/deflate the polygon
  449. // and use a dummy CUSTOM pad to plot the final shape.
  450. PAD dummy( *pad );
  451. // Build the dummy pad outline with coordinates relative to the pad position
  452. // pad offset and orientation 0. The actual pos, offset and rotation will be
  453. // taken in account later by the plot function
  454. dummy.SetPosition( VECTOR2I( 0, 0 ) );
  455. dummy.SetOffset( aLayer, VECTOR2I( 0, 0 ) );
  456. dummy.SetOrientation( ANGLE_0 );
  457. SHAPE_POLY_SET outline;
  458. dummy.TransformShapeToPolygon( outline, aLayer, 0, maxError, ERROR_INSIDE );
  459. outline.InflateWithLinkedHoles( mask_clearance, CORNER_STRATEGY::ROUND_ALL_CORNERS,
  460. maxError );
  461. // Initialize the dummy pad shape:
  462. dummy.SetAnchorPadShape( aLayer, PAD_SHAPE::CIRCLE );
  463. dummy.SetShape( aLayer, PAD_SHAPE::CUSTOM );
  464. dummy.DeletePrimitivesList();
  465. dummy.AddPrimitivePoly( aLayer, outline, 0, true );
  466. // Be sure the anchor pad is not bigger than the deflated shape because this
  467. // anchor will be added to the pad shape when plotting the pad.
  468. // So we set the anchor size to 0
  469. dummy.SetSize( aLayer, VECTOR2I( 0, 0 ) );
  470. // Restore pad position and offset
  471. dummy.SetPosition( pad->GetPosition() );
  472. dummy.SetOffset( aLayer, pad->GetOffset( aLayer ) );
  473. dummy.SetOrientation( pad->GetOrientation() );
  474. itemplotter.PlotPad( &dummy, aLayer, color, doSketchPads );
  475. }
  476. break;
  477. case PAD_SHAPE::CUSTOM:
  478. {
  479. // inflate/deflate a custom shape is a bit complex.
  480. // so build a similar pad shape, and inflate/deflate the polygonal shape
  481. PAD dummy( *pad );
  482. dummy.SetParentGroup( nullptr );
  483. SHAPE_POLY_SET shape;
  484. pad->MergePrimitivesAsPolygon( aLayer, &shape );
  485. // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
  486. // which can create bad shapes if margin.x is < 0
  487. shape.InflateWithLinkedHoles( mask_clearance,
  488. CORNER_STRATEGY::ROUND_ALL_CORNERS, maxError );
  489. dummy.DeletePrimitivesList();
  490. dummy.AddPrimitivePoly( aLayer, shape, 0, true );
  491. // Be sure the anchor pad is not bigger than the deflated shape because this
  492. // anchor will be added to the pad shape when plotting the pad. So now the
  493. // polygonal shape is built, we can clamp the anchor size
  494. if( mask_clearance < 0 ) // we expect margin.x = margin.y for custom pads
  495. {
  496. dummy.SetSize( aLayer, VECTOR2I( std::max( 0, padPlotsSize.x ),
  497. std::max( 0, padPlotsSize.y ) ) );
  498. }
  499. itemplotter.PlotPad( &dummy, aLayer, color, doSketchPads );
  500. break;
  501. }
  502. }
  503. // Restore the pad parameters modified by the plot code
  504. pad->SetSize( aLayer, padSize );
  505. pad->SetDelta( aLayer, padDelta );
  506. pad->SetShape( aLayer, padShape );
  507. pad->SetRoundRectRadiusRatio( aLayer, padCornerRadiusRatio );
  508. };
  509. for( PCB_LAYER_ID layer : aLayerMask.SeqStackupForPlotting() )
  510. plotPadLayer( layer );
  511. }
  512. if( footprint->IsDNP()
  513. && !itemplotter.GetHideDNPFPsOnFabLayers()
  514. && itemplotter.GetCrossoutDNPFPsOnFabLayers()
  515. && ( ( onFrontFab && footprint->GetLayer() == F_Cu )
  516. || ( onBackFab && footprint->GetLayer() == B_Cu ) ) )
  517. {
  518. BOX2I rect;
  519. const SHAPE_POLY_SET& courtyard = footprint->GetCourtyard( footprint->GetLayer() );
  520. if( courtyard.IsEmpty() )
  521. rect = footprint->GetEffectiveShape()->BBox();
  522. else
  523. rect = courtyard.BBox();
  524. int width = aBoard->GetDesignSettings().m_LineThickness[ LAYER_CLASS_FAB ];
  525. aPlotter->ThickSegment( rect.GetOrigin(), rect.GetEnd(), width, nullptr );
  526. aPlotter->ThickSegment( VECTOR2I( rect.GetLeft(), rect.GetBottom() ),
  527. VECTOR2I( rect.GetRight(), rect.GetTop() ),
  528. width, nullptr );
  529. }
  530. aPlotter->EndBlock( nullptr );
  531. }
  532. // Plot vias on copper layers, and if aPlotOpt.GetPlotViaOnMaskLayer() is true,
  533. GBR_METADATA gbr_metadata;
  534. if( onCopperLayer )
  535. {
  536. gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_VIAPAD );
  537. gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
  538. }
  539. auto getMetadata =
  540. [&]()
  541. {
  542. if( aPlotter->GetPlotterType() == PLOT_FORMAT::GERBER )
  543. return (void*) &gbr_metadata;
  544. else if( aPlotter->GetPlotterType() == PLOT_FORMAT::DXF )
  545. return (void*) &aPlotOpt;
  546. else
  547. return (void*) nullptr;
  548. };
  549. aPlotter->StartBlock( nullptr );
  550. for( const PCB_TRACK* track : aBoard->Tracks() )
  551. {
  552. if( track->Type() != PCB_VIA_T )
  553. continue;
  554. const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
  555. // vias are not plotted if not on selected layer
  556. LSET via_mask_layer = via->GetLayerSet();
  557. if( !( via_mask_layer & aLayerMask ).any() )
  558. continue;
  559. int via_margin = 0;
  560. double width_adj = 0;
  561. // TODO(JE) padstacks - separate top/bottom margin
  562. if( onSolderMaskLayer )
  563. via_margin = via->GetSolderMaskExpansion();
  564. if( ( aLayerMask & LSET::AllCuMask() ).any() )
  565. width_adj = itemplotter.getFineWidthAdj();
  566. /// Vias not connected to copper are optionally not drawn
  567. if( onCopperLayer && !via->FlashLayer( aLayerMask ) )
  568. continue;
  569. int diameter = 0;
  570. for( PCB_LAYER_ID layer : aLayerMask )
  571. diameter = std::max( diameter, via->GetWidth( layer ) );
  572. diameter += 2 * via_margin + width_adj;
  573. // Don't draw a null size item :
  574. if( diameter <= 0 )
  575. continue;
  576. // Some vias can be not connected (no net).
  577. // Set the m_NotInNet for these vias to force a empty net name in gerber file
  578. gbr_metadata.m_NetlistMetadata.m_NotInNet = via->GetNetname().IsEmpty();
  579. gbr_metadata.SetNetName( via->GetNetname() );
  580. COLOR4D color;
  581. // If we're plotting a single layer, the color for that layer can be used directly.
  582. if( aLayerMask.count() == 1 )
  583. color = aPlotOpt.ColorSettings()->GetColor( aLayerMask.Seq()[0] );
  584. else
  585. color = aPlotOpt.ColorSettings()->GetColor( LAYER_VIAS + static_cast<int>( via->GetViaType() ) );
  586. // Change UNSPECIFIED or WHITE to LIGHTGRAY because the white items are not seen on a
  587. // white paper or screen
  588. if( color == COLOR4D::UNSPECIFIED || color == WHITE )
  589. color = LIGHTGRAY;
  590. aPlotter->SetColor( color );
  591. aPlotter->FlashPadCircle( via->GetStart(), diameter, getMetadata() );
  592. }
  593. aPlotter->EndBlock( nullptr );
  594. aPlotter->StartBlock( nullptr );
  595. if( onCopperLayer )
  596. {
  597. gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
  598. gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
  599. }
  600. else
  601. {
  602. // Reset attributes if non-copper (soldermask) layer
  603. gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONE );
  604. gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_UNSPECIFIED );
  605. }
  606. // Plot tracks (not vias) :
  607. for( const PCB_TRACK* track : aBoard->Tracks() )
  608. {
  609. if( track->Type() == PCB_VIA_T )
  610. continue;
  611. if( !( aLayerMask & track->GetLayerSet() ).any() )
  612. continue;
  613. // Some track segments can be not connected (no net).
  614. // Set the m_NotInNet for these segments to force a empty net name in gerber file
  615. gbr_metadata.m_NetlistMetadata.m_NotInNet = track->GetNetname().IsEmpty();
  616. gbr_metadata.SetNetName( track->GetNetname() );
  617. int margin = 0;
  618. if( onSolderMaskLayer )
  619. margin = track->GetSolderMaskExpansion();
  620. int width = track->GetWidth() + 2 * margin + itemplotter.getFineWidthAdj();
  621. aPlotter->SetColor( itemplotter.getColor( track->GetLayer() ) );
  622. if( track->Type() == PCB_ARC_T )
  623. {
  624. const PCB_ARC* arc = static_cast<const PCB_ARC*>( track );
  625. // Too small arcs cannot be really handled: arc center (and arc radius)
  626. // cannot be safely computed
  627. if( !arc->IsDegenerated( 10 /* in IU */ ) )
  628. {
  629. aPlotter->ThickArc( arc->GetCenter(), arc->GetArcAngleStart(), arc->GetAngle(),
  630. arc->GetRadius(), width, getMetadata() );
  631. }
  632. else
  633. {
  634. // Approximate this very small arc by a segment.
  635. aPlotter->ThickSegment( track->GetStart(), track->GetEnd(), width, getMetadata() );
  636. }
  637. }
  638. else
  639. {
  640. aPlotter->ThickSegment( track->GetStart(), track->GetEnd(), width, getMetadata() );
  641. }
  642. }
  643. aPlotter->EndBlock( nullptr );
  644. // Plot filled ares
  645. aPlotter->StartBlock( nullptr );
  646. NETINFO_ITEM nonet( aBoard );
  647. for( const ZONE* zone : aBoard->Zones() )
  648. {
  649. if( zone->GetIsRuleArea() )
  650. continue;
  651. for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
  652. {
  653. if( !aLayerMask[layer] )
  654. continue;
  655. SHAPE_POLY_SET mainArea = zone->GetFilledPolysList( layer )->CloneDropTriangulation();
  656. SHAPE_POLY_SET islands;
  657. for( int i = mainArea.OutlineCount() - 1; i >= 0; i-- )
  658. {
  659. if( zone->IsIsland( layer, i ) )
  660. {
  661. islands.AddOutline( mainArea.CPolygon( i )[0] );
  662. mainArea.DeletePolygon( i );
  663. }
  664. }
  665. itemplotter.PlotZone( zone, layer, mainArea );
  666. if( !islands.IsEmpty() )
  667. {
  668. ZONE dummy( *zone );
  669. dummy.SetNet( &nonet );
  670. itemplotter.PlotZone( &dummy, layer, islands );
  671. }
  672. }
  673. }
  674. aPlotter->EndBlock( nullptr );
  675. }
  676. /**
  677. * Plot outlines.
  678. */
  679. void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter, const LSET& aLayerMask,
  680. const PCB_PLOT_PARAMS& aPlotOpt )
  681. {
  682. BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
  683. itemplotter.SetLayerSet( aLayerMask );
  684. int smallDrill = pcbIUScale.mmToIU( ADVANCED_CFG::GetCfg().m_SmallDrillMarkSize );
  685. SHAPE_POLY_SET outlines;
  686. for( PCB_LAYER_ID layer : aLayerMask.Seq( aLayerMask.SeqStackupForPlotting() ) )
  687. {
  688. outlines.RemoveAllContours();
  689. aBoard->ConvertBrdLayerToPolygonalContours( layer, outlines, aPlotter->RenderSettings() );
  690. outlines.Simplify();
  691. // Plot outlines
  692. std::vector<VECTOR2I> cornerList;
  693. // Now we have one or more basic polygons: plot each polygon
  694. for( int ii = 0; ii < outlines.OutlineCount(); ii++ )
  695. {
  696. for( int kk = 0; kk <= outlines.HoleCount(ii); kk++ )
  697. {
  698. cornerList.clear();
  699. const SHAPE_LINE_CHAIN& path = ( kk == 0 ) ? outlines.COutline( ii )
  700. : outlines.CHole( ii, kk - 1 );
  701. aPlotter->PlotPoly( path, FILL_T::NO_FILL, PLOTTER::USE_DEFAULT_LINE_WIDTH, nullptr );
  702. }
  703. }
  704. // Plot pad holes
  705. if( aPlotOpt.GetDrillMarksType() != DRILL_MARKS::NO_DRILL_SHAPE )
  706. {
  707. for( FOOTPRINT* footprint : aBoard->Footprints() )
  708. {
  709. for( PAD* pad : footprint->Pads() )
  710. {
  711. if( pad->HasHole() )
  712. {
  713. if( pad->GetDrillSizeX() == pad->GetDrillSizeY() )
  714. {
  715. int drill = pad->GetDrillSizeX();
  716. if( aPlotOpt.GetDrillMarksType() == DRILL_MARKS::SMALL_DRILL_SHAPE )
  717. drill = std::min( smallDrill, drill );
  718. aPlotter->ThickCircle( pad->ShapePos( layer ), drill,
  719. PLOTTER::USE_DEFAULT_LINE_WIDTH, nullptr );
  720. }
  721. else
  722. {
  723. // Note: small drill marks have no significance when applied to slots
  724. aPlotter->ThickOval( pad->ShapePos( layer ), pad->GetSize( layer ),
  725. pad->GetOrientation(), PLOTTER::USE_DEFAULT_LINE_WIDTH,
  726. nullptr );
  727. }
  728. }
  729. }
  730. }
  731. }
  732. // Plot vias holes
  733. for( PCB_TRACK* track : aBoard->Tracks() )
  734. {
  735. if( track->Type() != PCB_VIA_T )
  736. continue;
  737. const PCB_VIA* via = static_cast<const PCB_VIA*>( track );
  738. if( via->GetLayerSet().Contains( layer ) ) // via holes can be not through holes
  739. {
  740. aPlotter->Circle( via->GetPosition(), via->GetDrillValue(), FILL_T::NO_FILL,
  741. PLOTTER::USE_DEFAULT_LINE_WIDTH );
  742. }
  743. }
  744. }
  745. }
  746. /**
  747. * Generates a SHAPE_POLY_SET representing the plotted items on a layer.
  748. */
  749. void GenerateLayerPoly( SHAPE_POLY_SET* aResult, BOARD *aBoard, PCB_LAYER_ID aLayer,
  750. bool aPlotFPText, bool aPlotReferences, bool aPlotValues )
  751. {
  752. int maxError = aBoard->GetDesignSettings().m_MaxError;
  753. SHAPE_POLY_SET buffer;
  754. int inflate = 0;
  755. if( aLayer == F_Mask || aLayer == B_Mask )
  756. {
  757. // We remove 1nm as we expand both sides of the shapes, so allowing for a strictly greater
  758. // than or equal comparison in the shape separation (boolean add)
  759. inflate = aBoard->GetDesignSettings().m_SolderMaskMinWidth / 2 - 1;
  760. }
  761. // Build polygons for each pad shape. The size of the shape on solder mask should be size
  762. // of pad + clearance around the pad, where clearance = solder mask clearance + extra margin.
  763. // Extra margin is half the min width for solder mask, which is used to merge too-close shapes
  764. // (distance < SolderMaskMinWidth).
  765. // Will contain exact shapes of all items on solder mask. We add this back in at the end just
  766. // to make sure that any artefacts introduced by the inflate/deflate don't remove parts of the
  767. // individual shapes.
  768. SHAPE_POLY_SET exactPolys;
  769. auto handleFPTextItem =
  770. [&]( const PCB_TEXT& aText )
  771. {
  772. if( !aPlotFPText )
  773. return;
  774. if( aText.GetText() == wxT( "${REFERENCE}" ) && !aPlotReferences )
  775. return;
  776. if( aText.GetText() == wxT( "${VALUE}" ) && !aPlotValues )
  777. return;
  778. if( inflate != 0 )
  779. aText.TransformTextToPolySet( exactPolys, 0, maxError, ERROR_OUTSIDE );
  780. aText.TransformTextToPolySet( *aResult, inflate, maxError, ERROR_OUTSIDE );
  781. };
  782. // Generate polygons with arcs inside the shape or exact shape to minimize shape changes
  783. // created by arc to segment size correction.
  784. DISABLE_ARC_RADIUS_CORRECTION disabler;
  785. {
  786. // Plot footprint pads and graphics
  787. for( const FOOTPRINT* footprint : aBoard->Footprints() )
  788. {
  789. if( inflate != 0 )
  790. footprint->TransformPadsToPolySet( exactPolys, aLayer, 0, maxError, ERROR_OUTSIDE );
  791. footprint->TransformPadsToPolySet( *aResult, aLayer, inflate, maxError, ERROR_OUTSIDE );
  792. for( const PCB_FIELD* field : footprint->GetFields() )
  793. {
  794. if( field->IsReference() && !aPlotReferences )
  795. continue;
  796. if( field->IsValue() && !aPlotValues )
  797. continue;
  798. if( field->IsVisible() && field->IsOnLayer( aLayer ) )
  799. handleFPTextItem( static_cast<const PCB_TEXT&>( *field ) );
  800. }
  801. for( const BOARD_ITEM* item : footprint->GraphicalItems() )
  802. {
  803. if( item->IsOnLayer( aLayer ) )
  804. {
  805. if( item->Type() == PCB_TEXT_T )
  806. {
  807. handleFPTextItem( static_cast<const PCB_TEXT&>( *item ) );
  808. }
  809. else
  810. {
  811. if( inflate != 0 )
  812. item->TransformShapeToPolySet( exactPolys, aLayer, 0, maxError, ERROR_OUTSIDE );
  813. item->TransformShapeToPolySet( *aResult, aLayer, inflate, maxError, ERROR_OUTSIDE );
  814. }
  815. }
  816. }
  817. }
  818. // Plot untented vias and tracks
  819. for( const PCB_TRACK* track : aBoard->Tracks() )
  820. {
  821. // Note: IsOnLayer() checks relevant mask layers of untented vias and tracks
  822. if( !track->IsOnLayer( aLayer ) )
  823. continue;
  824. int clearance = track->GetSolderMaskExpansion();
  825. if( inflate != 0 )
  826. track->TransformShapeToPolygon( exactPolys, aLayer, clearance, maxError, ERROR_OUTSIDE );
  827. track->TransformShapeToPolygon( *aResult, aLayer, clearance + inflate, maxError, ERROR_OUTSIDE );
  828. }
  829. for( const BOARD_ITEM* item : aBoard->Drawings() )
  830. {
  831. if( item->IsOnLayer( aLayer ) )
  832. {
  833. if( item->Type() == PCB_TEXT_T )
  834. {
  835. const PCB_TEXT* text = static_cast<const PCB_TEXT*>( item );
  836. if( inflate != 0 )
  837. text->TransformTextToPolySet( exactPolys, 0, maxError, ERROR_OUTSIDE );
  838. text->TransformTextToPolySet( *aResult, inflate, maxError, ERROR_OUTSIDE );
  839. }
  840. else
  841. {
  842. if( inflate != 0 )
  843. item->TransformShapeToPolygon( exactPolys, aLayer, 0, maxError, ERROR_OUTSIDE );
  844. item->TransformShapeToPolygon( *aResult, aLayer, inflate, maxError, ERROR_OUTSIDE );
  845. }
  846. }
  847. }
  848. // Add filled zone areas.
  849. for( ZONE* zone : aBoard->Zones() )
  850. {
  851. if( zone->GetIsRuleArea() )
  852. continue;
  853. if( !zone->IsOnLayer( aLayer ) )
  854. continue;
  855. SHAPE_POLY_SET area = *zone->GetFill( aLayer );
  856. if( inflate != 0 )
  857. exactPolys.Append( area );
  858. area.Inflate( inflate, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
  859. aResult->Append( area );
  860. }
  861. }
  862. // Merge all polygons
  863. aResult->Simplify();
  864. if( inflate != 0 )
  865. {
  866. aResult->Deflate( inflate, CORNER_STRATEGY::CHAMFER_ALL_CORNERS, maxError );
  867. // Add back in the exact polys. This is mandatory because inflate/deflate transform is
  868. // not perfect, and we want the initial areas perfectly kept.
  869. aResult->BooleanAdd( exactPolys );
  870. }
  871. #undef ERROR
  872. }
  873. /**
  874. * Set up most plot options for plotting a board (especially the viewport)
  875. * Important thing:
  876. * page size is the 'drawing' page size,
  877. * paper size is the physical page size
  878. */
  879. static void initializePlotter( PLOTTER* aPlotter, const BOARD* aBoard, const PCB_PLOT_PARAMS* aPlotOpts )
  880. {
  881. PAGE_INFO pageA4( PAGE_SIZE_TYPE::A4 );
  882. const PAGE_INFO& pageInfo = aBoard->GetPageSettings();
  883. const PAGE_INFO* sheet_info;
  884. double paperscale; // Page-to-paper ratio
  885. VECTOR2I paperSizeIU;
  886. VECTOR2I pageSizeIU( pageInfo.GetSizeIU( pcbIUScale.IU_PER_MILS ) );
  887. bool autocenter = false;
  888. // Special options: to fit the sheet to an A4 sheet replace the paper size. However there
  889. // is a difference between the autoscale and the a4paper option:
  890. // - Autoscale fits the board to the paper size
  891. // - A4paper fits the original paper size to an A4 sheet
  892. // - Both of them fit the board to an A4 sheet
  893. if( aPlotOpts->GetA4Output() )
  894. {
  895. sheet_info = &pageA4;
  896. paperSizeIU = pageA4.GetSizeIU( pcbIUScale.IU_PER_MILS );
  897. paperscale = (double) paperSizeIU.x / pageSizeIU.x;
  898. autocenter = true;
  899. }
  900. else
  901. {
  902. sheet_info = &pageInfo;
  903. paperSizeIU = pageSizeIU;
  904. paperscale = 1;
  905. // Need autocentering only if scale is not 1:1
  906. autocenter = (aPlotOpts->GetScale() != 1.0) || aPlotOpts->GetAutoScale();
  907. }
  908. BOX2I bbox = aBoard->ComputeBoundingBox( false );
  909. VECTOR2I boardCenter = bbox.Centre();
  910. VECTOR2I boardSize = bbox.GetSize();
  911. double compound_scale;
  912. // Fit to 80% of the page if asked; it could be that the board is empty, in this case
  913. // regress to 1:1 scale
  914. if( aPlotOpts->GetAutoScale() && boardSize.x > 0 && boardSize.y > 0 )
  915. {
  916. double xscale = (paperSizeIU.x * 0.8) / boardSize.x;
  917. double yscale = (paperSizeIU.y * 0.8) / boardSize.y;
  918. compound_scale = std::min( xscale, yscale ) * paperscale;
  919. }
  920. else
  921. {
  922. compound_scale = aPlotOpts->GetScale() * paperscale;
  923. }
  924. // For the plot offset we have to keep in mind the auxiliary origin too: if autoscaling is
  925. // off we check that plot option (i.e. autoscaling overrides auxiliary origin)
  926. VECTOR2I offset( 0, 0);
  927. if( autocenter )
  928. {
  929. offset.x = KiROUND( boardCenter.x - ( paperSizeIU.x / 2.0 ) / compound_scale );
  930. offset.y = KiROUND( boardCenter.y - ( paperSizeIU.y / 2.0 ) / compound_scale );
  931. }
  932. else
  933. {
  934. if( aPlotOpts->GetUseAuxOrigin() )
  935. offset = aBoard->GetDesignSettings().GetAuxOrigin();
  936. }
  937. aPlotter->SetPageSettings( *sheet_info );
  938. aPlotter->SetViewport( offset, pcbIUScale.IU_PER_MILS/10, compound_scale, aPlotOpts->GetMirror() );
  939. // Has meaning only for gerber plotter. Must be called only after SetViewport
  940. aPlotter->SetGerberCoordinatesFormat( aPlotOpts->GetGerberPrecision() );
  941. // Has meaning only for SVG plotter. Must be called only after SetViewport
  942. aPlotter->SetSvgCoordinatesFormat( aPlotOpts->GetSvgPrecision() );
  943. aPlotter->SetCreator( wxT( "PCBNEW" ) );
  944. aPlotter->SetColorMode( !aPlotOpts->GetBlackAndWhite() ); // default is plot in Black and White.
  945. aPlotter->SetTextMode( aPlotOpts->GetTextMode() );
  946. }
  947. /**
  948. * Prefill in black an area a little bigger than the board to prepare for the negative plot
  949. */
  950. static void FillNegativeKnockout( PLOTTER *aPlotter, const BOX2I &aBbbox )
  951. {
  952. const int margin = 5 * pcbIUScale.IU_PER_MM; // Add a 5 mm margin around the board
  953. aPlotter->SetNegative( true );
  954. aPlotter->SetColor( WHITE ); // Which will be plotted as black
  955. BOX2I area = aBbbox;
  956. area.Inflate( margin );
  957. aPlotter->Rect( area.GetOrigin(), area.GetEnd(), FILL_T::FILLED_SHAPE, 0, 0 );
  958. aPlotter->SetColor( BLACK );
  959. }
  960. static void plotPdfBackground( BOARD* aBoard, const PCB_PLOT_PARAMS* aPlotOpts, PLOTTER* aPlotter )
  961. {
  962. if( aPlotter->GetColorMode()
  963. && aPlotOpts->GetPDFBackgroundColor() != COLOR4D::UNSPECIFIED )
  964. {
  965. aPlotter->SetColor( aPlotOpts->GetPDFBackgroundColor() );
  966. // Use page size selected in pcb to know the schematic bg area
  967. const PAGE_INFO& actualPage = aBoard->GetPageSettings();
  968. VECTOR2I end( actualPage.GetWidthIU( pcbIUScale.IU_PER_MILS ),
  969. actualPage.GetHeightIU( pcbIUScale.IU_PER_MILS ) );
  970. aPlotter->Rect( VECTOR2I( 0, 0 ), end, FILL_T::FILLED_SHAPE, 1.0 );
  971. }
  972. }
  973. /**
  974. * Open a new plotfile using the options (and especially the format) specified in the options
  975. * and prepare the page for plotting.
  976. *
  977. * @return the plotter object if OK, NULL if the file is not created (or has a problem).
  978. */
  979. PLOTTER* StartPlotBoard( BOARD *aBoard, const PCB_PLOT_PARAMS *aPlotOpts, int aLayer,
  980. const wxString& aLayerName, const wxString& aFullFileName,
  981. const wxString& aSheetName, const wxString& aSheetPath,
  982. const wxString& aPageName, const wxString& aPageNumber,
  983. const int aPageCount )
  984. {
  985. wxCHECK( aBoard && aPlotOpts, nullptr );
  986. // Create the plotter driver and set the few plotter specific options
  987. PLOTTER* plotter = nullptr;
  988. switch( aPlotOpts->GetFormat() )
  989. {
  990. case PLOT_FORMAT::DXF:
  991. DXF_PLOTTER* DXF_plotter;
  992. DXF_plotter = new DXF_PLOTTER();
  993. DXF_plotter->SetUnits( aPlotOpts->GetDXFPlotUnits() );
  994. plotter = DXF_plotter;
  995. break;
  996. case PLOT_FORMAT::POST:
  997. PS_PLOTTER* PS_plotter;
  998. PS_plotter = new PS_PLOTTER();
  999. PS_plotter->SetScaleAdjust( aPlotOpts->GetFineScaleAdjustX(),
  1000. aPlotOpts->GetFineScaleAdjustY() );
  1001. plotter = PS_plotter;
  1002. break;
  1003. case PLOT_FORMAT::PDF:
  1004. plotter = new PDF_PLOTTER( aBoard->GetProject() );
  1005. break;
  1006. case PLOT_FORMAT::HPGL:
  1007. wxLogError( _( "HPGL plotting is no longer supported as of KiCad 10.0" ) );
  1008. return nullptr;
  1009. case PLOT_FORMAT::GERBER:
  1010. // For Gerber plotter, a valid board layer must be set, in order to create a valid
  1011. // Gerber header, especially the TF.FileFunction and .FilePolarity data
  1012. if( aLayer < PCBNEW_LAYER_ID_START || aLayer >= PCB_LAYER_ID_COUNT )
  1013. {
  1014. wxLogError( wxString::Format( "Invalid board layer %d, cannot build a valid Gerber file header",
  1015. aLayer ) );
  1016. }
  1017. plotter = new GERBER_PLOTTER();
  1018. break;
  1019. case PLOT_FORMAT::SVG:
  1020. plotter = new SVG_PLOTTER();
  1021. break;
  1022. default:
  1023. wxASSERT( false );
  1024. return nullptr;
  1025. }
  1026. KIGFX::PCB_RENDER_SETTINGS* renderSettings = new KIGFX::PCB_RENDER_SETTINGS();
  1027. renderSettings->LoadColors( aPlotOpts->ColorSettings() );
  1028. renderSettings->SetDefaultPenWidth( pcbIUScale.mmToIU( 0.0212 ) ); // Hairline at 1200dpi
  1029. renderSettings->SetLayerName( aLayerName );
  1030. plotter->SetRenderSettings( renderSettings );
  1031. // Compute the viewport and set the other options
  1032. // page layout is not mirrored, so temporarily change mirror option for the page layout
  1033. PCB_PLOT_PARAMS plotOpts = *aPlotOpts;
  1034. if( plotOpts.GetPlotFrameRef() )
  1035. {
  1036. if( plotOpts.GetMirror() )
  1037. plotOpts.SetMirror( false );
  1038. if( plotOpts.GetScale() != 1.0 )
  1039. plotOpts.SetScale( 1.0 );
  1040. if( plotOpts.GetAutoScale() )
  1041. plotOpts.SetAutoScale( false );
  1042. }
  1043. initializePlotter( plotter, aBoard, &plotOpts );
  1044. if( plotter->OpenFile( aFullFileName ) )
  1045. {
  1046. plotter->ClearHeaderLinesList();
  1047. // For the Gerber "file function" attribute, set the layer number
  1048. if( plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
  1049. {
  1050. bool useX2mode = plotOpts.GetUseGerberX2format();
  1051. GERBER_PLOTTER* gbrplotter = static_cast <GERBER_PLOTTER*> ( plotter );
  1052. gbrplotter->DisableApertMacros( plotOpts.GetDisableGerberMacros() );
  1053. gbrplotter->UseX2format( useX2mode );
  1054. gbrplotter->UseX2NetAttributes( plotOpts.GetIncludeGerberNetlistInfo() );
  1055. // Attributes can be added using X2 format or as comment (X1 format)
  1056. AddGerberX2Attribute( plotter, aBoard, aLayer, not useX2mode );
  1057. }
  1058. bool startPlotSuccess = false;
  1059. try
  1060. {
  1061. if( plotter->GetPlotterType() == PLOT_FORMAT::PDF )
  1062. startPlotSuccess = static_cast<PDF_PLOTTER*>( plotter )->StartPlot( aPageNumber, aPageName );
  1063. else
  1064. startPlotSuccess = plotter->StartPlot( aPageName );
  1065. }
  1066. catch( ... )
  1067. {
  1068. startPlotSuccess = false;
  1069. }
  1070. if( startPlotSuccess )
  1071. {
  1072. if( aPlotOpts->GetFormat() == PLOT_FORMAT::PDF )
  1073. plotPdfBackground( aBoard, aPlotOpts, plotter );
  1074. // Plot the frame reference if requested
  1075. if( aPlotOpts->GetPlotFrameRef() )
  1076. {
  1077. PlotDrawingSheet( plotter, aBoard->GetProject(), aBoard->GetTitleBlock(),
  1078. aBoard->GetPageSettings(), &aBoard->GetProperties(), aPageNumber,
  1079. aPageCount, aSheetName, aSheetPath, aBoard->GetFileName(),
  1080. renderSettings->GetLayerColor( LAYER_DRAWINGSHEET ) );
  1081. if( aPlotOpts->GetMirror() || aPlotOpts->GetScale() != 1.0 || aPlotOpts->GetAutoScale() )
  1082. initializePlotter( plotter, aBoard, aPlotOpts );
  1083. }
  1084. // When plotting a negative board: draw a black rectangle (background for plot board
  1085. // in white) and switch the current color to WHITE; note the color inversion is actually
  1086. // done in the driver (if supported)
  1087. if( aPlotOpts->GetNegative() )
  1088. {
  1089. BOX2I bbox = aBoard->ComputeBoundingBox( false );
  1090. FillNegativeKnockout( plotter, bbox );
  1091. }
  1092. return plotter;
  1093. }
  1094. }
  1095. delete plotter->RenderSettings();
  1096. delete plotter;
  1097. return nullptr;
  1098. }
  1099. void setupPlotterNewPDFPage( PLOTTER* aPlotter, BOARD* aBoard, PCB_PLOT_PARAMS* aPlotOpts,
  1100. const wxString& aLayerName, const wxString& aSheetName,
  1101. const wxString& aSheetPath, const wxString& aPageNumber,
  1102. int aPageCount )
  1103. {
  1104. plotPdfBackground( aBoard, aPlotOpts, aPlotter );
  1105. aPlotter->RenderSettings()->SetLayerName( aLayerName );
  1106. // Plot the frame reference if requested
  1107. if( aPlotOpts->GetPlotFrameRef() )
  1108. {
  1109. // Mirror and scale shouldn't be applied to the drawing sheet
  1110. bool revertOps = false;
  1111. bool oldMirror = aPlotOpts->GetMirror();
  1112. bool oldAutoScale = aPlotOpts->GetAutoScale();
  1113. double oldScale = aPlotOpts->GetScale();
  1114. if( oldMirror || oldAutoScale || oldScale != 1.0 )
  1115. {
  1116. aPlotOpts->SetMirror( false );
  1117. aPlotOpts->SetScale( 1.0 );
  1118. aPlotOpts->SetAutoScale( false );
  1119. initializePlotter( aPlotter, aBoard, aPlotOpts );
  1120. revertOps = true;
  1121. }
  1122. PlotDrawingSheet( aPlotter, aBoard->GetProject(), aBoard->GetTitleBlock(),
  1123. aBoard->GetPageSettings(), &aBoard->GetProperties(), aPageNumber,
  1124. aPageCount,
  1125. aSheetName, aSheetPath, aBoard->GetFileName(),
  1126. aPlotter->RenderSettings()->GetLayerColor( LAYER_DRAWINGSHEET ) );
  1127. if( revertOps )
  1128. {
  1129. aPlotOpts->SetMirror( oldMirror );
  1130. aPlotOpts->SetScale( oldScale );
  1131. aPlotOpts->SetAutoScale( oldAutoScale );
  1132. initializePlotter( aPlotter, aBoard, aPlotOpts );
  1133. }
  1134. }
  1135. }