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