1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
19 * or you may search the http://www.gnu.org website for the version 2 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24 #include <algorithm> // for min
25 #include <bitset> // for bitset, operator&, __bi...
26 #include <math.h> // for abs
27 #include <stddef.h> // for NULL, size_t
28
29 #include <geometry/seg.h> // for SEG
30 #include <geometry/shape_circle.h>
31 #include <geometry/shape_line_chain.h> // for SHAPE_LINE_CHAIN
32 #include <geometry/shape_poly_set.h> // for SHAPE_POLY_SET, SHAPE_P...
33 #include <geometry/shape_segment.h>
34 #include <string_utils.h>
35 #include <macros.h>
36 #include <math/util.h> // for KiROUND, Clamp
37 #include <math/vector2d.h> // for VECTOR2I
38 #include <plotters/plotter_gerber.h>
39 #include <trigo.h>
40
41 #include <board_design_settings.h> // for BOARD_DESIGN_SETTINGS
42 #include <core/typeinfo.h> // for dyn_cast, PCB_DIMENSION_T
43 #include <gbr_metadata.h>
44 #include <gbr_netlist_metadata.h> // for GBR_NETLIST_METADATA
45 #include <layer_ids.h> // for LSET, IsCopperLayer
46 #include <pad_shapes.h> // for PAD_ATTRIB::NPTH
47 #include <pcbplot.h>
48 #include <pcb_plot_params.h> // for PCB_PLOT_PARAMS, PCB_PL...
49 #include <advanced_config.h>
50
51 #include <board.h>
52 #include <board_item.h> // for BOARD_ITEM, S_CIRCLE
53 #include <pcb_dimension.h>
54 #include <pcb_shape.h>
55 #include <fp_shape.h>
56 #include <footprint.h>
57 #include <fp_text.h>
58 #include <pcb_track.h>
59 #include <pad.h>
60 #include <pcb_target.h>
61 #include <pcb_text.h>
62 #include <zone.h>
63
64 #include <wx/debug.h> // for wxASSERT_MSG
65 #include <wx/gdicmn.h>
66
67
getColor(LAYER_NUM aLayer) const68 COLOR4D BRDITEMS_PLOTTER::getColor( LAYER_NUM aLayer ) const
69 {
70 COLOR4D color = ColorSettings()->GetColor( aLayer );
71
72 // A hack to avoid plotting a white item in white color, expecting the paper
73 // is also white: use a non white color:
74 if( color == COLOR4D::WHITE )
75 color = COLOR4D( LIGHTGRAY );
76
77 return color;
78 }
79
80
PlotPad(const PAD * aPad,const COLOR4D & aColor,OUTLINE_MODE aPlotMode)81 void BRDITEMS_PLOTTER::PlotPad( const PAD* aPad, const COLOR4D& aColor, OUTLINE_MODE aPlotMode )
82 {
83 wxPoint shape_pos = aPad->ShapePos();
84 GBR_METADATA gbr_metadata;
85
86 bool plotOnCopperLayer = ( m_layerMask & LSET::AllCuMask() ).any();
87 bool plotOnExternalCopperLayer = ( m_layerMask & LSET::ExternalCuMask() ).any();
88
89 // Pad not on the solder mask layer cannot be soldered.
90 // therefore it can have a specific aperture attribute.
91 // Not yet in use.
92 // bool isPadOnBoardTechLayers = ( aPad->GetLayerSet() & LSET::AllBoardTechMask() ).any();
93
94 gbr_metadata.SetCmpReference( aPad->GetParent()->GetReference() );
95
96 if( plotOnCopperLayer )
97 {
98 gbr_metadata.SetNetAttribType( GBR_NETINFO_ALL );
99 gbr_metadata.SetCopper( true );
100
101 // Gives a default attribute, for instance for pads used as tracks in net ties:
102 // Connector pads and SMD pads are on external layers
103 // if on internal layers, they are certainly used as net tie
104 // and are similar to tracks: just conductor items
105 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
106
107 const bool useUTF8 = false;
108 const bool useQuoting = false;
109 gbr_metadata.SetPadName( aPad->GetNumber(), useUTF8, useQuoting );
110
111 if( !aPad->GetNumber().IsEmpty() )
112 gbr_metadata.SetPadPinFunction( aPad->GetPinFunction(), useUTF8, useQuoting );
113
114 gbr_metadata.SetNetName( aPad->GetNetname() );
115
116 // Some pads are mechanical pads ( through hole or smd )
117 // when this is the case, they have no pad name and/or are not plated.
118 // In this case gerber files have slightly different attributes.
119 if( aPad->GetAttribute() == PAD_ATTRIB::NPTH || aPad->GetNumber().IsEmpty() )
120 gbr_metadata.m_NetlistMetadata.m_NotInNet = true;
121
122 if( !plotOnExternalCopperLayer )
123 {
124 // the .P object attribute (GBR_NETLIST_METADATA::GBR_NETINFO_PAD)
125 // is used on outer layers, unless the component is embedded
126 // or a "etched" component (fp only drawn, not a physical component)
127 // Currently, Pcbnew does not handle embedded component, so we disable the .P
128 // attribute on internal layers
129 // Note the Gerber doc is not really clear about through holes pads about the .P
130 gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET |
131 GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
132
133 }
134
135 // Some attributes are reserved to the external copper layers:
136 // GBR_APERTURE_ATTRIB_CONNECTORPAD and GBR_APERTURE_ATTRIB_SMDPAD_CUDEF
137 // for instance.
138 // Pad with type PAD_ATTRIB::CONN or PAD_ATTRIB::SMD that is not on outer layer
139 // has its aperture attribute set to GBR_APERTURE_ATTRIB_CONDUCTOR
140 switch( aPad->GetAttribute() )
141 {
142 case PAD_ATTRIB::NPTH: // Mechanical pad through hole
143 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_WASHERPAD );
144 break;
145
146 case PAD_ATTRIB::PTH : // Pad through hole, a hole is also expected
147 gbr_metadata.SetApertureAttrib(
148 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_COMPONENTPAD );
149 break;
150
151 case PAD_ATTRIB::CONN: // Connector pads, no solder paste but with solder mask.
152 if( plotOnExternalCopperLayer )
153 gbr_metadata.SetApertureAttrib(
154 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONNECTORPAD );
155 break;
156
157 case PAD_ATTRIB::SMD: // SMD pads (on external copper layer only)
158 // with solder paste and mask
159 if( plotOnExternalCopperLayer )
160 gbr_metadata.SetApertureAttrib(
161 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_SMDPAD_CUDEF );
162 break;
163 }
164
165 // Fabrication properties can have specific GBR_APERTURE_METADATA options
166 // that replace previous aperture attribute:
167 switch( aPad->GetProperty() )
168 {
169 case PAD_PROP::BGA: // Only applicable to outer layers
170 if( plotOnExternalCopperLayer )
171 gbr_metadata.SetApertureAttrib(
172 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_BGAPAD_CUDEF );
173 break;
174
175 case PAD_PROP::FIDUCIAL_GLBL:
176 gbr_metadata.SetApertureAttrib(
177 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_FIDUCIAL_GLBL );
178 break;
179
180 case PAD_PROP::FIDUCIAL_LOCAL:
181 gbr_metadata.SetApertureAttrib(
182 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_FIDUCIAL_LOCAL );
183 break;
184
185 case PAD_PROP::TESTPOINT: // Only applicable to outer layers
186 if( plotOnExternalCopperLayer )
187 gbr_metadata.SetApertureAttrib(
188 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_TESTPOINT );
189 break;
190
191 case PAD_PROP::HEATSINK:
192 gbr_metadata.SetApertureAttrib(
193 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_HEATSINKPAD );
194 break;
195
196 case PAD_PROP::CASTELLATED:
197 gbr_metadata.SetApertureAttrib(
198 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CASTELLATEDPAD );
199 break;
200
201 case PAD_PROP::NONE:
202 break;
203 }
204
205 // Ensure NPTH pads have *always* the GBR_APERTURE_ATTRIB_WASHERPAD attribute
206 if( aPad->GetAttribute() == PAD_ATTRIB::NPTH )
207 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_WASHERPAD );
208 }
209 else
210 {
211 gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
212 }
213
214 // Set plot color (change WHITE to LIGHTGRAY because
215 // the white items are not seen on a white paper or screen
216 m_plotter->SetColor( aColor != WHITE ? aColor : LIGHTGRAY);
217
218 if( aPlotMode == SKETCH )
219 m_plotter->SetCurrentLineWidth( GetSketchPadLineWidth(), &gbr_metadata );
220
221 switch( aPad->GetShape() )
222 {
223 case PAD_SHAPE::CIRCLE:
224 m_plotter->FlashPadCircle( shape_pos, aPad->GetSize().x, aPlotMode, &gbr_metadata );
225 break;
226
227 case PAD_SHAPE::OVAL:
228 m_plotter->FlashPadOval( shape_pos, aPad->GetSize(), aPad->GetOrientation(), aPlotMode,
229 &gbr_metadata );
230 break;
231
232 case PAD_SHAPE::RECT:
233 m_plotter->FlashPadRect( shape_pos, aPad->GetSize(), aPad->GetOrientation(), aPlotMode,
234 &gbr_metadata );
235 break;
236
237 case PAD_SHAPE::ROUNDRECT:
238 m_plotter->FlashPadRoundRect( shape_pos, aPad->GetSize(), aPad->GetRoundRectCornerRadius(),
239 aPad->GetOrientation(), aPlotMode, &gbr_metadata );
240 break;
241
242 case PAD_SHAPE::TRAPEZOID:
243 {
244 // Build the pad polygon in coordinates relative to the pad
245 // (i.e. for a pad at pos 0,0, rot 0.0). Needed to use aperture macros,
246 // to be able to create a pattern common to all trapezoid pads having the same shape
247 wxPoint coord[4];
248
249 // Order is lower left, lower right, upper right, upper left.
250 wxSize half_size = aPad->GetSize()/2;
251 wxSize trap_delta = aPad->GetDelta()/2;
252
253 coord[0] = wxPoint( -half_size.x - trap_delta.y, half_size.y + trap_delta.x );
254 coord[1] = wxPoint( half_size.x + trap_delta.y, half_size.y - trap_delta.x );
255 coord[2] = wxPoint( half_size.x - trap_delta.y, -half_size.y + trap_delta.x );
256 coord[3] = wxPoint( -half_size.x + trap_delta.y, -half_size.y - trap_delta.x );
257
258 m_plotter->FlashPadTrapez( shape_pos, coord, aPad->GetOrientation(), aPlotMode,
259 &gbr_metadata );
260 }
261 break;
262
263 case PAD_SHAPE::CHAMFERED_RECT:
264 if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
265 {
266 static_cast<GERBER_PLOTTER*>( m_plotter )->FlashPadChamferRoundRect(
267 shape_pos, aPad->GetSize(),
268 aPad->GetRoundRectCornerRadius(),
269 aPad->GetChamferRectRatio(),
270 aPad->GetChamferPositions(),
271 aPad->GetOrientation(), aPlotMode, &gbr_metadata );
272 break;
273 }
274
275 KI_FALLTHROUGH;
276
277 default:
278 case PAD_SHAPE::CUSTOM:
279 {
280 const std::shared_ptr<SHAPE_POLY_SET>& polygons = aPad->GetEffectivePolygon();
281
282 if( polygons->OutlineCount() )
283 {
284 m_plotter->FlashPadCustom( shape_pos, aPad->GetSize(), aPad->GetOrientation(),
285 polygons.get(), aPlotMode, &gbr_metadata );
286 }
287 }
288 break;
289 }
290 }
291
292
PlotFootprintTextItems(const FOOTPRINT * aFootprint)293 void BRDITEMS_PLOTTER::PlotFootprintTextItems( const FOOTPRINT* aFootprint )
294 {
295 const FP_TEXT* textItem = &aFootprint->Reference();
296 LAYER_NUM textLayer = textItem->GetLayer();
297
298 // Reference and value are specific items, not in graphic items list
299 if( GetPlotReference() && m_layerMask[textLayer]
300 && ( textItem->IsVisible() || GetPlotInvisibleText() ) )
301 {
302 PlotFootprintTextItem( textItem, getColor( textLayer ) );
303 }
304
305 textItem = &aFootprint->Value();
306 textLayer = textItem->GetLayer();
307
308 if( GetPlotValue() && m_layerMask[textLayer]
309 && ( textItem->IsVisible() || GetPlotInvisibleText() ) )
310 {
311 PlotFootprintTextItem( textItem, getColor( textLayer ) );
312 }
313
314 for( const BOARD_ITEM* item : aFootprint->GraphicalItems() )
315 {
316 textItem = dyn_cast<const FP_TEXT*>( item );
317
318 if( !textItem )
319 continue;
320
321 if( !textItem->IsVisible() )
322 continue;
323
324 textLayer = textItem->GetLayer();
325
326 if( textLayer == Edge_Cuts || textLayer >= PCB_LAYER_ID_COUNT )
327 continue;
328
329 if( !m_layerMask[textLayer] )
330 continue;
331
332 if( textItem->GetText() == wxT( "${REFERENCE}" ) && !GetPlotReference() )
333 continue;
334
335 if( textItem->GetText() == wxT( "${VALUE}" ) && !GetPlotValue() )
336 continue;
337
338 PlotFootprintTextItem( textItem, getColor( textLayer ) );
339 }
340 }
341
342
PlotBoardGraphicItems()343 void BRDITEMS_PLOTTER::PlotBoardGraphicItems()
344 {
345 for( BOARD_ITEM* item : m_board->Drawings() )
346 {
347 switch( item->Type() )
348 {
349 case PCB_SHAPE_T:
350 PlotPcbShape( (PCB_SHAPE*) item );
351 break;
352
353 case PCB_TEXT_T:
354 PlotPcbText( (PCB_TEXT*) item );
355 break;
356
357 case PCB_DIM_ALIGNED_T:
358 case PCB_DIM_CENTER_T:
359 case PCB_DIM_ORTHOGONAL_T:
360 case PCB_DIM_LEADER_T:
361 PlotDimension( (PCB_DIMENSION_BASE*) item );
362 break;
363
364 case PCB_TARGET_T:
365 PlotPcbTarget( (PCB_TARGET*) item );
366 break;
367
368 default:
369 break;
370 }
371 }
372 }
373
374
PlotFootprintTextItem(const FP_TEXT * aTextMod,const COLOR4D & aColor)375 void BRDITEMS_PLOTTER::PlotFootprintTextItem( const FP_TEXT* aTextMod, const COLOR4D& aColor )
376 {
377 COLOR4D color = aColor;
378
379 if( aColor == COLOR4D::WHITE )
380 color = COLOR4D( LIGHTGRAY );
381
382 m_plotter->SetColor( color );
383
384 // calculate some text parameters :
385 wxSize size = aTextMod->GetTextSize();
386 wxPoint pos = aTextMod->GetTextPos();
387 double orient = aTextMod->GetDrawRotation();
388 int thickness = aTextMod->GetEffectiveTextPenWidth();
389
390 if( aTextMod->IsMirrored() )
391 size.x = -size.x; // Text is mirrored
392
393 // Non bold texts thickness is clamped at 1/6 char size by the low level draw function.
394 // but in Pcbnew we do not manage bold texts and thickness up to 1/4 char size
395 // (like bold text) and we manage the thickness.
396 // So we set bold flag to true
397 bool allow_bold = true;
398
399 GBR_METADATA gbr_metadata;
400
401 if( IsCopperLayer( aTextMod->GetLayer() ) )
402 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
403
404 gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
405 const FOOTPRINT* parent = static_cast<const FOOTPRINT*> ( aTextMod->GetParent() );
406 gbr_metadata.SetCmpReference( parent->GetReference() );
407
408 m_plotter->SetCurrentLineWidth( thickness );
409
410 m_plotter->Text( pos, aColor, aTextMod->GetShownText(), orient, size,
411 aTextMod->GetHorizJustify(), aTextMod->GetVertJustify(), thickness,
412 aTextMod->IsItalic(), allow_bold, false, &gbr_metadata );
413 }
414
415
PlotDimension(const PCB_DIMENSION_BASE * aDim)416 void BRDITEMS_PLOTTER::PlotDimension( const PCB_DIMENSION_BASE* aDim )
417 {
418 if( !m_layerMask[aDim->GetLayer()] )
419 return;
420
421 PCB_SHAPE draw;
422
423 draw.SetWidth( aDim->GetLineThickness() );
424 draw.SetLayer( aDim->GetLayer() );
425
426 COLOR4D color = ColorSettings()->GetColor( aDim->GetLayer() );
427
428 // Set plot color (change WHITE to LIGHTGRAY because
429 // the white items are not seen on a white paper or screen
430 m_plotter->SetColor( color != WHITE ? color : LIGHTGRAY);
431
432 PlotPcbText( &aDim->Text() );
433
434 for( const std::shared_ptr<SHAPE>& shape : aDim->GetShapes() )
435 {
436 switch( shape->Type() )
437 {
438 case SH_SEGMENT:
439 {
440 const SEG& seg = static_cast<const SHAPE_SEGMENT*>( shape.get() )->GetSeg();
441
442 draw.SetShape( SHAPE_T::SEGMENT );
443 draw.SetStart( wxPoint( seg.A ) );
444 draw.SetEnd( wxPoint( seg.B ) );
445
446 PlotPcbShape( &draw );
447 break;
448 }
449
450 case SH_CIRCLE:
451 {
452 wxPoint start( shape->Centre() );
453 int radius = static_cast<const SHAPE_CIRCLE*>( shape.get() )->GetRadius();
454
455 draw.SetShape( SHAPE_T::CIRCLE );
456 draw.SetFilled( false );
457 draw.SetStart( start );
458 draw.SetEnd( wxPoint( start.x + radius, start.y ) );
459
460 PlotPcbShape( &draw );
461 break;
462 }
463
464 default:
465 break;
466 }
467 }
468 }
469
470
PlotPcbTarget(const PCB_TARGET * aMire)471 void BRDITEMS_PLOTTER::PlotPcbTarget( const PCB_TARGET* aMire )
472 {
473 int dx1, dx2, dy1, dy2, radius;
474
475 if( !m_layerMask[aMire->GetLayer()] )
476 return;
477
478 m_plotter->SetColor( getColor( aMire->GetLayer() ) );
479
480 PCB_SHAPE draw;
481
482 draw.SetShape( SHAPE_T::CIRCLE );
483 draw.SetFilled( false );
484 draw.SetWidth( aMire->GetWidth() );
485 draw.SetLayer( aMire->GetLayer() );
486 draw.SetStart( aMire->GetPosition() );
487 radius = aMire->GetSize() / 3;
488
489 if( aMire->GetShape() ) // shape X
490 radius = aMire->GetSize() / 2;
491
492 // Draw the circle
493 draw.SetEnd( wxPoint( draw.GetStart().x + radius, draw.GetStart().y ) );
494
495 PlotPcbShape( &draw );
496
497 draw.SetShape( SHAPE_T::SEGMENT );
498
499 radius = aMire->GetSize() / 2;
500 dx1 = radius;
501 dy1 = 0;
502 dx2 = 0;
503 dy2 = radius;
504
505 if( aMire->GetShape() ) // Shape X
506 {
507 dx1 = dy1 = radius;
508 dx2 = dx1;
509 dy2 = -dy1;
510 }
511
512 wxPoint mirePos( aMire->GetPosition() );
513
514 // Draw the X or + shape:
515 draw.SetStart( wxPoint( mirePos.x - dx1, mirePos.y - dy1 ) );
516 draw.SetEnd( wxPoint( mirePos.x + dx1, mirePos.y + dy1 ) );
517 PlotPcbShape( &draw );
518
519 draw.SetStart( wxPoint( mirePos.x - dx2, mirePos.y - dy2 ) );
520 draw.SetEnd( wxPoint( mirePos.x + dx2, mirePos.y + dy2 ) );
521 PlotPcbShape( &draw );
522 }
523
524
PlotFootprintGraphicItems(const FOOTPRINT * aFootprint)525 void BRDITEMS_PLOTTER::PlotFootprintGraphicItems( const FOOTPRINT* aFootprint )
526 {
527 for( const BOARD_ITEM* item : aFootprint->GraphicalItems() )
528 {
529 const FP_SHAPE* shape = dynamic_cast<const FP_SHAPE*>( item );
530
531 if( shape && m_layerMask[ shape->GetLayer() ] )
532 PlotFootprintGraphicItem( shape );
533 }
534 }
535
536
PlotFootprintGraphicItem(const FP_SHAPE * aShape)537 void BRDITEMS_PLOTTER::PlotFootprintGraphicItem( const FP_SHAPE* aShape )
538 {
539 if( aShape->Type() != PCB_FP_SHAPE_T )
540 return;
541
542 m_plotter->SetColor( getColor( aShape->GetLayer() ) );
543
544 bool sketch = GetPlotMode() == SKETCH;
545 int thickness = aShape->GetWidth();
546
547 GBR_METADATA gbr_metadata;
548 gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_CMP );
549 const FOOTPRINT* parent = static_cast<const FOOTPRINT*> ( aShape->GetParent() );
550 gbr_metadata.SetCmpReference( parent->GetReference() );
551
552 bool isOnCopperLayer = ( m_layerMask & LSET::AllCuMask() ).any();
553
554 if( aShape->GetLayer() == Edge_Cuts ) // happens also when plotting copper layers
555 {
556 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_EDGECUT );
557 }
558 else if( isOnCopperLayer ) // only for items not on Edge_Cuts.
559 {
560 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_ETCHEDCMP );
561 gbr_metadata.SetCopper( true );
562 }
563
564 int radius; // Circle/arc radius.
565
566 switch( aShape->GetShape() )
567 {
568 case SHAPE_T::SEGMENT:
569 m_plotter->ThickSegment( aShape->GetStart(), aShape->GetEnd(), thickness, GetPlotMode(),
570 &gbr_metadata );
571 break;
572
573 case SHAPE_T::RECT:
574 {
575 std::vector<wxPoint> pts = aShape->GetRectCorners();
576
577 if( sketch || thickness > 0 )
578 {
579 m_plotter->ThickSegment( pts[0], pts[1], thickness, GetPlotMode(), &gbr_metadata );
580 m_plotter->ThickSegment( pts[1], pts[2], thickness, GetPlotMode(), &gbr_metadata );
581 m_plotter->ThickSegment( pts[2], pts[3], thickness, GetPlotMode(), &gbr_metadata );
582 m_plotter->ThickSegment( pts[3], pts[0], thickness, GetPlotMode(), &gbr_metadata );
583 }
584
585 if( !sketch && aShape->IsFilled() )
586 {
587 SHAPE_LINE_CHAIN poly;
588
589 for( const wxPoint& pt : pts )
590 poly.Append( pt );
591
592 m_plotter->PlotPoly( poly, FILL_T::FILLED_SHAPE, -1, &gbr_metadata );
593 }
594 }
595 break;
596
597 case SHAPE_T::CIRCLE:
598 radius = KiROUND( GetLineLength( aShape->GetStart(), aShape->GetEnd() ) );
599
600 if( aShape->IsFilled() )
601 {
602 m_plotter->FilledCircle( aShape->GetStart(), radius * 2 + thickness, GetPlotMode(),
603 &gbr_metadata );
604 }
605 else
606 {
607 m_plotter->ThickCircle( aShape->GetStart(), radius * 2, thickness, GetPlotMode(),
608 &gbr_metadata );
609 }
610
611 break;
612
613 case SHAPE_T::ARC:
614 {
615 radius = KiROUND( GetLineLength( aShape->GetCenter(), aShape->GetStart() ) );
616 double startAngle = ArcTangente( aShape->GetStart().y - aShape->GetCenter().y,
617 aShape->GetStart().x - aShape->GetCenter().x );
618 double endAngle = startAngle + aShape->GetArcAngle();
619
620 // when startAngle == endAngle ThickArc() doesn't know whether it's 0 deg and 360 deg
621 if( std::abs( aShape->GetArcAngle() ) == 3600.0 )
622 {
623 m_plotter->ThickCircle( aShape->GetCenter(), radius * 2, thickness, GetPlotMode(),
624 &gbr_metadata );
625 }
626 else
627 {
628 m_plotter->ThickArc( aShape->GetCenter(), -endAngle, -startAngle, radius, thickness,
629 GetPlotMode(), &gbr_metadata );
630 }
631 }
632 break;
633
634 case SHAPE_T::POLY:
635 if( aShape->IsPolyShapeValid() )
636 {
637 std::vector<wxPoint> cornerList;
638 aShape->DupPolyPointsList( cornerList );
639
640 // We must compute board coordinates from m_PolyList which are relative to the parent
641 // position at orientation 0
642 const FOOTPRINT *parentFootprint = aShape->GetParentFootprint();
643
644 if( parentFootprint )
645 {
646 for( unsigned ii = 0; ii < cornerList.size(); ++ii )
647 {
648 wxPoint* corner = &cornerList[ii];
649 RotatePoint( corner, parentFootprint->GetOrientation() );
650 *corner += parentFootprint->GetPosition();
651 }
652 }
653
654 if( sketch || thickness > 0 )
655 {
656 for( size_t i = 1; i < cornerList.size(); i++ )
657 {
658 m_plotter->ThickSegment( cornerList[i - 1], cornerList[i], thickness,
659 GetPlotMode(), &gbr_metadata );
660 }
661
662 m_plotter->ThickSegment( cornerList.back(), cornerList.front(), thickness,
663 GetPlotMode(), &gbr_metadata );
664
665 }
666
667 if( !sketch && aShape->IsFilled() )
668 {
669 // This must be simplified and fractured to prevent overlapping polygons
670 // from generating invalid Gerber files
671
672 SHAPE_LINE_CHAIN line( cornerList );
673 SHAPE_POLY_SET tmpPoly;
674
675 line.SetClosed( true );
676 tmpPoly.AddOutline( line );
677 tmpPoly.Fracture( SHAPE_POLY_SET::PM_FAST );
678
679 for( int jj = 0; jj < tmpPoly.OutlineCount(); ++jj )
680 {
681 SHAPE_LINE_CHAIN &poly = tmpPoly.Outline( jj );
682 m_plotter->PlotPoly( poly, FILL_T::FILLED_SHAPE, thickness, &gbr_metadata );
683 }
684 }
685 }
686
687 break;
688
689 case SHAPE_T::BEZIER:
690 m_plotter->BezierCurve( aShape->GetStart(), aShape->GetBezierC1(),
691 aShape->GetBezierC2(), aShape->GetEnd(), 0, thickness );
692 break;
693
694 default:
695 wxASSERT_MSG( false, "Unhandled FP_SHAPE shape" );
696 break;
697 }
698 }
699
700
PlotPcbText(const PCB_TEXT * aText)701 void BRDITEMS_PLOTTER::PlotPcbText( const PCB_TEXT* aText )
702 {
703 wxString shownText( aText->GetShownText() );
704
705 if( shownText.IsEmpty() )
706 return;
707
708 if( !m_layerMask[aText->GetLayer()] )
709 return;
710
711 GBR_METADATA gbr_metadata;
712
713 if( IsCopperLayer( aText->GetLayer() ) )
714 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
715
716 COLOR4D color = getColor( aText->GetLayer() );
717 m_plotter->SetColor( color );
718
719 wxSize size = aText->GetTextSize();
720 wxPoint pos = aText->GetTextPos();
721 double orient = aText->GetTextAngle();
722 int thickness = aText->GetEffectiveTextPenWidth();
723
724 if( aText->IsMirrored() )
725 size.x = -size.x;
726
727 // Non bold texts thickness is clamped at 1/6 char size by the low level draw function.
728 // but in Pcbnew we do not manage bold texts and thickness up to 1/4 char size
729 // (like bold text) and we manage the thickness.
730 // So we set bold flag to true
731 bool allow_bold = true;
732
733 m_plotter->SetCurrentLineWidth( thickness );
734
735 if( aText->IsMultilineAllowed() )
736 {
737 std::vector<wxPoint> positions;
738 wxArrayString strings_list;
739 wxStringSplit( shownText, strings_list, '\n' );
740 positions.reserve( strings_list.Count() );
741
742 aText->GetLinePositions( positions, strings_list.Count() );
743
744 for( unsigned ii = 0; ii < strings_list.Count(); ii++ )
745 {
746 wxString& txt = strings_list.Item( ii );
747 m_plotter->Text( positions[ii], color, txt, orient, size, aText->GetHorizJustify(),
748 aText->GetVertJustify(), thickness, aText->IsItalic(),
749 allow_bold, false, &gbr_metadata );
750 }
751 }
752 else
753 {
754 m_plotter->Text( pos, color, shownText, orient, size, aText->GetHorizJustify(),
755 aText->GetVertJustify(), thickness, aText->IsItalic(), allow_bold,
756 false, &gbr_metadata );
757 }
758 }
759
760
PlotFilledAreas(const ZONE * aZone,const SHAPE_POLY_SET & polysList)761 void BRDITEMS_PLOTTER::PlotFilledAreas( const ZONE* aZone, const SHAPE_POLY_SET& polysList )
762 {
763 if( polysList.IsEmpty() )
764 return;
765
766 GBR_METADATA gbr_metadata;
767
768 bool isOnCopperLayer = aZone->IsOnCopperLayer();
769
770 if( isOnCopperLayer )
771 {
772 gbr_metadata.SetNetName( aZone->GetNetname() );
773 gbr_metadata.SetCopper( true );
774
775 // Zones with no net name can exist.
776 // they are not used to connect items, so the aperture attribute cannot
777 // be set as conductor
778 if( aZone->GetNetname().IsEmpty() )
779 {
780 gbr_metadata.SetApertureAttrib(
781 GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
782 }
783 else
784 {
785 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_CONDUCTOR );
786 gbr_metadata.SetNetAttribType( GBR_NETLIST_METADATA::GBR_NETINFO_NET );
787 }
788 }
789
790 m_plotter->SetColor( getColor( aZone->GetLayer() ) );
791
792 m_plotter->StartBlock( nullptr ); // Clean current object attributes
793
794 /* Plot all filled areas: filled areas have a filled area and a thick
795 * outline (depending on the fill area option we must plot the filled area itself
796 * and plot the thick outline itself, if the thickness has meaning (at least is > 1)
797 *
798 * in non filled mode the outline is plotted, but not the filling items
799 */
800 int outline_thickness = aZone->GetFilledPolysUseThickness() ? aZone->GetMinThickness() : 0;
801
802 for( int idx = 0; idx < polysList.OutlineCount(); ++idx )
803 {
804 const SHAPE_LINE_CHAIN& outline = polysList.Outline( idx );
805
806 // Plot the current filled area (as region for Gerber plotter
807 // to manage attributes) and its outline for thick outline
808 if( GetPlotMode() == FILLED )
809 {
810 if( m_plotter->GetPlotterType() == PLOT_FORMAT::GERBER )
811 {
812 if( outline_thickness > 0 )
813 {
814 m_plotter->PlotPoly( outline, FILL_T::NO_FILL, outline_thickness,
815 &gbr_metadata );
816
817 // Ensure the outline is closed:
818 int last_idx = outline.PointCount() - 1;
819
820 if( outline.CPoint( 0 ) != outline.CPoint( last_idx ) )
821 {
822 m_plotter->ThickSegment( wxPoint( outline.CPoint( 0 ) ),
823 wxPoint( outline.CPoint( last_idx ) ),
824 outline_thickness, GetPlotMode(), &gbr_metadata );
825 }
826 }
827
828 static_cast<GERBER_PLOTTER*>( m_plotter )->PlotGerberRegion( outline,
829 &gbr_metadata );
830 }
831 else
832 {
833 m_plotter->PlotPoly( outline, FILL_T::FILLED_SHAPE, outline_thickness,
834 &gbr_metadata );
835 }
836 }
837 else
838 {
839 if( outline_thickness )
840 {
841 int last_idx = outline.PointCount() - 1;
842
843 for( int jj = 1; jj <= last_idx; jj++ )
844 {
845 m_plotter->ThickSegment( wxPoint( outline.CPoint( jj - 1) ),
846 wxPoint( outline.CPoint( jj ) ),
847 outline_thickness,
848 GetPlotMode(), &gbr_metadata );
849 }
850
851 // Ensure the outline is closed:
852 if( outline.CPoint( 0 ) != outline.CPoint( last_idx ) )
853 {
854 m_plotter->ThickSegment( wxPoint( outline.CPoint( 0 ) ),
855 wxPoint( outline.CPoint( last_idx ) ),
856 outline_thickness,
857 GetPlotMode(), &gbr_metadata );
858 }
859 }
860
861 m_plotter->SetCurrentLineWidth( -1 );
862 }
863 }
864
865 m_plotter->EndBlock( nullptr ); // Clear object attributes
866 }
867
868
PlotPcbShape(const PCB_SHAPE * aShape)869 void BRDITEMS_PLOTTER::PlotPcbShape( const PCB_SHAPE* aShape )
870 {
871 if( !m_layerMask[aShape->GetLayer()] )
872 return;
873
874 bool sketch = GetPlotMode() == SKETCH;
875 int thickness = aShape->GetWidth();
876
877 m_plotter->SetColor( getColor( aShape->GetLayer() ) );
878
879 GBR_METADATA gbr_metadata;
880
881 if( aShape->GetLayer() == Edge_Cuts )
882 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_EDGECUT );
883
884 if( IsCopperLayer( aShape->GetLayer() ) )
885 // Graphic items (PCB_SHAPE, TEXT) having no net have the NonConductor attribute
886 // Graphic items having a net have the Conductor attribute, but are not (yet?)
887 // supported in Pcbnew
888 gbr_metadata.SetApertureAttrib( GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB_NONCONDUCTOR );
889
890 switch( aShape->GetShape() )
891 {
892 case SHAPE_T::SEGMENT:
893 m_plotter->ThickSegment( aShape->GetStart(), aShape->GetEnd(), thickness, GetPlotMode(),
894 &gbr_metadata );
895 break;
896
897 case SHAPE_T::CIRCLE:
898 if( aShape->IsFilled() )
899 {
900 m_plotter->FilledCircle( aShape->GetStart(), aShape->GetRadius() * 2 + thickness,
901 GetPlotMode(), &gbr_metadata );
902 }
903 else
904 {
905 m_plotter->ThickCircle( aShape->GetStart(), aShape->GetRadius() * 2, thickness,
906 GetPlotMode(), &gbr_metadata );
907 }
908
909 break;
910
911 case SHAPE_T::ARC:
912 {
913 double startAngle = ArcTangente( aShape->GetStart().y - aShape->GetCenter().y,
914 aShape->GetStart().x - aShape->GetCenter().x );
915 double endAngle = startAngle + aShape->GetArcAngle();
916
917 // when startAngle == endAngle ThickArc() doesn't know whether it's 0 deg and 360 deg
918 if( std::abs( aShape->GetArcAngle() ) == 3600.0 )
919 {
920 m_plotter->ThickCircle( aShape->GetCenter(), aShape->GetRadius() * 2, thickness,
921 GetPlotMode(), &gbr_metadata );
922 }
923 else
924 {
925 m_plotter->ThickArc( aShape->GetCenter(), -endAngle, -startAngle, aShape->GetRadius(),
926 thickness, GetPlotMode(), &gbr_metadata );
927 }
928 }
929 break;
930
931 case SHAPE_T::BEZIER:
932 m_plotter->BezierCurve( aShape->GetStart(), aShape->GetBezierC1(),
933 aShape->GetBezierC2(), aShape->GetEnd(), 0, thickness );
934 break;
935
936 case SHAPE_T::POLY:
937 if( aShape->IsPolyShapeValid() )
938 {
939 if( sketch || thickness > 0 )
940 {
941 for( auto it = aShape->GetPolyShape().CIterateSegments( 0 ); it; it++ )
942 {
943 auto seg = it.Get();
944 m_plotter->ThickSegment( wxPoint( seg.A ), wxPoint( seg.B ),
945 thickness, GetPlotMode(), &gbr_metadata );
946 }
947 }
948
949 if( !sketch && aShape->IsFilled() )
950 {
951 m_plotter->SetCurrentLineWidth( thickness, &gbr_metadata );
952
953 // Draw the polygon: only one polygon is expected
954 // However we provide a multi polygon shape drawing
955 // ( for the future or to show a non expected shape )
956 // This must be simplified and fractured to prevent overlapping polygons
957 // from generating invalid Gerber files
958 auto tmpPoly = SHAPE_POLY_SET( aShape->GetPolyShape() );
959 tmpPoly.Fracture( SHAPE_POLY_SET::PM_FAST );
960
961 for( int jj = 0; jj < tmpPoly.OutlineCount(); ++jj )
962 {
963 SHAPE_LINE_CHAIN& poly = tmpPoly.Outline( jj );
964 m_plotter->PlotPoly( poly, FILL_T::FILLED_SHAPE, thickness, &gbr_metadata );
965 }
966 }
967 }
968 break;
969
970 case SHAPE_T::RECT:
971 {
972 std::vector<wxPoint> pts = aShape->GetRectCorners();
973
974 if( sketch || thickness > 0 )
975 {
976 m_plotter->ThickSegment( pts[0], pts[1], thickness, GetPlotMode(), &gbr_metadata );
977 m_plotter->ThickSegment( pts[1], pts[2], thickness, GetPlotMode(), &gbr_metadata );
978 m_plotter->ThickSegment( pts[2], pts[3], thickness, GetPlotMode(), &gbr_metadata );
979 m_plotter->ThickSegment( pts[3], pts[0], thickness, GetPlotMode(), &gbr_metadata );
980 }
981
982 if( !sketch && aShape->IsFilled() )
983 {
984 SHAPE_LINE_CHAIN poly;
985
986 for( const wxPoint& pt : pts )
987 poly.Append( pt );
988
989 m_plotter->PlotPoly( poly, FILL_T::FILLED_SHAPE, -1, &gbr_metadata );
990 }
991
992 break;
993 }
994
995 default:
996 UNIMPLEMENTED_FOR( aShape->SHAPE_T_asString() );
997 }
998 }
999
1000
plotOneDrillMark(PAD_DRILL_SHAPE_T aDrillShape,const wxPoint & aDrillPos,const wxSize & aDrillSize,const wxSize & aPadSize,double aOrientation,int aSmallDrill)1001 void BRDITEMS_PLOTTER::plotOneDrillMark( PAD_DRILL_SHAPE_T aDrillShape, const wxPoint& aDrillPos,
1002 const wxSize& aDrillSize, const wxSize& aPadSize,
1003 double aOrientation, int aSmallDrill )
1004 {
1005 wxSize drillSize = aDrillSize;
1006
1007 // Small drill marks have no significance when applied to slots
1008 if( aSmallDrill && aDrillShape == PAD_DRILL_SHAPE_CIRCLE )
1009 drillSize.x = std::min( aSmallDrill, drillSize.x );
1010
1011 // Round holes only have x diameter, slots have both
1012 drillSize.x -= getFineWidthAdj();
1013 drillSize.x = Clamp( 1, drillSize.x, aPadSize.x - 1 );
1014
1015 if( aDrillShape == PAD_DRILL_SHAPE_OBLONG )
1016 {
1017 drillSize.y -= getFineWidthAdj();
1018 drillSize.y = Clamp( 1, drillSize.y, aPadSize.y - 1 );
1019 m_plotter->FlashPadOval( aDrillPos, drillSize, aOrientation, GetPlotMode(), nullptr );
1020 }
1021 else
1022 {
1023 m_plotter->FlashPadCircle( aDrillPos, drillSize.x, GetPlotMode(), nullptr );
1024 }
1025 }
1026
1027
PlotDrillMarks()1028 void BRDITEMS_PLOTTER::PlotDrillMarks()
1029 {
1030 /* If small drills marks were requested prepare a clamp value to pass
1031 to the helper function */
1032 int smallDrill = GetDrillMarksType() == PCB_PLOT_PARAMS::SMALL_DRILL_SHAPE
1033 ? Millimeter2iu( ADVANCED_CFG::GetCfg().m_SmallDrillMarkSize ) : 0;
1034
1035 /* In the filled trace mode drill marks are drawn white-on-black to scrape
1036 the underlying pad. This works only for drivers supporting color change,
1037 obviously... it means that:
1038 - PS, SVG and PDF output is correct (i.e. you have a 'donut' pad)
1039 - In HPGL you can't see them
1040 - In gerbers you can't see them, too. This is arguably the right thing to
1041 do since having drill marks and high speed drill stations is a sure
1042 recipe for broken tools and angry manufacturers. If you *really* want them
1043 you could start a layer with negative polarity to scrape the film.
1044 - In DXF they go into the 'WHITE' layer. This could be useful.
1045 */
1046 if( GetPlotMode() == FILLED )
1047 m_plotter->SetColor( WHITE );
1048
1049 for( PCB_TRACK* tracks : m_board->Tracks() )
1050 {
1051 const PCB_VIA* via = dyn_cast<const PCB_VIA*>( tracks );
1052
1053 if( via )
1054 {
1055 plotOneDrillMark( PAD_DRILL_SHAPE_CIRCLE, via->GetStart(),
1056 wxSize( via->GetDrillValue(), 0 ),
1057 wxSize( via->GetWidth(), 0 ), 0, smallDrill );
1058 }
1059 }
1060
1061 for( FOOTPRINT* footprint : m_board->Footprints() )
1062 {
1063 for( PAD* pad : footprint->Pads() )
1064 {
1065 if( pad->GetDrillSize().x == 0 )
1066 continue;
1067
1068 plotOneDrillMark( pad->GetDrillShape(), pad->GetPosition(), pad->GetDrillSize(),
1069 pad->GetSize(), pad->GetOrientation(), smallDrill );
1070 }
1071 }
1072
1073 if( GetPlotMode() == FILLED )
1074 m_plotter->SetColor( BLACK );
1075 }
1076