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