1 /**
2  * @file plot_board_layers.cpp
3  * @brief Functions to plot one board layer (silkscreen layers or other layers).
4  * Silkscreen layers have specific requirement for pads (not filled) and texts
5  * (with option to remove them from some copper areas (pads...)
6  */
7 
8 /*
9  * This program source code file is part of KiCad, a free EDA CAD application.
10  *
11  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
12  *
13  * This program is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU General Public License
15  * as published by the Free Software Foundation; either version 2
16  * of the License, or (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, you may find one here:
25  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
26  * or you may search the http://www.gnu.org website for the version 2 license,
27  * or you may write to the Free Software Foundation, Inc.,
28  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
29  */
30 
31 
32 #include <eda_item.h>
33 #include <geometry/geometry_utils.h>
34 #include <geometry/shape_segment.h>
35 #include <pcb_base_frame.h>
36 #include <math/util.h>      // for KiROUND
37 
38 #include <board.h>
39 #include <board_design_settings.h>
40 #include <core/arraydim.h>
41 #include <footprint.h>
42 #include <pcb_track.h>
43 #include <fp_shape.h>
44 #include <pad.h>
45 #include <pcb_text.h>
46 #include <zone.h>
47 #include <pcb_shape.h>
48 #include <pcb_target.h>
49 #include <pcb_dimension.h>
50 
51 #include <pcbplot.h>
52 #include <plotters/plotter_dxf.h>
53 #include <plotters/plotter_hpgl.h>
54 #include <plotters/plotter_gerber.h>
55 #include <plotters/plotters_pslike.h>
56 #include <pcb_painter.h>
57 #include <gbr_metadata.h>
58 #include <advanced_config.h>
59 
60 /*
61  * Plot a solder mask layer.  Solder mask layers have a minimum thickness value and cannot be
62  * drawn like standard layers, unless the minimum thickness is 0.
63  */
64 static void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
65                                  const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness );
66 
67 
PlotOneBoardLayer(BOARD * aBoard,PLOTTER * aPlotter,PCB_LAYER_ID aLayer,const PCB_PLOT_PARAMS & aPlotOpt)68 void PlotOneBoardLayer( BOARD *aBoard, PLOTTER* aPlotter, PCB_LAYER_ID aLayer,
69                         const PCB_PLOT_PARAMS& aPlotOpt )
70 {
71     PCB_PLOT_PARAMS plotOpt = aPlotOpt;
72     int soldermask_min_thickness = aBoard->GetDesignSettings().m_SolderMaskMinWidth;
73 
74     // Set a default color and the text mode for this layer
75     aPlotter->SetColor( BLACK );
76     aPlotter->SetTextMode( aPlotOpt.GetTextMode() );
77 
78     // Specify that the contents of the "Edges Pcb" layer are to be plotted in addition to the
79     // contents of the currently specified layer.
80     LSET    layer_mask( aLayer );
81 
82     if( !aPlotOpt.GetExcludeEdgeLayer() )
83         layer_mask.set( Edge_Cuts );
84 
85     if( IsCopperLayer( aLayer ) )
86     {
87         // Skip NPTH pads on copper layers ( only if hole size == pad size ):
88         // Drill mark will be plotted if drill mark is SMALL_DRILL_SHAPE  or FULL_DRILL_SHAPE
89         if( plotOpt.GetFormat() == PLOT_FORMAT::DXF )
90         {
91             plotOpt.SetSkipPlotNPTH_Pads( false );
92             PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
93         }
94         else
95         {
96             plotOpt.SetSkipPlotNPTH_Pads( true );
97             PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
98         }
99     }
100     else
101     {
102         switch( aLayer )
103         {
104         case B_Mask:
105         case F_Mask:
106             plotOpt.SetSkipPlotNPTH_Pads( false );
107             // Disable plot pad holes
108             plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
109 
110             // Plot solder mask:
111             if( soldermask_min_thickness == 0 )
112             {
113                 if( plotOpt.GetFormat() == PLOT_FORMAT::DXF )
114                     PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
115                 else
116                     PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
117             }
118             else
119             {
120                 PlotSolderMaskLayer( aBoard, aPlotter, layer_mask, plotOpt,
121                                      soldermask_min_thickness );
122             }
123 
124             break;
125 
126         case B_Adhes:
127         case F_Adhes:
128         case B_Paste:
129         case F_Paste:
130             plotOpt.SetSkipPlotNPTH_Pads( false );
131             // Disable plot pad holes
132             plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
133 
134             if( plotOpt.GetFormat() == PLOT_FORMAT::DXF )
135                 PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
136             else
137                 PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
138 
139             break;
140 
141         case F_SilkS:
142         case B_SilkS:
143             if( plotOpt.GetFormat() == PLOT_FORMAT::DXF && plotOpt.GetDXFPlotPolygonMode() )
144                 // PlotLayerOutlines() is designed only for DXF plotters.
145                 // and must not be used for other plot formats
146                 PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
147             else
148                 PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
149 
150             // Gerber: Subtract soldermask from silkscreen if enabled
151             if( aPlotter->GetPlotterType() == PLOT_FORMAT::GERBER
152                     && plotOpt.GetSubtractMaskFromSilk() )
153             {
154                 if( aLayer == F_SilkS )
155                     layer_mask = LSET( F_Mask );
156                 else
157                     layer_mask = LSET( B_Mask );
158 
159                 // Create the mask to subtract by creating a negative layer polarity
160                 aPlotter->SetLayerPolarity( false );
161 
162                 // Disable plot pad holes
163                 plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
164 
165                 // Plot the mask
166                 PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
167             }
168 
169             break;
170 
171         // These layers are plotted like silk screen layers.
172         // Mainly, pads on these layers are not filled.
173         // This is not necessary the best choice.
174         case Dwgs_User:
175         case Cmts_User:
176         case Eco1_User:
177         case Eco2_User:
178         case Edge_Cuts:
179         case Margin:
180         case F_CrtYd:
181         case B_CrtYd:
182         case F_Fab:
183         case B_Fab:
184             plotOpt.SetSkipPlotNPTH_Pads( false );
185             plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
186 
187             if( plotOpt.GetFormat() == PLOT_FORMAT::DXF && plotOpt.GetDXFPlotPolygonMode() )
188                 // PlotLayerOutlines() is designed only for DXF plotters.
189                 // and must not be used for other plot formats
190                 PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
191             else
192                 PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
193 
194             break;
195 
196         default:
197             plotOpt.SetSkipPlotNPTH_Pads( false );
198             plotOpt.SetDrillMarksType( PCB_PLOT_PARAMS::NO_DRILL_SHAPE );
199 
200             if( plotOpt.GetFormat() == PLOT_FORMAT::DXF && plotOpt.GetDXFPlotPolygonMode() )
201                 // PlotLayerOutlines() is designed only for DXF plotters.
202                 // and must not be used for other plot formats
203                 PlotLayerOutlines( aBoard, aPlotter, layer_mask, plotOpt );
204             else
205                 PlotStandardLayer( aBoard, aPlotter, layer_mask, plotOpt );
206 
207             break;
208         }
209     }
210 }
211 
212 
213 /**
214  * Plot a copper layer or mask.
215  *
216  * Silk screen layers are not plotted here.
217  */
PlotStandardLayer(BOARD * aBoard,PLOTTER * aPlotter,LSET aLayerMask,const PCB_PLOT_PARAMS & aPlotOpt)218 void PlotStandardLayer( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask,
219                         const PCB_PLOT_PARAMS& aPlotOpt )
220 {
221     BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
222 
223     itemplotter.SetLayerSet( aLayerMask );
224 
225     OUTLINE_MODE plotMode = aPlotOpt.GetPlotMode();
226     bool onCopperLayer = ( LSET::AllCuMask() & aLayerMask ).any();
227     bool onSolderMaskLayer = ( LSET( 2, F_Mask, B_Mask ) & aLayerMask ).any();
228     bool onSolderPasteLayer = ( LSET( 2, F_Paste, B_Paste ) & aLayerMask ).any();
229     bool onFrontFab = ( LSET(  F_Fab ) & aLayerMask ).any();
230     bool onBackFab  = ( LSET( B_Fab ) & aLayerMask ).any();
231     bool sketchPads = ( onFrontFab || onBackFab ) && aPlotOpt.GetSketchPadsOnFabLayers();
232 
233      // Plot edge layer and graphic items
234     itemplotter.PlotBoardGraphicItems();
235 
236     // Draw footprint texts:
237     for( const FOOTPRINT* footprint : aBoard->Footprints() )
238         itemplotter.PlotFootprintTextItems( footprint );
239 
240     // Draw footprint other graphic items:
241     for( const FOOTPRINT* footprint : aBoard->Footprints() )
242         itemplotter.PlotFootprintGraphicItems( footprint );
243 
244     // Plot footprint pads
245     for( FOOTPRINT* footprint : aBoard->Footprints() )
246     {
247         aPlotter->StartBlock( nullptr );
248 
249         for( PAD* pad : footprint->Pads() )
250         {
251             OUTLINE_MODE padPlotMode = plotMode;
252 
253             if( !( pad->GetLayerSet() & aLayerMask ).any() )
254             {
255                 if( sketchPads &&
256                         ( ( onFrontFab && pad->GetLayerSet().Contains( F_Cu ) ) ||
257                           ( onBackFab && pad->GetLayerSet().Contains( B_Cu ) ) ) )
258                 {
259                     padPlotMode = SKETCH;
260                 }
261                 else
262                 {
263                     continue;
264                 }
265             }
266 
267             /// pads not connected to copper are optionally not drawn
268             if( onCopperLayer && !pad->FlashLayer( aLayerMask ) )
269                 continue;
270 
271             COLOR4D color = COLOR4D::BLACK;
272 
273             if( pad->GetLayerSet()[B_Cu] )
274                color = aPlotOpt.ColorSettings()->GetColor( LAYER_PAD_BK );
275 
276             if( pad->GetLayerSet()[F_Cu] )
277                 color = color.LegacyMix( aPlotOpt.ColorSettings()->GetColor( LAYER_PAD_FR ) );
278 
279             if( sketchPads && aLayerMask[F_Fab] )
280                 color = aPlotOpt.ColorSettings()->GetColor( F_Fab );
281             else if( sketchPads && aLayerMask[B_Fab] )
282                 color = aPlotOpt.ColorSettings()->GetColor( B_Fab );
283 
284             wxSize margin;
285             int width_adj = 0;
286 
287             if( onCopperLayer )
288                 width_adj = itemplotter.getFineWidthAdj();
289 
290             if( onSolderMaskLayer )
291                 margin.x = margin.y = pad->GetSolderMaskMargin();
292 
293             if( onSolderPasteLayer )
294                 margin = pad->GetSolderPasteMargin();
295 
296             // not all shapes can have a different margin for x and y axis
297             // in fact only oval and rect shapes can have different values.
298             // Round shape have always the same x,y margin
299             // so define a unique value for other shapes that do not support different values
300             int mask_clearance = margin.x;
301 
302             // Now offset the pad size by margin + width_adj
303             wxSize padPlotsSize = pad->GetSize() + margin * 2 + wxSize( width_adj, width_adj );
304 
305             // Store these parameters that can be modified to plot inflated/deflated pads shape
306             PAD_SHAPE padShape = pad->GetShape();
307             wxSize      padSize  = pad->GetSize();
308             wxSize      padDelta = pad->GetDelta(); // has meaning only for trapezoidal pads
309             double      padCornerRadius = pad->GetRoundRectCornerRadius();
310 
311             // Don't draw a 0 sized pad.
312             // Note: a custom pad can have its pad anchor with size = 0
313             if( pad->GetShape() != PAD_SHAPE::CUSTOM
314                 && ( padPlotsSize.x <= 0 || padPlotsSize.y <= 0 ) )
315                 continue;
316 
317             switch( pad->GetShape() )
318             {
319             case PAD_SHAPE::CIRCLE:
320             case PAD_SHAPE::OVAL:
321                 pad->SetSize( padPlotsSize );
322 
323                 if( aPlotOpt.GetSkipPlotNPTH_Pads() &&
324                     ( aPlotOpt.GetDrillMarksType() == PCB_PLOT_PARAMS::NO_DRILL_SHAPE ) &&
325                     ( pad->GetSize() == pad->GetDrillSize() ) &&
326                     ( pad->GetAttribute() == PAD_ATTRIB::NPTH ) )
327                 {
328                     break;
329                 }
330 
331                 itemplotter.PlotPad( pad, color, padPlotMode );
332                 break;
333 
334             case PAD_SHAPE::RECT:
335                 pad->SetSize( padPlotsSize );
336 
337                 if( mask_clearance > 0 )
338                 {
339                     pad->SetShape( PAD_SHAPE::ROUNDRECT );
340                     pad->SetRoundRectCornerRadius( mask_clearance );
341                 }
342 
343                 itemplotter.PlotPad( pad, color, padPlotMode );
344                 break;
345 
346             case PAD_SHAPE::TRAPEZOID:
347                 // inflate/deflate a trapezoid is a bit complex.
348                 // so if the margin is not null, build a similar polygonal pad shape,
349                 // and inflate/deflate the polygonal shape
350                 // because inflating/deflating using different values for y and y
351                 // we are using only margin.x as inflate/deflate value
352                 if( mask_clearance == 0 )
353                 {
354                     itemplotter.PlotPad( pad, color, padPlotMode );
355                 }
356                 else
357                 {
358                     PAD dummy( *pad );
359                     dummy.SetAnchorPadShape( PAD_SHAPE::CIRCLE );
360                     dummy.SetShape( PAD_SHAPE::CUSTOM );
361                     SHAPE_POLY_SET outline;
362                     outline.NewOutline();
363                     int dx = padSize.x / 2;
364                     int dy = padSize.y / 2;
365                     int ddx = padDelta.x / 2;
366                     int ddy = padDelta.y / 2;
367 
368                     outline.Append( -dx - ddy,  dy + ddx );
369                     outline.Append(  dx + ddy,  dy - ddx );
370                     outline.Append(  dx - ddy, -dy + ddx );
371                     outline.Append( -dx + ddy, -dy - ddx );
372 
373                     // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
374                     // which can create bad shapes if margin.x is < 0
375                     int maxError = aBoard->GetDesignSettings().m_MaxError;
376                     int numSegs = GetArcToSegmentCount( mask_clearance, maxError, 360.0 );
377                     outline.InflateWithLinkedHoles( mask_clearance, numSegs,
378                                                     SHAPE_POLY_SET::PM_FAST );
379                     dummy.DeletePrimitivesList();
380                     dummy.AddPrimitivePoly( outline, 0, true );
381 
382                     // Be sure the anchor pad is not bigger than the deflated shape because this
383                     // anchor will be added to the pad shape when plotting the pad. So now the
384                     // polygonal shape is built, we can clamp the anchor size
385                     dummy.SetSize( wxSize( 0,0 ) );
386 
387                     itemplotter.PlotPad( &dummy, color, padPlotMode );
388                 }
389 
390                 break;
391 
392             case PAD_SHAPE::ROUNDRECT:
393             {
394                 // rounding is stored as a percent, but we have to change the new radius
395                 // to initial_radius + clearance to have a inflated/deflated similar shape
396                 int initial_radius = pad->GetRoundRectCornerRadius();
397                 pad->SetSize( padPlotsSize );
398                 pad->SetRoundRectCornerRadius( std::max( initial_radius + mask_clearance, 0 ) );
399 
400                 itemplotter.PlotPad( pad, color, padPlotMode );
401                 break;
402             }
403 
404             case PAD_SHAPE::CHAMFERED_RECT:
405                 if( mask_clearance == 0 )
406                 {
407                     // the size can be slightly inflated by width_adj (PS/PDF only)
408                     pad->SetSize( padPlotsSize );
409                     itemplotter.PlotPad( pad, color, padPlotMode );
410                 }
411                 else
412                 {
413                     // Due to the polygonal shape of a CHAMFERED_RECT pad, the best way is to
414                     // convert the pad shape to a full polygon, inflate/deflate the polygon
415                     // and use a dummy  CUSTOM pad to plot the final shape.
416                     PAD dummy( *pad );
417                     // Build the dummy pad outline with coordinates relative to the pad position
418                     // and orientation 0. The actual pos and rotation will be taken in account
419                     // later by the plot function
420                     dummy.SetPosition( wxPoint( 0, 0 ) );
421                     dummy.SetOrientation( 0 );
422                     SHAPE_POLY_SET outline;
423                     int maxError = aBoard->GetDesignSettings().m_MaxError;
424                     int numSegs = GetArcToSegmentCount( mask_clearance, maxError, 360.0 );
425                     dummy.TransformShapeWithClearanceToPolygon( outline, UNDEFINED_LAYER, 0,
426                                                                 maxError, ERROR_INSIDE );
427                     outline.InflateWithLinkedHoles( mask_clearance, numSegs,
428                                                     SHAPE_POLY_SET::PM_FAST );
429 
430                     // Initialize the dummy pad shape:
431                     dummy.SetAnchorPadShape( PAD_SHAPE::CIRCLE );
432                     dummy.SetShape( PAD_SHAPE::CUSTOM );
433                     dummy.DeletePrimitivesList();
434                     dummy.AddPrimitivePoly( outline, 0, true );
435 
436                     // Be sure the anchor pad is not bigger than the deflated shape because this
437                     // anchor will be added to the pad shape when plotting the pad.
438                     // So we set the anchor size to 0
439                     dummy.SetSize( wxSize( 0,0 ) );
440                     dummy.SetPosition( pad->GetPosition() );
441                     dummy.SetOrientation( pad->GetOrientation() );
442 
443                     itemplotter.PlotPad( &dummy, color, padPlotMode );
444                 }
445 
446                 break;
447 
448             case PAD_SHAPE::CUSTOM:
449             {
450                 // inflate/deflate a custom shape is a bit complex.
451                 // so build a similar pad shape, and inflate/deflate the polygonal shape
452                 PAD dummy( *pad );
453                 SHAPE_POLY_SET shape;
454                 pad->MergePrimitivesAsPolygon( &shape );
455 
456                 // Shape polygon can have holes so use InflateWithLinkedHoles(), not Inflate()
457                 // which can create bad shapes if margin.x is < 0
458                 int maxError = aBoard->GetDesignSettings().m_MaxError;
459                 int numSegs = GetArcToSegmentCount( mask_clearance, maxError, 360.0 );
460                 shape.InflateWithLinkedHoles( mask_clearance, numSegs, SHAPE_POLY_SET::PM_FAST );
461                 dummy.DeletePrimitivesList();
462                 dummy.AddPrimitivePoly( shape, 0, true );
463 
464                 // Be sure the anchor pad is not bigger than the deflated shape because this
465                 // anchor will be added to the pad shape when plotting the pad. So now the
466                 // polygonal shape is built, we can clamp the anchor size
467                 if( mask_clearance < 0 )  // we expect margin.x = margin.y for custom pads
468                     dummy.SetSize( padPlotsSize );
469 
470                 itemplotter.PlotPad( &dummy, color, padPlotMode );
471                 break;
472             }
473             }
474 
475             // Restore the pad parameters modified by the plot code
476             pad->SetSize( padSize );
477             pad->SetDelta( padDelta );
478             pad->SetShape( padShape );
479             pad->SetRoundRectCornerRadius( padCornerRadius );
480         }
481 
482         aPlotter->EndBlock( nullptr );
483     }
484 
485     // Plot vias on copper layers, and if aPlotOpt.GetPlotViaOnMaskLayer() is true,
486     // plot them on solder mask
487 
488     GBR_METADATA gbr_metadata;
489 
490     bool isOnCopperLayer = ( aLayerMask & LSET::AllCuMask() ).any();
491 
492     if( isOnCopperLayer )
493     {
494         gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_VIAPAD );
495         gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
496     }
497 
498     aPlotter->StartBlock( nullptr );
499 
500     for( const PCB_TRACK* track : aBoard->Tracks() )
501     {
502         const PCB_VIA* via = dyn_cast<const PCB_VIA*>( track );
503 
504         if( !via )
505             continue;
506 
507         // vias are not plotted if not on selected layer, but if layer is SOLDERMASK_LAYER_BACK
508         // or SOLDERMASK_LAYER_FRONT, vias are drawn only if they are on the corresponding
509         // external copper layer
510         LSET via_mask_layer = via->GetLayerSet();
511 
512         if( aPlotOpt.GetPlotViaOnMaskLayer() )
513         {
514             if( via_mask_layer[B_Cu] )
515                 via_mask_layer.set( B_Mask );
516 
517             if( via_mask_layer[F_Cu] )
518                 via_mask_layer.set( F_Mask );
519         }
520 
521         if( !( via_mask_layer & aLayerMask ).any() )
522             continue;
523 
524         int via_margin = 0;
525         double width_adj = 0;
526 
527         // If the current layer is a solder mask, use the global mask clearance for vias
528         if( aLayerMask[B_Mask] || aLayerMask[F_Mask] )
529             via_margin = aBoard->GetDesignSettings().m_SolderMaskMargin;
530 
531         if( ( aLayerMask & LSET::AllCuMask() ).any() )
532             width_adj = itemplotter.getFineWidthAdj();
533 
534         int diameter = via->GetWidth() + 2 * via_margin + width_adj;
535 
536         /// Vias not connected to copper are optionally not drawn
537         if( onCopperLayer && !via->FlashLayer( aLayerMask ) )
538             continue;
539 
540         // Don't draw a null size item :
541         if( diameter <= 0 )
542             continue;
543 
544         // Some vias can be not connected (no net).
545         // Set the m_NotInNet for these vias to force a empty net name in gerber file
546         gbr_metadata.m_NetlistMetadata.m_NotInNet = via->GetNetname().IsEmpty();
547 
548         gbr_metadata.SetNetName( via->GetNetname() );
549 
550         COLOR4D color = aPlotOpt.ColorSettings()->GetColor(
551                 LAYER_VIAS + static_cast<int>( via->GetViaType() ) );
552 
553         // Set plot color (change WHITE to LIGHTGRAY because the white items are not seen on a
554         // white paper or screen
555         aPlotter->SetColor( color != WHITE ? color : LIGHTGRAY );
556         aPlotter->FlashPadCircle( via->GetStart(), diameter, plotMode, &gbr_metadata );
557     }
558 
559     aPlotter->EndBlock( nullptr );
560     aPlotter->StartBlock( nullptr );
561     gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
562 
563     // Plot tracks (not vias) :
564     for( const PCB_TRACK* track : aBoard->Tracks() )
565     {
566         if( track->Type() == PCB_VIA_T )
567             continue;
568 
569         if( !aLayerMask[track->GetLayer()] )
570             continue;
571 
572         // Some track segments can be not connected (no net).
573         // Set the m_NotInNet for these segments to force a empty net name in gerber file
574         gbr_metadata.m_NetlistMetadata.m_NotInNet = track->GetNetname().IsEmpty();
575 
576         gbr_metadata.SetNetName( track->GetNetname() );
577         int width = track->GetWidth() + itemplotter.getFineWidthAdj();
578         aPlotter->SetColor( itemplotter.getColor( track->GetLayer() ) );
579 
580         if( track->Type() == PCB_ARC_T )
581         {
582             const    PCB_ARC* arc = static_cast<const PCB_ARC*>( track );
583             VECTOR2D center( arc->GetCenter() );
584             int      radius = arc->GetRadius();
585             double   start_angle = arc->GetArcAngleStart();
586             double   end_angle = start_angle + arc->GetAngle();
587 
588             aPlotter->ThickArc( wxPoint( center.x, center.y ), -end_angle, -start_angle,
589                                 radius, width, plotMode, &gbr_metadata );
590         }
591         else
592         {
593             aPlotter->ThickSegment( track->GetStart(), track->GetEnd(), width, plotMode,
594                                     &gbr_metadata );
595         }
596     }
597 
598     aPlotter->EndBlock( nullptr );
599 
600     // Plot filled ares
601     aPlotter->StartBlock( nullptr );
602 
603     NETINFO_ITEM nonet( aBoard );
604 
605     for( const ZONE* zone : aBoard->Zones() )
606     {
607         for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
608         {
609             if( !aLayerMask[layer] )
610                 continue;
611 
612             SHAPE_POLY_SET mainArea = zone->GetFilledPolysList( layer );
613             SHAPE_POLY_SET islands;
614 
615             for( int i = mainArea.OutlineCount() - 1; i >= 0; i-- )
616             {
617                 if( zone->IsIsland( layer, i ) )
618                 {
619                     islands.AddOutline( mainArea.CPolygon( i )[0] );
620                     mainArea.DeletePolygon( i );
621                 }
622             }
623 
624             itemplotter.PlotFilledAreas( zone, mainArea );
625 
626             if( !islands.IsEmpty() )
627             {
628                 ZONE dummy( *zone );
629                 dummy.SetNet( &nonet );
630                 itemplotter.PlotFilledAreas( &dummy, islands );
631             }
632         }
633     }
634 
635     aPlotter->EndBlock( nullptr );
636 
637     // Adding drill marks, if required and if the plotter is able to plot them:
638     if( aPlotOpt.GetDrillMarksType() != PCB_PLOT_PARAMS::NO_DRILL_SHAPE )
639         itemplotter.PlotDrillMarks();
640 }
641 
642 
643 // Seems like we want to plot from back to front?
644 static const PCB_LAYER_ID plot_seq[] = {
645 
646     B_Adhes,        // 32
647     F_Adhes,
648     B_Paste,
649     F_Paste,
650     B_SilkS,
651     B_Mask,
652     F_Mask,
653     Dwgs_User,
654     Cmts_User,
655     Eco1_User,
656     Eco2_User,
657     Edge_Cuts,
658     Margin,
659 
660     F_CrtYd,        // CrtYd & Body are footprint only
661     B_CrtYd,
662     F_Fab,
663     B_Fab,
664 
665     B_Cu,
666     In30_Cu,
667     In29_Cu,
668     In28_Cu,
669     In27_Cu,
670     In26_Cu,
671     In25_Cu,
672     In24_Cu,
673     In23_Cu,
674     In22_Cu,
675     In21_Cu,
676     In20_Cu,
677     In19_Cu,
678     In18_Cu,
679     In17_Cu,
680     In16_Cu,
681     In15_Cu,
682     In14_Cu,
683     In13_Cu,
684     In12_Cu,
685     In11_Cu,
686     In10_Cu,
687     In9_Cu,
688     In8_Cu,
689     In7_Cu,
690     In6_Cu,
691     In5_Cu,
692     In4_Cu,
693     In3_Cu,
694     In2_Cu,
695     In1_Cu,
696     F_Cu,
697 
698     F_SilkS,
699 };
700 
701 
702 /**
703  * Plot outlines of copper layer.
704  */
PlotLayerOutlines(BOARD * aBoard,PLOTTER * aPlotter,LSET aLayerMask,const PCB_PLOT_PARAMS & aPlotOpt)705 void PlotLayerOutlines( BOARD* aBoard, PLOTTER* aPlotter, LSET aLayerMask,
706                         const PCB_PLOT_PARAMS& aPlotOpt )
707 {
708     BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
709     itemplotter.SetLayerSet( aLayerMask );
710 
711     SHAPE_POLY_SET outlines;
712 
713     for( LSEQ seq = aLayerMask.Seq( plot_seq, arrayDim( plot_seq ) );  seq;  ++seq )
714     {
715         PCB_LAYER_ID layer = *seq;
716 
717         outlines.RemoveAllContours();
718         aBoard->ConvertBrdLayerToPolygonalContours( layer, outlines );
719 
720         outlines.Simplify( SHAPE_POLY_SET::PM_FAST );
721 
722         // Plot outlines
723         std::vector<wxPoint> cornerList;
724 
725         // Now we have one or more basic polygons: plot each polygon
726         for( int ii = 0; ii < outlines.OutlineCount(); ii++ )
727         {
728             for( int kk = 0; kk <= outlines.HoleCount(ii); kk++ )
729             {
730                 cornerList.clear();
731                 const SHAPE_LINE_CHAIN& path =
732                         ( kk == 0 ) ? outlines.COutline( ii ) : outlines.CHole( ii, kk - 1 );
733 
734                 aPlotter->PlotPoly( path, FILL_T::NO_FILL );
735             }
736         }
737 
738         // Plot pad holes
739         if( aPlotOpt.GetDrillMarksType() != PCB_PLOT_PARAMS::NO_DRILL_SHAPE )
740         {
741             int smallDrill = (aPlotOpt.GetDrillMarksType() == PCB_PLOT_PARAMS::SMALL_DRILL_SHAPE)
742                                   ? ADVANCED_CFG::GetCfg().m_SmallDrillMarkSize : INT_MAX;
743 
744             for( FOOTPRINT* footprint : aBoard->Footprints() )
745             {
746                 for( PAD* pad : footprint->Pads() )
747                 {
748                     wxSize hole = pad->GetDrillSize();
749 
750                     if( hole.x == 0 || hole.y == 0 )
751                         continue;
752 
753                     if( hole.x == hole.y )
754                     {
755                         hole.x = std::min( smallDrill, hole.x );
756                         aPlotter->Circle( pad->GetPosition(), hole.x, FILL_T::NO_FILL );
757                     }
758                     else
759                     {
760                         // Note: small drill marks have no significance when applied to slots
761                         const SHAPE_SEGMENT* seg = pad->GetEffectiveHoleShape();
762                         aPlotter->ThickSegment( (wxPoint) seg->GetSeg().A,
763                                                 (wxPoint) seg->GetSeg().B,
764                                                 seg->GetWidth(), SKETCH, nullptr );
765                     }
766                 }
767             }
768         }
769 
770         // Plot vias holes
771         for( PCB_TRACK* track : aBoard->Tracks() )
772         {
773             const PCB_VIA* via = dyn_cast<const PCB_VIA*>( track );
774 
775             if( via && via->IsOnLayer( layer ) )    // via holes can be not through holes
776                 aPlotter->Circle( via->GetPosition(), via->GetDrillValue(), FILL_T::NO_FILL );
777         }
778     }
779 }
780 
781 
782 /**
783  * Plot a solder mask layer.
784  *
785  * Solder mask layers have a minimum thickness value and cannot be drawn like standard layers,
786  * unless the minimum thickness is 0.
787  * Currently the algo is:
788  * 1 - build all pad shapes as polygons with a size inflated by
789  *      mask clearance + (min width solder mask /2)
790  * 2 - Merge shapes
791  * 3 - deflate result by (min width solder mask /2)
792  * 4 - ORing result by all pad shapes as polygons with a size inflated by
793  *      mask clearance only (because deflate sometimes creates shape artifacts)
794  * 5 - draw result as polygons
795  *
796  * We have 2 algos:
797  * the initial algo, that create polygons for every shape, inflate and deflate polygons
798  * with Min Thickness/2, and merges the result.
799  * Drawback: pads attributes are lost (annoying in Gerber)
800  * the new algo:
801  * create initial polygons for every shape (pad or polygon),
802  * inflate and deflate polygons
803  * with Min Thickness/2, and merges the result (like initial algo)
804  * remove all initial polygons.
805  * The remaining polygons are areas with thickness < min thickness
806  * plot all initial shapes by flashing (or using regions) for pad and polygons
807  * (shapes will be better) and remaining polygons to
808  * remove areas with thickness < min thickness from final mask
809  *
810  * TODO: remove old code after more testing.
811  */
812 #define NEW_ALGO 1
813 
PlotSolderMaskLayer(BOARD * aBoard,PLOTTER * aPlotter,LSET aLayerMask,const PCB_PLOT_PARAMS & aPlotOpt,int aMinThickness)814 void PlotSolderMaskLayer( BOARD *aBoard, PLOTTER* aPlotter, LSET aLayerMask,
815                           const PCB_PLOT_PARAMS& aPlotOpt, int aMinThickness )
816 {
817     int             maxError = aBoard->GetDesignSettings().m_MaxError;
818     PCB_LAYER_ID    layer = aLayerMask[B_Mask] ? B_Mask : F_Mask;
819     SHAPE_POLY_SET  buffer;
820     SHAPE_POLY_SET* boardOutline = nullptr;
821 
822     if( aBoard->GetBoardPolygonOutlines( buffer ) )
823         boardOutline = &buffer;
824 
825     // We remove 1nm as we expand both sides of the shapes, so allowing for
826     // a strictly greater than or equal comparison in the shape separation (boolean add)
827     // means that we will end up with separate shapes that then are shrunk
828     int inflate = aMinThickness/2 - 1;
829 
830     BRDITEMS_PLOTTER itemplotter( aPlotter, aBoard, aPlotOpt );
831     itemplotter.SetLayerSet( aLayerMask );
832 
833     // Plot edge layer and graphic items.
834     // They do not have a solder Mask margin, because they are graphic items
835     // on this layer (like logos), not actually areas around pads.
836 
837     itemplotter.PlotBoardGraphicItems();
838 
839     for( FOOTPRINT* footprint : aBoard->Footprints() )
840     {
841         itemplotter.PlotFootprintTextItems( footprint );
842 
843         for( BOARD_ITEM* item : footprint->GraphicalItems() )
844         {
845             if( item->Type() == PCB_FP_SHAPE_T && item->GetLayer() == layer )
846                 itemplotter.PlotFootprintGraphicItem( (FP_SHAPE*) item );
847         }
848     }
849 
850     // Build polygons for each pad shape.  The size of the shape on solder mask should be size
851     // of pad + clearance around the pad, where clearance = solder mask clearance + extra margin.
852     // Extra margin is half the min width for solder mask, which is used to merge too-close shapes
853     // (distance < aMinThickness), and will be removed when creating the actual shapes.
854 
855     // Will contain shapes inflated by inflate value that will be merged and deflated by
856     // inflate value to build final polygons
857     // After calculations the remaining polygons are polygons to plot
858     SHAPE_POLY_SET areas;
859 
860     // Will contain exact shapes of all items on solder mask
861     SHAPE_POLY_SET initialPolys;
862 
863 #if NEW_ALGO
864     // Generate polygons with arcs inside the shape or exact shape
865     // to minimize shape changes created by arc to segment size correction.
866     DISABLE_ARC_RADIUS_CORRECTION disabler;
867 #endif
868     {
869         // Plot pads
870         for( FOOTPRINT* footprint : aBoard->Footprints() )
871         {
872             // add shapes with their exact mask layer size in initialPolys
873             footprint->TransformPadsWithClearanceToPolygon( initialPolys, layer, 0, maxError,
874                                                             ERROR_OUTSIDE );
875             // add shapes inflated by aMinThickness/2 in areas
876             footprint->TransformPadsWithClearanceToPolygon( areas, layer, inflate, maxError,
877                                                             ERROR_OUTSIDE );
878         }
879 
880         // Plot vias on solder masks, if aPlotOpt.GetPlotViaOnMaskLayer() is true,
881         if( aPlotOpt.GetPlotViaOnMaskLayer() )
882         {
883             // The current layer is a solder mask, use the global mask clearance for vias
884             int via_clearance = aBoard->GetDesignSettings().m_SolderMaskMargin;
885             int via_margin = via_clearance + inflate;
886 
887             for( PCB_TRACK* track : aBoard->Tracks() )
888             {
889                 const PCB_VIA* via = dyn_cast<const PCB_VIA*>( track );
890 
891                 if( !via )
892                     continue;
893 
894                 // vias are plotted only if they are on the corresponding external copper layer
895                 LSET via_set = via->GetLayerSet();
896 
897                 if( via_set[B_Cu] )
898                     via_set.set( B_Mask );
899 
900                 if( via_set[F_Cu] )
901                     via_set.set( F_Mask );
902 
903                 if( !( via_set & aLayerMask ).any() )
904                     continue;
905 
906                 // add shapes with their exact mask layer size in initialPolys
907                 via->TransformShapeWithClearanceToPolygon( initialPolys, layer, via_clearance,
908                                                            maxError, ERROR_OUTSIDE );
909                 // add shapes inflated by aMinThickness/2 in areas
910                 via->TransformShapeWithClearanceToPolygon( areas, layer, via_margin, maxError,
911                                                            ERROR_OUTSIDE );
912             }
913         }
914 
915         // Add filled zone areas.
916 #if 0   // Set to 1 if a solder mask margin must be applied to zones on solder mask
917         int zone_margin = aBoard->GetDesignSettings().m_SolderMaskMargin;
918 #else
919         int zone_margin = 0;
920 #endif
921 
922         for( ZONE* zone : aBoard->Zones() )
923         {
924             if( !zone->IsOnLayer( layer ) )
925                 continue;
926 
927             // add shapes inflated by aMinThickness/2 in areas
928             zone->TransformSmoothedOutlineToPolygon( areas, inflate + zone_margin, boardOutline );
929 
930             // add shapes with their exact mask layer size in initialPolys
931             zone->TransformSmoothedOutlineToPolygon( initialPolys, zone_margin, boardOutline );
932         }
933     }
934 
935     int numSegs = GetArcToSegmentCount( inflate, maxError, 360.0 );
936 
937     // Merge all polygons: After deflating, not merged (not overlapping) polygons
938     // will have the initial shape (with perhaps small changes due to deflating transform)
939     areas.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
940     areas.Deflate( inflate, numSegs );
941 
942 #if !NEW_ALGO
943     // To avoid a lot of code, use a ZONE to handle and plot polygons, because our polygons look
944     // exactly like filled areas in zones.
945     // Note, also this code is not optimized: it creates a lot of copy/duplicate data.
946     // However it is not complex, and fast enough for plot purposes (copy/convert data is only a
947     // very small calculation time for these calculations).
948     ZONE zone( aBoard );
949     zone.SetMinThickness( 0 );      // trace polygons only
950     zone.SetLayer( layer );
951 
952     // Combine the current areas to initial areas. This is mandatory because inflate/deflate
953     // transform is not perfect, and we want the initial areas perfectly kept
954     areas.BooleanAdd( initialPolys, SHAPE_POLY_SET::PM_FAST );
955     areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
956 
957     itemplotter.PlotFilledAreas( &zone, areas );
958 #else
959 
960     // Remove initial shapes: each shape will be added later, as flashed item or region
961     // with a suitable attribute.
962     // Do not merge pads is mandatory in Gerber files: They must be identified as pads
963 
964     // we deflate areas in polygons, to avoid after subtracting initial shapes
965     // having small artifacts due to approximations during polygon transforms
966     areas.BooleanSubtract( initialPolys, SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
967 
968     // Slightly inflate polygons to avoid any gap between them and other shapes,
969     // These gaps are created by arc to segments approximations
970     areas.Inflate( Millimeter2iu( 0.002 ), 6 );
971 
972     // Now, only polygons with a too small thickness are stored in areas.
973     areas.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
974 
975     // Plot each initial shape (pads and polygons on mask layer), with suitable attributes:
976     PlotStandardLayer( aBoard, aPlotter, aLayerMask, aPlotOpt );
977 
978     for( int ii = 0; ii < areas.OutlineCount(); ii++ )
979     {
980         const SHAPE_LINE_CHAIN& path = areas.COutline( ii );
981 
982         // polygon area in mm^2 :
983         double curr_area = path.Area() / ( IU_PER_MM * IU_PER_MM );
984 
985         // Skip very small polygons: they are certainly artifacts created by
986         // arc approximations and polygon transforms
987         // (inflate/deflate transforms)
988         constexpr double poly_min_area_mm2 = 0.01;     // 0.01 mm^2 gives a good filtering
989 
990         if( curr_area < poly_min_area_mm2 )
991             continue;
992 
993         aPlotter->PlotPoly( path, FILL_T::FILLED_SHAPE );
994     }
995 #endif
996 }
997 
998 
999 /**
1000  * Set up most plot options for plotting a board (especially the viewport)
1001  * Important thing:
1002  *      page size is the 'drawing' page size,
1003  *      paper size is the physical page size
1004  */
initializePlotter(PLOTTER * aPlotter,const BOARD * aBoard,const PCB_PLOT_PARAMS * aPlotOpts)1005 static void initializePlotter( PLOTTER* aPlotter, const BOARD* aBoard,
1006                                const PCB_PLOT_PARAMS* aPlotOpts )
1007 {
1008     PAGE_INFO pageA4( wxT( "A4" ) );
1009     const PAGE_INFO& pageInfo = aBoard->GetPageSettings();
1010     const PAGE_INFO* sheet_info;
1011     double paperscale; // Page-to-paper ratio
1012     wxSize paperSizeIU;
1013     wxSize pageSizeIU( pageInfo.GetSizeIU() );
1014     bool autocenter = false;
1015 
1016     // Special options: to fit the sheet to an A4 sheet replace the paper size. However there
1017     // is a difference between the autoscale and the a4paper option:
1018     //  - Autoscale fits the board to the paper size
1019     //  - A4paper fits the original paper size to an A4 sheet
1020     //  - Both of them fit the board to an A4 sheet
1021     if( aPlotOpts->GetA4Output() )
1022     {
1023         sheet_info  = &pageA4;
1024         paperSizeIU = pageA4.GetSizeIU();
1025         paperscale  = (double) paperSizeIU.x / pageSizeIU.x;
1026         autocenter  = true;
1027     }
1028     else
1029     {
1030         sheet_info  = &pageInfo;
1031         paperSizeIU = pageSizeIU;
1032         paperscale  = 1;
1033 
1034         // Need autocentering only if scale is not 1:1
1035         autocenter  = (aPlotOpts->GetScale() != 1.0);
1036     }
1037 
1038     EDA_RECT bbox = aBoard->ComputeBoundingBox();
1039     wxPoint boardCenter = bbox.Centre();
1040     wxSize boardSize = bbox.GetSize();
1041 
1042     double compound_scale;
1043 
1044     // Fit to 80% of the page if asked; it could be that the board is empty, in this case
1045     // regress to 1:1 scale
1046     if( aPlotOpts->GetAutoScale() && boardSize.x > 0 && boardSize.y > 0 )
1047     {
1048         double xscale = (paperSizeIU.x * 0.8) / boardSize.x;
1049         double yscale = (paperSizeIU.y * 0.8) / boardSize.y;
1050 
1051         compound_scale = std::min( xscale, yscale ) * paperscale;
1052     }
1053     else
1054     {
1055         compound_scale = aPlotOpts->GetScale() * paperscale;
1056     }
1057 
1058     // For the plot offset we have to keep in mind the auxiliary origin too: if autoscaling is
1059     // off we check that plot option (i.e. autoscaling overrides auxiliary origin)
1060     wxPoint offset( 0, 0);
1061 
1062     if( autocenter )
1063     {
1064         offset.x = KiROUND( boardCenter.x - ( paperSizeIU.x / 2.0 ) / compound_scale );
1065         offset.y = KiROUND( boardCenter.y - ( paperSizeIU.y / 2.0 ) / compound_scale );
1066     }
1067     else
1068     {
1069         if( aPlotOpts->GetUseAuxOrigin() )
1070             offset = aBoard->GetDesignSettings().GetAuxOrigin();
1071     }
1072 
1073     aPlotter->SetPageSettings( *sheet_info );
1074 
1075     aPlotter->SetViewport( offset, IU_PER_MILS/10, compound_scale, aPlotOpts->GetMirror() );
1076 
1077     // Has meaning only for gerber plotter. Must be called only after SetViewport
1078     aPlotter->SetGerberCoordinatesFormat( aPlotOpts->GetGerberPrecision() );
1079 
1080     // Has meaning only for SVG plotter. Must be called only after SetViewport
1081     aPlotter->SetSvgCoordinatesFormat( aPlotOpts->GetSvgPrecision(), aPlotOpts->GetSvgUseInch() );
1082 
1083     aPlotter->SetCreator( wxT( "PCBNEW" ) );
1084     aPlotter->SetColorMode( false );        // default is plot in Black and White.
1085     aPlotter->SetTextMode( aPlotOpts->GetTextMode() );
1086 }
1087 
1088 
1089 /**
1090  * Prefill in black an area a little bigger than the board to prepare for the negative plot
1091  */
FillNegativeKnockout(PLOTTER * aPlotter,const EDA_RECT & aBbbox)1092 static void FillNegativeKnockout( PLOTTER *aPlotter, const EDA_RECT &aBbbox )
1093 {
1094     const int margin = 5 * IU_PER_MM;   // Add a 5 mm margin around the board
1095     aPlotter->SetNegative( true );
1096     aPlotter->SetColor( WHITE );        // Which will be plotted as black
1097     EDA_RECT area = aBbbox;
1098     area.Inflate( margin );
1099     aPlotter->Rect( area.GetOrigin(), area.GetEnd(), FILL_T::FILLED_SHAPE );
1100     aPlotter->SetColor( BLACK );
1101 }
1102 
1103 
1104 /**
1105  * Calculate the effective size of HPGL pens and set them in the plotter object
1106  */
ConfigureHPGLPenSizes(HPGL_PLOTTER * aPlotter,const PCB_PLOT_PARAMS * aPlotOpts)1107 static void ConfigureHPGLPenSizes( HPGL_PLOTTER *aPlotter, const PCB_PLOT_PARAMS *aPlotOpts )
1108 {
1109     // Compute penDiam (the value is given in mils) in pcb units, with plot scale (if Scale is 2,
1110     // penDiam value is always m_HPGLPenDiam so apparent penDiam is actually penDiam / Scale
1111     int penDiam = KiROUND( aPlotOpts->GetHPGLPenDiameter() * IU_PER_MILS / aPlotOpts->GetScale() );
1112 
1113     // Set HPGL-specific options and start
1114     aPlotter->SetPenSpeed( aPlotOpts->GetHPGLPenSpeed() );
1115     aPlotter->SetPenNumber( aPlotOpts->GetHPGLPenNum() );
1116     aPlotter->SetPenDiameter( penDiam );
1117 }
1118 
1119 
1120 /**
1121  * Open a new plotfile using the options (and especially the format) specified in the options
1122  * and prepare the page for plotting.
1123  *
1124  * @return the plotter object if OK, NULL if the file is not created (or has a problem).
1125  */
StartPlotBoard(BOARD * aBoard,const PCB_PLOT_PARAMS * aPlotOpts,int aLayer,const wxString & aFullFileName,const wxString & aSheetDesc)1126 PLOTTER* StartPlotBoard( BOARD *aBoard, const PCB_PLOT_PARAMS *aPlotOpts, int aLayer,
1127                          const wxString& aFullFileName, const wxString& aSheetDesc )
1128 {
1129     // Create the plotter driver and set the few plotter specific options
1130     PLOTTER*    plotter = nullptr;
1131 
1132     switch( aPlotOpts->GetFormat() )
1133     {
1134     case PLOT_FORMAT::DXF:
1135         DXF_PLOTTER* DXF_plotter;
1136         DXF_plotter = new DXF_PLOTTER();
1137         DXF_plotter->SetUnits( aPlotOpts->GetDXFPlotUnits() );
1138 
1139         plotter = DXF_plotter;
1140         break;
1141 
1142     case PLOT_FORMAT::POST:
1143         PS_PLOTTER* PS_plotter;
1144         PS_plotter = new PS_PLOTTER();
1145         PS_plotter->SetScaleAdjust( aPlotOpts->GetFineScaleAdjustX(),
1146                                     aPlotOpts->GetFineScaleAdjustY() );
1147         plotter = PS_plotter;
1148         break;
1149 
1150     case PLOT_FORMAT::PDF:
1151         plotter = new PDF_PLOTTER();
1152         break;
1153 
1154     case PLOT_FORMAT::HPGL:
1155         HPGL_PLOTTER* HPGL_plotter;
1156         HPGL_plotter = new HPGL_PLOTTER();
1157 
1158         // HPGL options are a little more convoluted to compute, so they get their own function
1159         ConfigureHPGLPenSizes( HPGL_plotter, aPlotOpts );
1160         plotter = HPGL_plotter;
1161         break;
1162 
1163     case PLOT_FORMAT::GERBER:
1164         plotter = new GERBER_PLOTTER();
1165         break;
1166 
1167     case PLOT_FORMAT::SVG:
1168         plotter = new SVG_PLOTTER();
1169         break;
1170 
1171     default:
1172         wxASSERT( false );
1173         return nullptr;
1174     }
1175 
1176     KIGFX::PCB_RENDER_SETTINGS* renderSettings = new KIGFX::PCB_RENDER_SETTINGS();
1177     renderSettings->LoadColors( aPlotOpts->ColorSettings() );
1178     renderSettings->SetDefaultPenWidth( Millimeter2iu( 0.0212 ) );  // Hairline at 1200dpi
1179     plotter->SetRenderSettings( renderSettings );
1180 
1181     // Compute the viewport and set the other options
1182 
1183     // page layout is not mirrored, so temporarily change mirror option for the page layout
1184     PCB_PLOT_PARAMS plotOpts = *aPlotOpts;
1185 
1186     if( plotOpts.GetPlotFrameRef() && plotOpts.GetMirror() )
1187         plotOpts.SetMirror( false );
1188 
1189     initializePlotter( plotter, aBoard, &plotOpts );
1190 
1191     if( plotter->OpenFile( aFullFileName ) )
1192     {
1193         plotter->ClearHeaderLinesList();
1194 
1195         // For the Gerber "file function" attribute, set the layer number
1196         if( plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
1197         {
1198             bool useX2mode = plotOpts.GetUseGerberX2format();
1199 
1200             GERBER_PLOTTER* gbrplotter = static_cast <GERBER_PLOTTER*> ( plotter );
1201             gbrplotter->DisableApertMacros( plotOpts.GetDisableGerberMacros() );
1202             gbrplotter->UseX2format( useX2mode );
1203             gbrplotter->UseX2NetAttributes( plotOpts.GetIncludeGerberNetlistInfo() );
1204 
1205             // Attributes can be added using X2 format or as comment (X1 format)
1206             AddGerberX2Attribute( plotter, aBoard, aLayer, not useX2mode );
1207         }
1208 
1209         plotter->StartPlot();
1210 
1211         // Plot the frame reference if requested
1212         if( aPlotOpts->GetPlotFrameRef() )
1213         {
1214             PlotDrawingSheet( plotter, aBoard->GetProject(), aBoard->GetTitleBlock(),
1215                               aBoard->GetPageSettings(), "1", 1, aSheetDesc,
1216                               aBoard->GetFileName() );
1217 
1218             if( aPlotOpts->GetMirror() )
1219                 initializePlotter( plotter, aBoard, aPlotOpts );
1220         }
1221 
1222         // When plotting a negative board: draw a black rectangle (background for plot board
1223         // in white) and switch the current color to WHITE; note the color inversion is actually
1224         // done in the driver (if supported)
1225         if( aPlotOpts->GetNegative() )
1226         {
1227             EDA_RECT bbox = aBoard->ComputeBoundingBox();
1228             FillNegativeKnockout( plotter, bbox );
1229         }
1230 
1231         return plotter;
1232     }
1233 
1234     delete plotter->RenderSettings();
1235     delete plotter;
1236     return nullptr;
1237 }
1238