1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 /**
22  * @file GERBER_plotter.cpp
23  * @brief specialized plotter for GERBER files format
24  */
25 
26 #include <string_utils.h>
27 #include <convert_basic_shapes_to_polygon.h>
28 #include <macros.h>
29 #include <math/util.h>      // for KiROUND
30 #include <trigo.h>
31 #include <wx/log.h>
32 
33 #include <build_version.h>
34 
35 #include <plotters/plotter_gerber.h>
36 #include <plotters/gbr_plotter_aperture_macros.h>
37 
38 #include <gbr_metadata.h>
39 
40 
41 // if GBR_USE_MACROS is defined, pads having a shape that is not a Gerber primitive
42 // will use a macro when possible
43 // Old code will be removed only after many tests
44 //
45 // Note also: setting m_gerberDisableApertMacros to true disable all aperture macros
46 // in Gerber files
47 //
48 #define GBR_USE_MACROS_FOR_CHAMFERED_ROUND_RECT
49 #define GBR_USE_MACROS_FOR_CHAMFERED_RECT
50 #define GBR_USE_MACROS_FOR_ROUNDRECT
51 #define GBR_USE_MACROS_FOR_TRAPEZOID
52 #define GBR_USE_MACROS_FOR_ROTATED_OVAL
53 #define GBR_USE_MACROS_FOR_ROTATED_RECT
54 #define GBR_USE_MACROS_FOR_CUSTOM_PAD
55 
56 // max count of corners to create a aperture macro for a custom shape.
57 // provided just in case a aperture macro type free polygon creates issues
58 // when the number of corners is too high.
59 // (1 corner = up to 24 chars)
60 // Gerber doc say max corners 5000. We use a slightly smaller value.
61 // if a custom shape needs more than GBR_MACRO_FOR_CUSTOM_PAD_MAX_CORNER_COUNT, it
62 // will be plot using a region.
63 #define GBR_MACRO_FOR_CUSTOM_PAD_MAX_CORNER_COUNT 4990
64 #define AM_FREEPOLY_BASENAME "FreePoly"
65 
66 
67 // A helper function to compare 2 polygons: polygons are similar if they have the same
68 // number of vertices and each vertex coordinate are similar, i.e. if the difference
69 // between coordinates is small ( <= margin to accept rounding issues coming from polygon
70 // geometric transforms like rotation
polyCompare(const std::vector<wxPoint> & aPolygon,const std::vector<wxPoint> & aTestPolygon)71 static bool polyCompare( const std::vector<wxPoint>& aPolygon,
72                          const std::vector<wxPoint>& aTestPolygon )
73 {
74     // fast test: polygon sizes must be the same:
75     if( aTestPolygon.size() != aPolygon.size() )
76         return false;
77 
78     const int margin = 2;
79 
80     for( size_t jj = 0; jj < aPolygon.size(); jj++ )
81     {
82         if( std::abs( aPolygon[jj].x - aTestPolygon[jj].x ) > margin ||
83             std::abs( aPolygon[jj].y - aTestPolygon[jj].y ) > margin )
84             return false;
85     }
86 
87     return true;
88 }
89 
90 
GERBER_PLOTTER()91 GERBER_PLOTTER::GERBER_PLOTTER()
92 {
93     workFile  = nullptr;
94     finalFile = nullptr;
95     m_currentApertureIdx = -1;
96     m_apertureAttribute = 0;
97 
98     // number of digits after the point (number of digits of the mantissa
99     // Be careful: the Gerber coordinates are stored in an integer
100     // so 6 digits (inches) or 5 digits (mm) is a good value
101     // To avoid overflow, 7 digits (inches) or 6 digits is a max.
102     // with lower values than 6 digits (inches) or 5 digits (mm),
103     // Creating self-intersecting polygons from non-intersecting polygons
104     // happen easily.
105     m_gerberUnitInch = false;
106     m_gerberUnitFmt = 6;
107     m_useX2format = true;
108     m_useNetAttributes = true;
109     m_gerberDisableApertMacros = false;
110 
111     m_hasApertureRoundRect = false;     // true is at least one round rect aperture is in use
112     m_hasApertureRotOval = false;       // true is at least one oval rotated aperture is in use
113     m_hasApertureRotRect = false;       // true is at least one rect. rotated aperture is in use
114     m_hasApertureOutline4P = false;       // true is at least one rotated rect or trapezoid pad
115                                         // aperture is in use
116     m_hasApertureChamferedRect = false; // true is at least one chamfered rect
117                                         // (no rounded corner) is in use
118 }
119 
120 
SetViewport(const wxPoint & aOffset,double aIusPerDecimil,double aScale,bool aMirror)121 void GERBER_PLOTTER::SetViewport( const wxPoint& aOffset, double aIusPerDecimil,
122                                   double aScale, bool aMirror )
123 {
124     wxASSERT( aMirror == false );
125     m_plotMirror = false;
126     m_plotOffset = aOffset;
127     wxASSERT( aScale == 1 );              // aScale parameter is not used in Gerber
128     m_plotScale = 1;                      // Plot scale is *always* 1.0
129 
130     m_IUsPerDecimil = aIusPerDecimil;
131 
132     // gives now a default value to iuPerDeviceUnit (because the units of the caller is now known)
133     // which could be modified later by calling SetGerberCoordinatesFormat()
134     m_iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 );
135 
136     // We don't handle the filmbox, and it's more useful to keep the
137     // origin at the origin
138     m_paperSize.x = 0;
139     m_paperSize.y = 0;
140 }
141 
142 
SetGerberCoordinatesFormat(int aResolution,bool aUseInches)143 void GERBER_PLOTTER::SetGerberCoordinatesFormat( int aResolution, bool aUseInches )
144 {
145     m_gerberUnitInch = aUseInches;
146     m_gerberUnitFmt = aResolution;
147 
148     m_iuPerDeviceUnit = pow( 10.0, m_gerberUnitFmt ) / ( m_IUsPerDecimil * 10000.0 );
149 
150     if( ! m_gerberUnitInch )
151         m_iuPerDeviceUnit *= 25.4;     // gerber output in mm
152 }
153 
154 
emitDcode(const DPOINT & pt,int dcode)155 void GERBER_PLOTTER::emitDcode( const DPOINT& pt, int dcode )
156 {
157 
158     fprintf( m_outputFile, "X%dY%dD%02d*\n", KiROUND( pt.x ), KiROUND( pt.y ), dcode );
159 }
160 
ClearAllAttributes()161 void GERBER_PLOTTER::ClearAllAttributes()
162 {
163     // Remove all attributes from object attributes dictionary (TO. and TA commands)
164     if( m_useX2format )
165         fputs( "%TD*%\n", m_outputFile );
166     else
167         fputs( "G04 #@! TD*\n", m_outputFile );
168 
169     m_objectAttributesDictionary.clear();
170 }
171 
172 
clearNetAttribute()173 void GERBER_PLOTTER::clearNetAttribute()
174 {
175     // disable a Gerber net attribute (exists only in X2 with net attributes mode).
176     if( m_objectAttributesDictionary.empty() )     // No net attribute or not X2 mode
177         return;
178 
179     // Remove all net attributes from object attributes dictionary
180     if( m_useX2format )
181         fputs( "%TD*%\n", m_outputFile );
182     else
183         fputs( "G04 #@! TD*\n", m_outputFile );
184 
185     m_objectAttributesDictionary.clear();
186 }
187 
188 
StartBlock(void * aData)189 void GERBER_PLOTTER::StartBlock( void* aData )
190 {
191     // Currently, it is the same as EndBlock(): clear all aperture net attributes
192     EndBlock( aData );
193 }
194 
195 
EndBlock(void * aData)196 void GERBER_PLOTTER::EndBlock( void* aData )
197 {
198     // Remove all net attributes from object attributes dictionary
199     clearNetAttribute();
200 }
201 
202 
formatNetAttribute(GBR_NETLIST_METADATA * aData)203 void GERBER_PLOTTER::formatNetAttribute( GBR_NETLIST_METADATA* aData )
204 {
205     // print a Gerber net attribute record.
206     // it is added to the object attributes dictionary
207     // On file, only modified or new attributes are printed.
208     if( aData == nullptr )
209         return;
210 
211     if( !m_useNetAttributes )
212         return;
213 
214     bool useX1StructuredComment = !m_useX2format;
215 
216     bool clearDict;
217     std::string short_attribute_string;
218 
219     if( !FormatNetAttribute( short_attribute_string, m_objectAttributesDictionary,
220                         aData, clearDict, useX1StructuredComment ) )
221         return;
222 
223     if( clearDict )
224         clearNetAttribute();
225 
226     if( !short_attribute_string.empty() )
227         fputs( short_attribute_string.c_str(), m_outputFile );
228 
229     if( m_useX2format && !aData->m_ExtraData.IsEmpty() )
230     {
231         std::string extra_data = TO_UTF8( aData->m_ExtraData );
232         fputs( extra_data.c_str(), m_outputFile );
233     }
234 }
235 
236 
StartPlot()237 bool GERBER_PLOTTER::StartPlot()
238 {
239     m_hasApertureRoundRect = false;     // true is at least one round rect aperture is in use
240     m_hasApertureRotOval = false;       // true is at least one oval rotated aperture is in use
241     m_hasApertureRotRect = false;       // true is at least one rect. rotated aperture is in use
242     m_hasApertureOutline4P = false;     // true is at least one rotated rect/trapezoid aperture
243                                         // is in use
244     m_hasApertureChamferedRect = false; // true is at least one chamfered rect is in use
245     m_am_freepoly_list.ClearList();
246 
247     wxASSERT( m_outputFile );
248 
249     finalFile = m_outputFile;     // the actual gerber file will be created later
250 
251     // Create a temp file in system temp to avoid potential network share buffer issues for
252     // the final read and save.
253     m_workFilename = wxFileName::CreateTempFileName( "" );
254     workFile   = wxFopen( m_workFilename, wxT( "wt" ));
255     m_outputFile = workFile;
256     wxASSERT( m_outputFile );
257 
258     if( m_outputFile == nullptr )
259         return false;
260 
261     for( unsigned ii = 0; ii < m_headerExtraLines.GetCount(); ii++ )
262     {
263         if( ! m_headerExtraLines[ii].IsEmpty() )
264             fprintf( m_outputFile, "%s\n", TO_UTF8( m_headerExtraLines[ii] ) );
265     }
266 
267     // Set coordinate format to 3.6 or 4.5 absolute, leading zero omitted
268     // the number of digits for the integer part of coordinates is needed
269     // in gerber format, but is not very important when omitting leading zeros
270     // It is fixed here to 3 (inch) or 4 (mm), but is not actually used
271     int leadingDigitCount = m_gerberUnitInch ? 3 : 4;
272 
273     fprintf( m_outputFile, "%%FSLAX%d%dY%d%d*%%\n",
274              leadingDigitCount, m_gerberUnitFmt,
275              leadingDigitCount, m_gerberUnitFmt );
276     fprintf( m_outputFile,
277              "G04 Gerber Fmt %d.%d, Leading zero omitted, Abs format (unit %s)*\n",
278              leadingDigitCount, m_gerberUnitFmt,
279              m_gerberUnitInch ? "inch" : "mm" );
280 
281     wxString Title = m_creator + wxT( " " ) + GetBuildVersion();
282 
283     // In gerber files, ASCII7 chars only are allowed.
284     // So use a ISO date format (using a space as separator between date and time),
285     // not a localized date format
286     wxDateTime date = wxDateTime::Now();
287     fprintf( m_outputFile, "G04 Created by KiCad (%s) date %s*\n",
288              TO_UTF8( Title ), TO_UTF8( date.FormatISOCombined( ' ') ) );
289 
290     /* Mass parameter: unit = INCHES/MM */
291     if( m_gerberUnitInch )
292         fputs( "%MOIN*%\n", m_outputFile );
293     else
294         fputs( "%MOMM*%\n", m_outputFile );
295 
296     // Be sure the usual dark polarity is selected:
297     fputs( "%LPD*%\n", m_outputFile );
298 
299     // Set initial interpolation mode: always G01 (linear):
300     fputs( "G01*\n", m_outputFile );
301 
302     // Add aperture list start point
303     fputs( "G04 APERTURE LIST*\n", m_outputFile );
304 
305     // Give a minimal value to the default pen size, used to plot items in sketch mode
306     if( m_renderSettings )
307     {
308         const int pen_min = 0.1 * m_IUsPerDecimil * 10000 / 25.4;   // for min width = 0.1 mm
309         m_renderSettings->SetDefaultPenWidth( std::max( m_renderSettings->GetDefaultPenWidth(),
310                                                         pen_min ) );
311     }
312 
313     return true;
314 }
315 
316 
EndPlot()317 bool GERBER_PLOTTER::EndPlot()
318 {
319     char     line[1024];
320     wxString msg;
321 
322     wxASSERT( m_outputFile );
323 
324     /* Outfile is actually a temporary file i.e. workFile */
325     fputs( "M02*\n", m_outputFile );
326     fflush( m_outputFile );
327 
328     fclose( workFile );
329     workFile   = wxFopen( m_workFilename, wxT( "rt" ));
330     wxASSERT( workFile );
331     m_outputFile = finalFile;
332 
333     // Placement of apertures in RS274X
334     while( fgets( line, 1024, workFile ) )
335     {
336         fputs( line, m_outputFile );
337 
338         char* substr = strtok( line, "\n\r" );
339 
340         if( substr && strcmp( substr, "G04 APERTURE LIST*" ) == 0 )
341         {
342             // Add aperture list macro:
343             if( m_hasApertureRoundRect | m_hasApertureRotOval ||
344                 m_hasApertureOutline4P || m_hasApertureRotRect ||
345                 m_hasApertureChamferedRect || m_am_freepoly_list.AmCount() )
346             {
347                 fputs( "G04 Aperture macros list*\n", m_outputFile );
348 
349                 if( m_hasApertureRoundRect )
350                     fputs( APER_MACRO_ROUNDRECT_HEADER, m_outputFile );
351 
352                 if( m_hasApertureRotOval )
353                     fputs( APER_MACRO_SHAPE_OVAL_HEADER, m_outputFile );
354 
355                 if( m_hasApertureRotRect )
356                     fputs( APER_MACRO_ROT_RECT_HEADER, m_outputFile );
357 
358                 if( m_hasApertureOutline4P )
359                     fputs( APER_MACRO_OUTLINE4P_HEADER, m_outputFile );
360 
361                 if( m_hasApertureChamferedRect )
362                 {
363                     fputs( APER_MACRO_OUTLINE5P_HEADER, m_outputFile );
364                     fputs( APER_MACRO_OUTLINE6P_HEADER, m_outputFile );
365                     fputs( APER_MACRO_OUTLINE7P_HEADER, m_outputFile );
366                     fputs( APER_MACRO_OUTLINE8P_HEADER, m_outputFile );
367                 }
368 
369                 if( m_am_freepoly_list.AmCount() )
370                 {
371                     // aperture sizes are in inch or mm, regardless the
372                     // coordinates format
373                     double fscale = 0.0001 * m_plotScale / m_IUsPerDecimil; // inches
374 
375                     if(! m_gerberUnitInch )
376                         fscale *= 25.4;     // size in mm
377 
378                     m_am_freepoly_list.Format( m_outputFile, fscale );
379                 }
380 
381                 fputs( "G04 Aperture macros list end*\n", m_outputFile );
382             }
383 
384             writeApertureList();
385             fputs( "G04 APERTURE END LIST*\n", m_outputFile );
386         }
387     }
388 
389     fclose( workFile );
390     fclose( finalFile );
391     ::wxRemoveFile( m_workFilename );
392     m_outputFile = nullptr;
393 
394     return true;
395 }
396 
397 
SetCurrentLineWidth(int aWidth,void * aData)398 void GERBER_PLOTTER::SetCurrentLineWidth( int aWidth, void* aData )
399 {
400     if( aWidth == DO_NOT_SET_LINE_WIDTH )
401         return;
402     else if( aWidth == USE_DEFAULT_LINE_WIDTH )
403         aWidth =  m_renderSettings->GetDefaultPenWidth();
404 
405     wxASSERT_MSG( aWidth >= 0, "Plotter called to set negative pen width" );
406 
407     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
408     int aperture_attribute = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
409 
410     selectAperture( wxSize( aWidth, aWidth ), 0, 0.0, APERTURE::AT_PLOTTING, aperture_attribute );
411     m_currentPenWidth = aWidth;
412 }
413 
414 
GetOrCreateAperture(const wxSize & aSize,int aRadius,double aRotDegree,APERTURE::APERTURE_TYPE aType,int aApertureAttribute)415 int GERBER_PLOTTER::GetOrCreateAperture( const wxSize& aSize, int aRadius, double aRotDegree,
416                         APERTURE::APERTURE_TYPE aType, int aApertureAttribute )
417 {
418     int last_D_code = 9;
419 
420     // Search an existing aperture
421     for( int idx = 0; idx < (int)m_apertures.size(); ++idx )
422     {
423         APERTURE* tool = &m_apertures[idx];
424         last_D_code = tool->m_DCode;
425 
426         if( (tool->m_Type == aType) && (tool->m_Size == aSize) &&
427             (tool->m_Radius == aRadius) && (tool->m_Rotation == aRotDegree) &&
428             (tool->m_ApertureAttribute == aApertureAttribute) )
429             return idx;
430     }
431 
432     // Allocate a new aperture
433     APERTURE new_tool;
434     new_tool.m_Size  = aSize;
435     new_tool.m_Type  = aType;
436     new_tool.m_Radius  = aRadius;
437     new_tool.m_Rotation  = aRotDegree;
438     new_tool.m_DCode = last_D_code + 1;
439     new_tool.m_ApertureAttribute = aApertureAttribute;
440 
441     m_apertures.push_back( new_tool );
442 
443     return m_apertures.size() - 1;
444 }
445 
446 
GetOrCreateAperture(const std::vector<wxPoint> & aCorners,double aRotDegree,APERTURE::APERTURE_TYPE aType,int aApertureAttribute)447 int GERBER_PLOTTER::GetOrCreateAperture( const std::vector<wxPoint>& aCorners, double aRotDegree,
448                                          APERTURE::APERTURE_TYPE aType, int aApertureAttribute )
449 {
450     int last_D_code = 9;
451 
452     // For APERTURE::AM_FREE_POLYGON aperture macros, we need to create the macro
453     // on the fly, because due to the fact the vertex count is not a constant we
454     // cannot create a static definition.
455     if( APERTURE::AM_FREE_POLYGON == aType )
456     {
457         int idx = m_am_freepoly_list.FindAm( aCorners );
458 
459         if( idx < 0 )
460             m_am_freepoly_list.Append( aCorners );
461     }
462 
463     // Search an existing aperture
464     for( int idx = 0; idx < (int)m_apertures.size(); ++idx )
465     {
466         APERTURE* tool = &m_apertures[idx];
467 
468        last_D_code = tool->m_DCode;
469 
470         if( (tool->m_Type == aType) &&
471             (tool->m_Corners.size() == aCorners.size() ) &&
472             (tool->m_Rotation == aRotDegree) &&
473             (tool->m_ApertureAttribute == aApertureAttribute) )
474         {
475             // A candidate is found. the corner lists must be similar
476             bool is_same = polyCompare( tool->m_Corners, aCorners );
477 
478             if( is_same )
479                 return idx;
480         }
481     }
482 
483     // Allocate a new aperture
484     APERTURE new_tool;
485 
486     new_tool.m_Corners  = aCorners;
487     new_tool.m_Size     = wxSize( 0, 0 );   // Not used
488     new_tool.m_Type     = aType;
489     new_tool.m_Radius   = 0;             // Not used
490     new_tool.m_Rotation = aRotDegree;
491     new_tool.m_DCode    = last_D_code + 1;
492     new_tool.m_ApertureAttribute = aApertureAttribute;
493 
494     m_apertures.push_back( new_tool );
495 
496     return m_apertures.size() - 1;
497 }
498 
499 
selectAperture(const wxSize & aSize,int aRadius,double aRotDegree,APERTURE::APERTURE_TYPE aType,int aApertureAttribute)500 void GERBER_PLOTTER::selectAperture( const wxSize& aSize, int aRadius, double aRotDegree,
501                                      APERTURE::APERTURE_TYPE aType, int aApertureAttribute )
502 {
503     bool change = ( m_currentApertureIdx < 0 ) ||
504                   ( m_apertures[m_currentApertureIdx].m_Type != aType ) ||
505                   ( m_apertures[m_currentApertureIdx].m_Size != aSize ) ||
506                   ( m_apertures[m_currentApertureIdx].m_Radius != aRadius ) ||
507                   ( m_apertures[m_currentApertureIdx].m_Rotation != aRotDegree );
508 
509     if( !change )
510         change = m_apertures[m_currentApertureIdx].m_ApertureAttribute != aApertureAttribute;
511 
512     if( change )
513     {
514         // Pick an existing aperture or create a new one
515         m_currentApertureIdx = GetOrCreateAperture( aSize, aRadius, aRotDegree,
516                                                     aType, aApertureAttribute );
517         fprintf( m_outputFile, "D%d*\n", m_apertures[m_currentApertureIdx].m_DCode );
518     }
519 }
520 
521 
selectAperture(const std::vector<wxPoint> & aCorners,double aRotDegree,APERTURE::APERTURE_TYPE aType,int aApertureAttribute)522 void GERBER_PLOTTER::selectAperture( const std::vector<wxPoint>& aCorners, double aRotDegree,
523                                      APERTURE::APERTURE_TYPE aType, int aApertureAttribute )
524 {
525     bool change = ( m_currentApertureIdx < 0 ) ||
526                   ( m_apertures[m_currentApertureIdx].m_Type != aType ) ||
527                   ( m_apertures[m_currentApertureIdx].m_Corners.size() != aCorners.size() ) ||
528                   ( m_apertures[m_currentApertureIdx].m_Rotation != aRotDegree );
529 
530     if( !change )   // Compare corner lists
531     {
532         for( size_t ii = 0; ii < aCorners.size(); ii++ )
533         {
534             if( aCorners[ii] != m_apertures[m_currentApertureIdx].m_Corners[ii] )
535             {
536                 change = true;
537                 break;
538             }
539         }
540     }
541 
542     if( !change )
543         change = m_apertures[m_currentApertureIdx].m_ApertureAttribute != aApertureAttribute;
544 
545     if( change )
546     {
547         // Pick an existing aperture or create a new one
548         m_currentApertureIdx = GetOrCreateAperture( aCorners, aRotDegree,
549                                                     aType, aApertureAttribute );
550         fprintf( m_outputFile, "D%d*\n", m_apertures[m_currentApertureIdx].m_DCode );
551     }
552 }
553 
554 
selectAperture(int aDiameter,double aPolygonRotation,APERTURE::APERTURE_TYPE aType,int aApertureAttribute)555 void GERBER_PLOTTER::selectAperture( int aDiameter, double aPolygonRotation,
556                                      APERTURE::APERTURE_TYPE aType, int aApertureAttribute )
557 {
558     // Pick an existing aperture or create a new one, matching the
559     // aDiameter, aPolygonRotation, type and attributes for type =
560     // AT_REGULAR_POLY3 to AT_REGULAR_POLY12
561 
562     wxASSERT( aType>= APERTURE::APERTURE_TYPE::AT_REGULAR_POLY3 &&
563               aType <= APERTURE::APERTURE_TYPE::AT_REGULAR_POLY12 );
564 
565     wxSize size( aDiameter, (int)( aPolygonRotation * 1000.0 ) );
566     selectAperture( wxSize( 0, 0), aDiameter/2, aPolygonRotation, aType, aApertureAttribute );
567 }
568 
writeApertureList()569 void GERBER_PLOTTER::writeApertureList()
570 {
571     wxASSERT( m_outputFile );
572     char cbuf[1024];
573     std::string buffer;
574 
575     bool useX1StructuredComment = false;
576 
577     if( !m_useX2format )
578         useX1StructuredComment = true;
579 
580     // Init
581     for( APERTURE& tool : m_apertures )
582     {
583         // aperture sizes are in inch or mm, regardless the
584         // coordinates format
585         double fscale = 0.0001 * m_plotScale / m_IUsPerDecimil; // inches
586 
587         if(! m_gerberUnitInch )
588             fscale *= 25.4;     // size in mm
589 
590         int attribute = tool.m_ApertureAttribute;
591 
592         if( attribute != m_apertureAttribute )
593         {
594             fputs( GBR_APERTURE_METADATA::FormatAttribute(
595                     (GBR_APERTURE_METADATA::GBR_APERTURE_ATTRIB) attribute,
596                             useX1StructuredComment ).c_str(), m_outputFile );
597         }
598 
599         sprintf( cbuf, "%%ADD%d", tool.m_DCode );
600         buffer = cbuf;
601 
602         /* Please note: the Gerber specs for mass parameters say that
603            exponential syntax is *not* allowed and the decimal point should
604            also be always inserted. So the %g format is ruled out, but %f is fine
605            (the # modifier forces the decimal point). Sadly the %f formatter
606            can't remove trailing zeros but that's not a problem, since nothing
607            forbid it (the file is only slightly longer) */
608 
609         switch( tool.m_Type )
610         {
611         case APERTURE::AT_CIRCLE:
612             sprintf( cbuf, "C,%#f*%%\n", tool.GetDiameter() * fscale );
613             break;
614 
615         case APERTURE::AT_RECT:
616             sprintf( cbuf, "R,%#fX%#f*%%\n", tool.m_Size.x * fscale,
617                                              tool.m_Size.y * fscale );
618             break;
619 
620         case APERTURE::AT_PLOTTING:
621             sprintf( cbuf, "C,%#f*%%\n", tool.m_Size.x * fscale );
622             break;
623 
624         case APERTURE::AT_OVAL:
625             sprintf( cbuf, "O,%#fX%#f*%%\n", tool.m_Size.x * fscale,
626                                              tool.m_Size.y * fscale );
627             break;
628 
629         case APERTURE::AT_REGULAR_POLY:
630         case APERTURE::AT_REGULAR_POLY3:
631         case APERTURE::AT_REGULAR_POLY4:
632         case APERTURE::AT_REGULAR_POLY5:
633         case APERTURE::AT_REGULAR_POLY6:
634         case APERTURE::AT_REGULAR_POLY7:
635         case APERTURE::AT_REGULAR_POLY8:
636         case APERTURE::AT_REGULAR_POLY9:
637         case APERTURE::AT_REGULAR_POLY10:
638         case APERTURE::AT_REGULAR_POLY11:
639         case APERTURE::AT_REGULAR_POLY12:
640             sprintf( cbuf, "P,%#fX%dX%#f*%%\n", tool.GetDiameter() * fscale,
641                      tool.GetRegPolyVerticeCount(), tool.GetRotation() );
642             break;
643 
644         case APERTURE::AM_ROUND_RECT:       // Aperture macro for round rect pads
645         {
646             // The aperture macro needs coordinates of the centers of the 4 corners
647             std::vector<VECTOR2I> corners;
648             wxSize half_size( tool.m_Size.x/2-tool.m_Radius, tool.m_Size.y/2-tool.m_Radius );
649 
650             corners.emplace_back( -half_size.x, -half_size.y );
651             corners.emplace_back( half_size.x, -half_size.y );
652             corners.emplace_back( half_size.x, half_size.y );
653             corners.emplace_back( -half_size.x, half_size.y );
654 
655             // Rotate the corner coordinates:
656             for( int ii = 0; ii < 4; ii++ )
657                 RotatePoint( corners[ii], -tool.m_Rotation*10.0 );
658 
659             sprintf( cbuf, "%s,%#fX", APER_MACRO_ROUNDRECT_NAME,
660                      tool.m_Radius * fscale );
661             buffer += cbuf;
662 
663             // Add each corner
664             for( int ii = 0; ii < 4; ii++ )
665             {
666                 sprintf( cbuf, "%#fX%#fX",
667                          corners[ii].x * fscale, corners[ii].y * fscale );
668                 buffer += cbuf;
669             }
670 
671             sprintf( cbuf, "0*%%\n" );
672         }
673             break;
674 
675         case APERTURE::AM_ROT_RECT:         // Aperture macro for rotated rect pads
676             sprintf( cbuf, "%s,%#fX%#fX%#f*%%\n", APER_MACRO_ROT_RECT_NAME,
677                      tool.m_Size.x * fscale, tool.m_Size.y * fscale,
678                      tool.m_Rotation );
679             break;
680 
681         case APERTURE::APER_MACRO_OUTLINE4P:    // Aperture macro for trapezoid pads
682         case APERTURE::APER_MACRO_OUTLINE5P:    // Aperture macro for chamfered rect pads
683         case APERTURE::APER_MACRO_OUTLINE6P:    // Aperture macro for chamfered rect pads
684         case APERTURE::APER_MACRO_OUTLINE7P:    // Aperture macro for chamfered rect pads
685         case APERTURE::APER_MACRO_OUTLINE8P:    // Aperture macro for chamfered rect pads
686             switch( tool.m_Type )
687             {
688             case APERTURE::APER_MACRO_OUTLINE4P:
689                 sprintf( cbuf, "%s,", APER_MACRO_OUTLINE4P_NAME ); break;
690             case APERTURE::APER_MACRO_OUTLINE5P:
691                 sprintf( cbuf, "%s,", APER_MACRO_OUTLINE5P_NAME ); break;
692             case APERTURE::APER_MACRO_OUTLINE6P:
693                 sprintf( cbuf, "%s,", APER_MACRO_OUTLINE6P_NAME ); break;
694             case APERTURE::APER_MACRO_OUTLINE7P:
695                 sprintf( cbuf, "%s,", APER_MACRO_OUTLINE7P_NAME ); break;
696             case APERTURE::APER_MACRO_OUTLINE8P:
697                 sprintf( cbuf, "%s,", APER_MACRO_OUTLINE8P_NAME ); break;
698             default:
699                 break;
700             }
701 
702             buffer += cbuf;
703 
704             // Output all corners (should be 4 to 8 corners)
705             // Remember: the Y coordinate must be negated, due to the fact in Pcbnew
706             // the Y axis is from top to bottom
707             for( size_t ii = 0; ii < tool.m_Corners.size(); ii++ )
708             {
709                 sprintf( cbuf, "%#fX%#fX",
710                          tool.m_Corners[ii].x * fscale, -tool.m_Corners[ii].y * fscale );
711                 buffer += cbuf;
712             }
713 
714             // close outline and output rotation
715             sprintf( cbuf, "%#f*%%\n", tool.m_Rotation );
716             break;
717 
718         case APERTURE::AM_ROTATED_OVAL:         // Aperture macro for rotated oval pads
719                                                 // (not rotated is a primitive)
720             // m_Size.x = full length; m_Size.y = width, and the macro aperture expects
721             // the position of ends
722         {
723                 // the seg_len is the distance between the 2 circle centers
724                 int seg_len = tool.m_Size.x - tool.m_Size.y;
725                 // Center of the circle on the segment start point:
726                 VECTOR2I start( seg_len/2, 0 );
727                 // Center of the circle on the segment end point:
728                 VECTOR2I end( - seg_len/2, 0 );
729 
730                 RotatePoint( start, tool.m_Rotation*10.0 );
731                 RotatePoint( end, tool.m_Rotation*10.0 );
732 
733                 sprintf( cbuf, "%s,%#fX%#fX%#fX%#fX%#fX0*%%\n", APER_MACRO_SHAPE_OVAL_NAME,
734                          tool.m_Size.y * fscale,                // width
735                          start.x * fscale, -start.y * fscale,   // X,Y corner start pos
736                          end.x * fscale, -end.y * fscale );     // X,Y cornerend  pos
737         }
738             break;
739 
740         case APERTURE::AM_FREE_POLYGON:
741         {
742             // Find the aperture macro name in the list of aperture macro
743             // created on the fly for this polygon:
744             int idx = m_am_freepoly_list.FindAm( tool.m_Corners );
745 
746             // Write DCODE id ( "%ADDxx" is already in buffer) and rotation
747             // the full line is something like :%ADD12FreePoly1,45.000000*%
748             sprintf( cbuf, "%s%d,%#f*%%\n", AM_FREEPOLY_BASENAME, idx, tool.m_Rotation );
749             break;
750         }
751         }
752 
753         buffer += cbuf;
754         fputs( buffer.c_str(), m_outputFile );
755 
756         m_apertureAttribute = attribute;
757 
758         // Currently reset the aperture attribute. Perhaps a better optimization
759         // is to store the last attribute
760         if( attribute )
761         {
762             if( m_useX2format )
763                 fputs( "%TD*%\n", m_outputFile );
764             else
765                 fputs( "G04 #@! TD*\n", m_outputFile );
766 
767             m_apertureAttribute = 0;
768         }
769 
770     }
771 }
772 
773 
PenTo(const wxPoint & aPos,char plume)774 void GERBER_PLOTTER::PenTo( const wxPoint& aPos, char plume )
775 {
776     wxASSERT( m_outputFile );
777     DPOINT pos_dev = userToDeviceCoordinates( aPos );
778 
779     switch( plume )
780     {
781     case 'Z':
782         break;
783 
784     case 'U':
785         emitDcode( pos_dev, 2 );
786         break;
787 
788     case 'D':
789         emitDcode( pos_dev, 1 );
790     }
791 
792     m_penState = plume;
793 }
794 
795 
Rect(const wxPoint & p1,const wxPoint & p2,FILL_T fill,int width)796 void GERBER_PLOTTER::Rect( const wxPoint& p1, const wxPoint& p2, FILL_T fill, int width )
797 {
798     std::vector< wxPoint > cornerList;
799 
800     // Build corners list
801     cornerList.push_back( p1 );
802     wxPoint corner(p1.x, p2.y);
803     cornerList.push_back( corner );
804     cornerList.push_back( p2 );
805     corner.x = p2.x;
806     corner.y = p1.y;
807     cornerList.push_back( corner );
808     cornerList.push_back( p1 );
809 
810     PlotPoly( cornerList, fill, width );
811 }
812 
813 
Circle(const wxPoint & aCenter,int aDiameter,FILL_T aFill,int aWidth)814 void GERBER_PLOTTER::Circle( const wxPoint& aCenter, int aDiameter, FILL_T aFill, int aWidth )
815 {
816     Arc( aCenter, 0, 3600, aDiameter / 2, aFill, aWidth );
817 }
818 
819 
820 
Arc(const wxPoint & aCenter,double aStAngle,double aEndAngle,int aRadius,FILL_T aFill,int aWidth)821 void GERBER_PLOTTER::Arc( const wxPoint& aCenter, double aStAngle, double aEndAngle, int aRadius,
822                           FILL_T aFill, int aWidth )
823 {
824     SetCurrentLineWidth( aWidth );
825 
826     // aFill is not used here.
827     plotArc( aCenter, aStAngle, aEndAngle, aRadius, false );
828 }
829 
830 
Arc(const SHAPE_ARC & aArc)831 void GERBER_PLOTTER::Arc( const SHAPE_ARC& aArc )
832 {
833     SetCurrentLineWidth( aArc.GetWidth() );
834 
835     // aFill is not used here.
836     plotArc( aArc, false );
837 }
838 
839 
plotArc(const SHAPE_ARC & aArc,bool aPlotInRegion)840 void GERBER_PLOTTER::plotArc( const SHAPE_ARC& aArc, bool aPlotInRegion )
841 {
842     wxPoint start( aArc.GetP0() );
843     wxPoint end( aArc.GetP1() );
844     wxPoint center( aArc.GetCenter() );
845     double start_angle = aArc.GetStartAngle();
846     double end_angle = aArc.GetEndAngle();
847 
848     if( !aPlotInRegion )
849         MoveTo( start);
850     else
851         LineTo( start );
852 
853     DPOINT devEnd = userToDeviceCoordinates( end );
854     DPOINT devCenter = userToDeviceCoordinates( center ) - userToDeviceCoordinates( start );
855 
856     fprintf( m_outputFile, "G75*\n" );        // Multiquadrant (360 degrees) mode
857 
858     if( start_angle < end_angle )
859         fprintf( m_outputFile, "G03*\n" );    // Active circular interpolation, CCW
860     else
861         fprintf( m_outputFile, "G02*\n" );    // Active circular interpolation, CW
862 
863     fprintf( m_outputFile, "X%dY%dI%dJ%dD01*\n",
864              KiROUND( devEnd.x ), KiROUND( devEnd.y ),
865              KiROUND( devCenter.x ), KiROUND( devCenter.y ) );
866 
867     fprintf( m_outputFile, "G01*\n" ); // Back to linear interpolate (perhaps useless here).
868 }
869 
870 
plotArc(const wxPoint & aCenter,double aStAngle,double aEndAngle,int aRadius,bool aPlotInRegion)871 void GERBER_PLOTTER::plotArc( const wxPoint& aCenter, double aStAngle, double aEndAngle,
872                               int aRadius, bool aPlotInRegion )
873 {
874     wxPoint start, end;
875     start.x = aCenter.x + KiROUND( cosdecideg( aRadius, aStAngle ) );
876     start.y = aCenter.y - KiROUND( sindecideg( aRadius, aStAngle ) );
877 
878     if( !aPlotInRegion )
879         MoveTo( start );
880     else
881         LineTo( start );
882 
883     end.x = aCenter.x + KiROUND( cosdecideg( aRadius, aEndAngle ) );
884     end.y = aCenter.y - KiROUND( sindecideg( aRadius, aEndAngle ) );
885     DPOINT devEnd = userToDeviceCoordinates( end );
886     DPOINT devCenter = userToDeviceCoordinates( aCenter ) - userToDeviceCoordinates( start );
887 
888     fprintf( m_outputFile, "G75*\n" );        // Multiquadrant (360 degrees) mode
889 
890     if( aStAngle < aEndAngle )
891         fprintf( m_outputFile, "G03*\n" );    // Active circular interpolation, CCW
892     else
893         fprintf( m_outputFile, "G02*\n" );    // Active circular interpolation, CW
894 
895     fprintf( m_outputFile, "X%dY%dI%dJ%dD01*\n",
896              KiROUND( devEnd.x ), KiROUND( devEnd.y ),
897              KiROUND( devCenter.x ), KiROUND( devCenter.y ) );
898 
899     fprintf( m_outputFile, "G01*\n" ); // Back to linear interpolate (perhaps useless here).
900 }
901 
902 
PlotGerberRegion(const SHAPE_LINE_CHAIN & aPoly,void * aData)903 void GERBER_PLOTTER::PlotGerberRegion( const SHAPE_LINE_CHAIN& aPoly, void* aData )
904 {
905     if( aPoly.PointCount() <= 2 )
906         return;
907 
908     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
909 
910     bool clearTA_AperFunction = false;     // true if a TA.AperFunction is used
911 
912     if( gbr_metadata )
913     {
914         std::string attrib = gbr_metadata->m_ApertureMetadata.FormatAttribute( !m_useX2format );
915 
916         if( !attrib.empty() )
917         {
918             fputs( attrib.c_str(), m_outputFile );
919             clearTA_AperFunction = true;
920         }
921     }
922 
923     PlotPoly( aPoly, FILL_T::FILLED_SHAPE, 0 , gbr_metadata );
924 
925     // Clear the TA attribute, to avoid the next item to inherit it:
926     if( clearTA_AperFunction )
927     {
928         if( m_useX2format )
929         {
930             fputs( "%TD.AperFunction*%\n", m_outputFile );
931         }
932         else
933         {
934             fputs( "G04 #@! TD.AperFunction*\n", m_outputFile );
935         }
936     }
937 }
938 
939 
PlotGerberRegion(const std::vector<wxPoint> & aCornerList,void * aData)940 void GERBER_PLOTTER::PlotGerberRegion( const std::vector< wxPoint >& aCornerList, void* aData )
941 {
942     if( aCornerList.size() <= 2 )
943         return;
944 
945     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
946 
947     bool clearTA_AperFunction = false;     // true if a TA.AperFunction is used
948 
949     if( gbr_metadata )
950     {
951         std::string attrib = gbr_metadata->m_ApertureMetadata.FormatAttribute( !m_useX2format );
952 
953         if( !attrib.empty() )
954         {
955             fputs( attrib.c_str(), m_outputFile );
956             clearTA_AperFunction = true;
957         }
958     }
959 
960     PlotPoly( aCornerList, FILL_T::FILLED_SHAPE, 0, gbr_metadata );
961 
962     // Clear the TA attribute, to avoid the next item to inherit it:
963     if( clearTA_AperFunction )
964     {
965         if( m_useX2format )
966         {
967             fputs( "%TD.AperFunction*%\n", m_outputFile );
968         }
969         else
970         {
971             fputs( "G04 #@! TD.AperFunction*\n", m_outputFile );
972         }
973     }
974 }
975 
976 
PlotPoly(const SHAPE_LINE_CHAIN & aPoly,FILL_T aFill,int aWidth,void * aData)977 void GERBER_PLOTTER::PlotPoly( const SHAPE_LINE_CHAIN& aPoly, FILL_T aFill, int aWidth,
978                                void* aData )
979 {
980     if( aPoly.CPoints().size() <= 1 )
981         return;
982 
983     // Gerber format does not know filled polygons with thick outline
984     // Therefore, to plot a filled polygon with outline having a thickness,
985     // one should plot outline as thick segments
986     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
987 
988     if( gbr_metadata )
989         formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
990 
991     if( aFill != FILL_T::NO_FILL )
992     {
993         fputs( "G36*\n", m_outputFile );
994 
995         MoveTo( wxPoint( aPoly.CPoint( 0 ) ) );
996 
997         fputs( "G01*\n", m_outputFile );      // Set linear interpolation.
998 
999         for( int ii = 1; ii < aPoly.PointCount(); ii++ )
1000         {
1001             int arcindex = aPoly.ArcIndex( ii );
1002 
1003             if( arcindex < 0 )
1004             {
1005                 /// Plain point
1006                 LineTo( wxPoint( aPoly.CPoint( ii ) ) );
1007             }
1008             else
1009             {
1010                 const SHAPE_ARC& arc = aPoly.Arc( arcindex );
1011 
1012                 plotArc( arc, ii > 0 );
1013             }
1014         }
1015 
1016         // If the polygon is not closed, close it:
1017         if( aPoly.CPoint( 0 ) != aPoly.CPoint( -1 ) )
1018             FinishTo( wxPoint( aPoly.CPoint( 0 ) ) );
1019 
1020         fputs( "G37*\n", m_outputFile );
1021     }
1022 
1023     if( aWidth > 0 )    // Draw the polyline/polygon outline
1024     {
1025         SetCurrentLineWidth( aWidth, gbr_metadata );
1026 
1027         MoveTo( wxPoint( aPoly.CPoint( 0 ) ) );
1028 
1029         for( int ii = 1; ii < aPoly.PointCount(); ii++ )
1030         {
1031             int arcindex = aPoly.ArcIndex( ii );
1032 
1033             if( arcindex < 0 )
1034             {
1035                 /// Plain point
1036                 LineTo( wxPoint( aPoly.CPoint( ii ) ) );
1037             }
1038             else
1039             {
1040                 const SHAPE_ARC& arc = aPoly.Arc( arcindex );
1041 
1042                 plotArc( arc, ii > 0 );
1043             }
1044         }
1045 
1046         // Ensure the thick outline is closed for filled polygons
1047         // (if not filled, could be only a polyline)
1048         if( aFill != FILL_T::NO_FILL && ( aPoly.CPoint( 0 ) != aPoly.CPoint( -1 ) ) )
1049             LineTo( wxPoint( aPoly.CPoint( 0 ) ) );
1050 
1051         PenFinish();
1052     }
1053 }
1054 
PlotPoly(const std::vector<wxPoint> & aCornerList,FILL_T aFill,int aWidth,void * aData)1055 void GERBER_PLOTTER::PlotPoly( const std::vector< wxPoint >& aCornerList, FILL_T aFill, int aWidth,
1056                                void * aData )
1057 {
1058     if( aCornerList.size() <= 1 )
1059         return;
1060 
1061     // Gerber format does not know filled polygons with thick outline
1062     // Therefore, to plot a filled polygon with outline having a thickness,
1063     // one should plot outline as thick segments
1064     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
1065 
1066     if( gbr_metadata )
1067         formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1068 
1069     if( aFill != FILL_T::NO_FILL )
1070     {
1071         fputs( "G36*\n", m_outputFile );
1072 
1073         MoveTo( aCornerList[0] );
1074         fputs( "G01*\n", m_outputFile );      // Set linear interpolation.
1075 
1076         for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
1077             LineTo( aCornerList[ii] );
1078 
1079         // If the polygon is not closed, close it:
1080         if( aCornerList[0] != aCornerList[aCornerList.size()-1] )
1081             FinishTo( aCornerList[0] );
1082 
1083         fputs( "G37*\n", m_outputFile );
1084     }
1085 
1086     if( aWidth > 0 )    // Draw the polyline/polygon outline
1087     {
1088         SetCurrentLineWidth( aWidth, gbr_metadata );
1089 
1090         MoveTo( aCornerList[0] );
1091 
1092         for( unsigned ii = 1; ii < aCornerList.size(); ii++ )
1093             LineTo( aCornerList[ii] );
1094 
1095         // Ensure the thick outline is closed for filled polygons
1096         // (if not filled, could be only a polyline)
1097         if( aFill != FILL_T::NO_FILL && ( aCornerList[aCornerList.size() - 1] != aCornerList[0] ) )
1098             LineTo( aCornerList[0] );
1099 
1100         PenFinish();
1101     }
1102 }
1103 
1104 
ThickSegment(const wxPoint & start,const wxPoint & end,int width,OUTLINE_MODE tracemode,void * aData)1105 void GERBER_PLOTTER::ThickSegment( const wxPoint& start, const wxPoint& end, int width,
1106                                    OUTLINE_MODE tracemode, void* aData )
1107 {
1108     if( tracemode == FILLED )
1109     {
1110         GBR_METADATA *gbr_metadata = static_cast<GBR_METADATA*>( aData );
1111         SetCurrentLineWidth( width, gbr_metadata );
1112 
1113         if( gbr_metadata )
1114             formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1115 
1116         MoveTo( start );
1117         FinishTo( end );
1118     }
1119     else
1120     {
1121         SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
1122         segmentAsOval( start, end, width, tracemode );
1123     }
1124 }
1125 
ThickArc(const wxPoint & centre,double StAngle,double EndAngle,int radius,int width,OUTLINE_MODE tracemode,void * aData)1126 void GERBER_PLOTTER::ThickArc( const wxPoint& centre, double StAngle, double EndAngle,
1127                            int radius, int width, OUTLINE_MODE tracemode, void* aData )
1128 {
1129     GBR_METADATA *gbr_metadata = static_cast<GBR_METADATA*>( aData );
1130     SetCurrentLineWidth( width, gbr_metadata );
1131 
1132     if( gbr_metadata )
1133         formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1134 
1135     if( tracemode == FILLED )
1136     {
1137         Arc( centre, StAngle, EndAngle, radius, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH );
1138     }
1139     else
1140     {
1141         SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
1142         Arc( centre, StAngle, EndAngle, radius - ( width - m_currentPenWidth ) / 2, FILL_T::NO_FILL,
1143              DO_NOT_SET_LINE_WIDTH );
1144         Arc( centre, StAngle, EndAngle, radius + ( width - m_currentPenWidth ) / 2, FILL_T::NO_FILL,
1145              DO_NOT_SET_LINE_WIDTH );
1146     }
1147 }
1148 
1149 
ThickRect(const wxPoint & p1,const wxPoint & p2,int width,OUTLINE_MODE tracemode,void * aData)1150 void GERBER_PLOTTER::ThickRect( const wxPoint& p1, const wxPoint& p2, int width,
1151                             OUTLINE_MODE tracemode, void* aData )
1152 {
1153     GBR_METADATA *gbr_metadata = static_cast<GBR_METADATA*>( aData );
1154     SetCurrentLineWidth( width, gbr_metadata );
1155 
1156     if( gbr_metadata )
1157         formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1158 
1159     if( tracemode == FILLED )
1160     {
1161         Rect( p1, p2, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH );
1162     }
1163     else
1164     {
1165         SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
1166         wxPoint offsetp1( p1.x - (width - m_currentPenWidth) / 2,
1167                           p1.y - (width - m_currentPenWidth) / 2 );
1168         wxPoint offsetp2( p2.x + (width - m_currentPenWidth) / 2,
1169                           p2.y + (width - m_currentPenWidth) / 2 );
1170         Rect( offsetp1, offsetp2, FILL_T::NO_FILL, -1 );
1171         offsetp1.x += (width - m_currentPenWidth);
1172         offsetp1.y += (width - m_currentPenWidth);
1173         offsetp2.x -= (width - m_currentPenWidth);
1174         offsetp2.y -= (width - m_currentPenWidth);
1175         Rect( offsetp1, offsetp2, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH );
1176     }
1177 }
1178 
1179 
ThickCircle(const wxPoint & pos,int diametre,int width,OUTLINE_MODE tracemode,void * aData)1180 void GERBER_PLOTTER::ThickCircle( const wxPoint& pos, int diametre, int width,
1181                                   OUTLINE_MODE tracemode, void* aData )
1182 {
1183     GBR_METADATA *gbr_metadata = static_cast<GBR_METADATA*>( aData );
1184     SetCurrentLineWidth( width, gbr_metadata );
1185 
1186     if( gbr_metadata )
1187         formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1188 
1189     if( tracemode == FILLED )
1190     {
1191         Circle( pos, diametre, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH );
1192     }
1193     else
1194     {
1195         SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata );
1196         Circle( pos, diametre - (width - m_currentPenWidth), FILL_T::NO_FILL,
1197                 DO_NOT_SET_LINE_WIDTH );
1198         Circle( pos, diametre + (width - m_currentPenWidth), FILL_T::NO_FILL,
1199                 DO_NOT_SET_LINE_WIDTH );
1200     }
1201 }
1202 
1203 
FilledCircle(const wxPoint & pos,int diametre,OUTLINE_MODE tracemode,void * aData)1204 void GERBER_PLOTTER::FilledCircle( const wxPoint& pos, int diametre,
1205                               OUTLINE_MODE tracemode, void* aData )
1206 {
1207     // A filled circle is a graphic item, not a pad.
1208     // So it is drawn, not flashed.
1209     GBR_METADATA *gbr_metadata = static_cast<GBR_METADATA*>( aData );
1210 
1211     if( gbr_metadata )
1212         formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1213 
1214     if( tracemode == FILLED )
1215     {
1216         // Draw a circle of diameter = diameter/2 with a line thickness = radius,
1217         // To create a filled circle
1218         SetCurrentLineWidth( diametre/2, gbr_metadata );
1219         Circle( pos, diametre/2, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH );
1220     }
1221     else
1222     {
1223         SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, gbr_metadata );
1224         Circle( pos, diametre, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH );
1225     }
1226 }
1227 
1228 
FlashPadCircle(const wxPoint & pos,int diametre,OUTLINE_MODE trace_mode,void * aData)1229 void GERBER_PLOTTER::FlashPadCircle( const wxPoint& pos, int diametre, OUTLINE_MODE trace_mode,
1230                                      void* aData )
1231 {
1232     wxSize size( diametre, diametre );
1233     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
1234 
1235     if( trace_mode == SKETCH )
1236     {
1237         if( gbr_metadata )
1238             formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1239 
1240         SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
1241 
1242         Circle( pos, diametre - m_currentPenWidth, FILL_T::NO_FILL, DO_NOT_SET_LINE_WIDTH );
1243     }
1244     else
1245     {
1246         DPOINT pos_dev = userToDeviceCoordinates( pos );
1247 
1248         int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
1249         selectAperture( size, 0, 0.0, APERTURE::AT_CIRCLE, aperture_attrib );
1250 
1251         if( gbr_metadata )
1252             formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1253 
1254         emitDcode( pos_dev, 3 );
1255     }
1256 }
1257 
1258 
FlashPadOval(const wxPoint & pos,const wxSize & aSize,double orient,OUTLINE_MODE trace_mode,void * aData)1259 void GERBER_PLOTTER::FlashPadOval( const wxPoint& pos, const wxSize& aSize, double orient,
1260                                    OUTLINE_MODE trace_mode, void* aData )
1261 {
1262     wxASSERT( m_outputFile );
1263     wxSize size( aSize );
1264     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
1265 
1266     // Flash a vertical or horizontal shape (this is a basic aperture).
1267     if( ( orient == 0 || orient == 900 || orient == 1800 || orient == 2700 )
1268         && trace_mode == FILLED )
1269     {
1270         if( orient == 900 || orient == 2700 ) /* orientation turned 90 deg. */
1271             std::swap( size.x, size.y );
1272 
1273         DPOINT pos_dev = userToDeviceCoordinates( pos );
1274         int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
1275         selectAperture( size, 0, 0.0, APERTURE::AT_OVAL, aperture_attrib );
1276 
1277         if( gbr_metadata )
1278             formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1279 
1280         emitDcode( pos_dev, 3 );
1281     }
1282     else    // Plot pad as region.
1283             // Only regions and flashed items accept a object attribute TO.P for the pin name
1284     {
1285         if( trace_mode == FILLED )
1286         {
1287         #ifdef GBR_USE_MACROS_FOR_ROTATED_OVAL
1288             if( !m_gerberDisableApertMacros )
1289         #endif
1290             {
1291                 m_hasApertureRotOval = true;
1292                 // We are using a aperture macro that expect size.y < size.x
1293                 // i.e draw a horizontal line for rotation = 0.0
1294                 // size.x = length, size.y = width
1295                 if( size.x < size.y )
1296                 {
1297                     std::swap( size.x, size.y );
1298                     orient += 900;
1299 
1300                     if( orient > 1800 )
1301                         orient -= 1800;
1302                 }
1303 
1304                 DPOINT pos_dev = userToDeviceCoordinates( pos );
1305                 int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
1306                 selectAperture( size, 0, orient/10.0, APERTURE::AM_ROTATED_OVAL, aperture_attrib );
1307 
1308                 if( gbr_metadata )
1309                     formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1310 
1311                 emitDcode( pos_dev, 3 );
1312                 return;
1313             }
1314             // Draw the oval as round rect pad with a radius = 50% min size)
1315             // In gerber file, it will be drawn as a region with arcs, and can be
1316             // detected as pads (similar to a flashed pad)
1317             FlashPadRoundRect( pos, aSize, std::min( aSize.x, aSize.y ) / 2,
1318                                orient, FILLED, aData );
1319         }
1320         else    // Non filled shape: plot outlines:
1321         {
1322             if( size.x > size.y )
1323             {
1324                 std::swap( size.x, size.y );
1325 
1326                 if( orient < 2700 )
1327                     orient += 900;
1328                 else
1329                     orient -= 2700;
1330             }
1331 
1332             sketchOval( pos, size, orient, -1 );
1333         }
1334     }
1335 }
1336 
1337 
FlashPadRect(const wxPoint & pos,const wxSize & aSize,double orient,OUTLINE_MODE trace_mode,void * aData)1338 void GERBER_PLOTTER::FlashPadRect( const wxPoint& pos, const wxSize& aSize,
1339                                    double orient, OUTLINE_MODE trace_mode, void* aData )
1340 
1341 {
1342     wxASSERT( m_outputFile );
1343     wxSize size( aSize );
1344     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
1345 
1346     // Plot as an aperture flash
1347     switch( int( orient ) )
1348     {
1349     case 900:
1350     case 2700:        // rotation of 90 degrees or 270 swaps sizes
1351         std::swap( size.x, size.y );
1352         KI_FALLTHROUGH;
1353 
1354     case 0:
1355     case 1800:
1356         if( trace_mode == SKETCH )
1357         {
1358             if( gbr_metadata )
1359                 formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1360 
1361             SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH );
1362             Rect( wxPoint( pos.x - (size.x - GetCurrentLineWidth()) / 2,
1363                            pos.y - (size.y - GetCurrentLineWidth()) / 2 ),
1364                   wxPoint( pos.x + (size.x - GetCurrentLineWidth()) / 2,
1365                            pos.y + (size.y - GetCurrentLineWidth()) / 2 ),
1366                   FILL_T::NO_FILL, GetCurrentLineWidth() );
1367         }
1368         else
1369         {
1370             DPOINT pos_dev = userToDeviceCoordinates( pos );
1371             int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
1372             selectAperture( size, 0, 0.0, APERTURE::AT_RECT, aperture_attrib );
1373 
1374             if( gbr_metadata )
1375                 formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1376 
1377             emitDcode( pos_dev, 3 );
1378         }
1379         break;
1380 
1381     default:
1382     #ifdef GBR_USE_MACROS_FOR_ROTATED_RECT
1383         if( trace_mode != SKETCH && !m_gerberDisableApertMacros )
1384         {
1385             m_hasApertureRotRect = true;
1386             DPOINT pos_dev = userToDeviceCoordinates( pos );
1387             int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
1388             selectAperture( size, 0, orient/10.0, APERTURE::AM_ROT_RECT, aperture_attrib );
1389 
1390             if( gbr_metadata )
1391                 formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1392 
1393             emitDcode( pos_dev, 3 );
1394 
1395             break;
1396         }
1397     #endif
1398         {
1399         // plot pad shape as Gerber region
1400         wxPoint coord[4];
1401         // coord[0] is assumed the lower left
1402         // coord[1] is assumed the upper left
1403         // coord[2] is assumed the upper right
1404         // coord[3] is assumed the lower right
1405 
1406         coord[0].x = -size.x/2;   // lower left
1407         coord[0].y = size.y/2;
1408         coord[1].x = -size.x/2;   // upper left
1409         coord[1].y = -size.y/2;
1410         coord[2].x = size.x/2;    // upper right
1411         coord[2].y = -size.y/2;
1412         coord[3].x = size.x/2;    // lower right
1413         coord[3].y = size.y/2;
1414 
1415         FlashPadTrapez( pos, coord, orient, trace_mode, aData );
1416         }
1417     break;
1418     }
1419 }
1420 
FlashPadRoundRect(const wxPoint & aPadPos,const wxSize & aSize,int aCornerRadius,double aOrient,OUTLINE_MODE aTraceMode,void * aData)1421 void GERBER_PLOTTER::FlashPadRoundRect( const wxPoint& aPadPos, const wxSize& aSize,
1422                                      int aCornerRadius, double aOrient,
1423                                      OUTLINE_MODE aTraceMode, void* aData )
1424 
1425 {
1426     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
1427 
1428     if( aTraceMode != FILLED )
1429     {
1430         SHAPE_POLY_SET outline;
1431         TransformRoundChamferedRectToPolygon( outline, aPadPos, aSize, aOrient, aCornerRadius,
1432                                               0.0, 0, 0, GetPlotterArcHighDef(), ERROR_INSIDE );
1433 
1434         SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &gbr_metadata );
1435         outline.Inflate( -GetCurrentLineWidth()/2, 16 );
1436 
1437         std::vector< wxPoint > cornerList;
1438         // TransformRoundRectToPolygon creates only one convex polygon
1439         SHAPE_LINE_CHAIN& poly = outline.Outline( 0 );
1440         cornerList.reserve( poly.PointCount() + 1 );
1441 
1442         for( int ii = 0; ii < poly.PointCount(); ++ii )
1443             cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );
1444 
1445         // Close polygon
1446         cornerList.push_back( cornerList[0] );
1447 
1448         // plot outlines
1449         PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), gbr_metadata );
1450     }
1451     else
1452     {
1453     #ifdef GBR_USE_MACROS_FOR_ROUNDRECT
1454         if( !m_gerberDisableApertMacros )
1455     #endif
1456         {
1457             m_hasApertureRoundRect = true;
1458 
1459             DPOINT pos_dev = userToDeviceCoordinates( aPadPos );
1460             int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
1461             selectAperture( aSize, aCornerRadius, aOrient/10.0,
1462                             APERTURE::AM_ROUND_RECT, aperture_attrib );
1463 
1464             if( gbr_metadata )
1465                 formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1466 
1467             emitDcode( pos_dev, 3 );
1468             return;
1469         }
1470 
1471         // A Pad RoundRect is plotted as a Gerber region.
1472         // Initialize region metadata:
1473         bool clearTA_AperFunction = false;     // true if a TA.AperFunction is used
1474 
1475         if( gbr_metadata )
1476         {
1477             formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1478             std::string attrib = gbr_metadata->m_ApertureMetadata.FormatAttribute( !m_useX2format );
1479 
1480             if( !attrib.empty() )
1481             {
1482                 fputs( attrib.c_str(), m_outputFile );
1483                 clearTA_AperFunction = true;
1484             }
1485         }
1486 
1487         // Plot the region using arcs in corners.
1488         plotRoundRectAsRegion( aPadPos, aSize, aCornerRadius, aOrient );
1489 
1490         // Clear the TA attribute, to avoid the next item to inherit it:
1491         if( clearTA_AperFunction )
1492         {
1493             if( m_useX2format )
1494                 fputs( "%TD.AperFunction*%\n", m_outputFile );
1495             else
1496                 fputs( "G04 #@! TD.AperFunction*\n", m_outputFile );
1497         }
1498     }
1499 }
1500 
1501 
plotRoundRectAsRegion(const wxPoint & aRectCenter,const wxSize & aSize,int aCornerRadius,double aOrient)1502 void GERBER_PLOTTER::plotRoundRectAsRegion( const wxPoint& aRectCenter, const wxSize& aSize,
1503                                             int aCornerRadius, double aOrient )
1504 {
1505     // The region outline is generated by 4 sides and 4 90 deg arcs
1506     //  1 --- 2
1507     //  |  c  |
1508     //  4 --- 3
1509 
1510     // Note also in user coordinates the Y axis is from top to bottom
1511     // for historical reasons.
1512 
1513     // A helper structure to handle outlines coordinates (segments and arcs)
1514     // in user coordinates
1515     struct RR_EDGE
1516     {
1517         wxPoint m_start;
1518         wxPoint m_end;
1519         wxPoint m_center;
1520         // in decidegrees: angle start. angle end = m_arc_angle_start+arc_angle
1521         double  m_arc_angle_start;
1522     };
1523 
1524     const double arc_angle = -900.0;    // in decidegrees
1525     int hsizeX = aSize.x/2;
1526     int hsizeY = aSize.y/2;
1527 
1528     RR_EDGE curr_edge;
1529     std::vector<RR_EDGE> rr_outline;
1530 
1531     // Build outline coordinates, relative to rectangle center, rotation 0:
1532 
1533     // Top left corner 1 (and 4 to 1 left vertical side @ x=-hsizeX)
1534     curr_edge.m_start.x = -hsizeX;
1535     curr_edge.m_start.y = hsizeY - aCornerRadius;
1536     curr_edge.m_end.x = curr_edge.m_start.x;
1537     curr_edge.m_end.y = -hsizeY + aCornerRadius;
1538     curr_edge.m_center.x = -hsizeX + aCornerRadius;
1539     curr_edge.m_center.y = curr_edge.m_end.y;
1540     curr_edge.m_arc_angle_start = aOrient + 1800.0;     // En decidegree
1541 
1542     rr_outline.push_back( curr_edge );
1543 
1544     // Top right corner 2 (and 1 to 2 top horizontal side @ y=-hsizeY)
1545     curr_edge.m_start.x = -hsizeX + aCornerRadius;
1546     curr_edge.m_start.y = -hsizeY;
1547     curr_edge.m_end.x = hsizeX - aCornerRadius;
1548     curr_edge.m_end.y = curr_edge.m_start.y;
1549     curr_edge.m_center.x = curr_edge.m_end.x;
1550     curr_edge.m_center.y = -hsizeY + aCornerRadius;
1551     curr_edge.m_arc_angle_start = aOrient + 900.0;     // En decidegree
1552 
1553     rr_outline.push_back( curr_edge );
1554 
1555     // bottom right corner 3 (and 2 to 3 right vertical side @ x=hsizeX)
1556     curr_edge.m_start.x = hsizeX;
1557     curr_edge.m_start.y = -hsizeY + aCornerRadius;
1558     curr_edge.m_end.x =  curr_edge.m_start.x;
1559     curr_edge.m_end.y = hsizeY - aCornerRadius;
1560     curr_edge.m_center.x = hsizeX - aCornerRadius;
1561     curr_edge.m_center.y = curr_edge.m_end.y;
1562     curr_edge.m_arc_angle_start = aOrient + 0.0;     // En decidegree
1563 
1564     rr_outline.push_back( curr_edge );
1565 
1566     // bottom left corner 4 (and 3 to 4 bottom horizontal side @ y=hsizeY)
1567     curr_edge.m_start.x = hsizeX - aCornerRadius;
1568     curr_edge.m_start.y = hsizeY;
1569     curr_edge.m_end.x =  -hsizeX + aCornerRadius;
1570     curr_edge.m_end.y = curr_edge.m_start.y;
1571     curr_edge.m_center.x = curr_edge.m_end.x;
1572     curr_edge.m_center.y = hsizeY - aCornerRadius;
1573     curr_edge.m_arc_angle_start = aOrient - 900.0;     // En decidegree
1574 
1575     rr_outline.push_back( curr_edge );
1576 
1577     // Move relative coordinates to the actual location and rotation:
1578     wxPoint arc_last_center;
1579     int arc_last_angle = curr_edge.m_arc_angle_start+arc_angle;
1580 
1581     for( RR_EDGE& rr_edge: rr_outline )
1582     {
1583         RotatePoint( &rr_edge.m_start, aOrient );
1584         RotatePoint( &rr_edge.m_end, aOrient );
1585         RotatePoint( &rr_edge.m_center, aOrient );
1586         rr_edge.m_start += aRectCenter;
1587         rr_edge.m_end += aRectCenter;
1588         rr_edge.m_center += aRectCenter;
1589         arc_last_center = rr_edge.m_center;
1590     }
1591 
1592     // Ensure the region is a closed polygon, i.e. the end point of last segment
1593     // (end of arc) is the same as the first point. Rounding issues can create a
1594     // small difference, mainly for rotated pads.
1595     // calculate last point (end of last arc):
1596     wxPoint last_pt;
1597     last_pt.x = arc_last_center.x + KiROUND( cosdecideg( aCornerRadius, arc_last_angle ) );
1598     last_pt.y = arc_last_center.y - KiROUND( sindecideg( aCornerRadius, arc_last_angle ) );
1599 
1600     wxPoint first_pt = rr_outline[0].m_start;
1601 
1602 #if 0    // For test only:
1603     if( last_pt != first_pt )
1604         wxLogMessage( "first pt %d %d last pt %d %d",
1605                       first_pt.x, first_pt.y, last_pt.x, last_pt.y );
1606 #endif
1607 
1608     fputs( "G36*\n", m_outputFile );  // Start region
1609     fputs( "G01*\n", m_outputFile );  // Set linear interpolation.
1610     first_pt = last_pt;
1611     MoveTo( first_pt );             // Start point of region, must be same as end point
1612 
1613     for( RR_EDGE& rr_edge: rr_outline )
1614     {
1615         if( aCornerRadius )     // Guard: ensure we do not create arcs with radius = 0
1616         {
1617             // LineTo( rr_edge.m_end ); // made in plotArc()
1618             plotArc( rr_edge.m_center, rr_edge.m_arc_angle_start,
1619                      rr_edge.m_arc_angle_start+arc_angle, aCornerRadius, true );
1620         }
1621         else
1622         {
1623             LineTo( rr_edge.m_end );
1624         }
1625     }
1626 
1627     fputs( "G37*\n", m_outputFile );      // Close region
1628 }
1629 
1630 
FlashPadCustom(const wxPoint & aPadPos,const wxSize & aSize,double aOrient,SHAPE_POLY_SET * aPolygons,OUTLINE_MODE aTraceMode,void * aData)1631 void GERBER_PLOTTER::FlashPadCustom( const wxPoint& aPadPos, const wxSize& aSize,
1632                                      double aOrient, SHAPE_POLY_SET* aPolygons,
1633                                      OUTLINE_MODE aTraceMode, void* aData )
1634 
1635 {
1636     // A Pad custom is plotted as polygon (a region in Gerber language).
1637     GBR_METADATA gbr_metadata;
1638 
1639     if( aData )
1640         gbr_metadata = *static_cast<GBR_METADATA*>( aData );
1641 
1642     SHAPE_POLY_SET polyshape = *aPolygons;
1643 
1644     if( aTraceMode != FILLED )
1645     {
1646         SetCurrentLineWidth( USE_DEFAULT_LINE_WIDTH, &gbr_metadata );
1647         polyshape.Inflate( -GetCurrentLineWidth()/2, 16 );
1648     }
1649 
1650     std::vector< wxPoint > cornerList;
1651 
1652     for( int cnt = 0; cnt < polyshape.OutlineCount(); ++cnt )
1653     {
1654         SHAPE_LINE_CHAIN& poly = polyshape.Outline( cnt );
1655 
1656         cornerList.clear();
1657 
1658         for( int ii = 0; ii < poly.PointCount(); ++ii )
1659             cornerList.emplace_back( poly.CPoint( ii ).x, poly.CPoint( ii ).y );
1660 
1661         // Close polygon
1662         cornerList.push_back( cornerList[0] );
1663 
1664         if( aTraceMode == SKETCH )
1665         {
1666             PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), &gbr_metadata );
1667         }
1668         else
1669         {
1670 #ifdef GBR_USE_MACROS_FOR_CUSTOM_PAD
1671             if( m_gerberDisableApertMacros
1672                     || cornerList.size() > GBR_MACRO_FOR_CUSTOM_PAD_MAX_CORNER_COUNT )
1673                 PlotGerberRegion( cornerList, &gbr_metadata );
1674             else
1675             {
1676                // An AM will be created. the shape must be in position 0,0 and orientation 0
1677                 // to be able to reuse the same AM for pads having the same shape
1678                 for( size_t ii = 0; ii < cornerList.size(); ii++ )
1679                 {
1680                     cornerList[ii] -= aPadPos;
1681                     RotatePoint( &cornerList[ii], -aOrient );
1682                 }
1683 
1684                 DPOINT pos_dev = userToDeviceCoordinates( aPadPos );
1685                 selectAperture( cornerList, aOrient/10.0,
1686                                 APERTURE::AM_FREE_POLYGON, gbr_metadata.GetApertureAttrib() );
1687                 formatNetAttribute( &gbr_metadata.m_NetlistMetadata );
1688 
1689                 emitDcode( pos_dev, 3 );
1690             }
1691 #else
1692             PlotGerberRegion( cornerList, &gbr_metadata );
1693 #endif
1694         }
1695     }
1696 }
1697 
1698 
FlashPadChamferRoundRect(const wxPoint & aShapePos,const wxSize & aPadSize,int aCornerRadius,double aChamferRatio,int aChamferPositions,double aPadOrient,OUTLINE_MODE aPlotMode,void * aData)1699 void GERBER_PLOTTER::FlashPadChamferRoundRect( const wxPoint& aShapePos, const wxSize& aPadSize,
1700                                                int aCornerRadius, double aChamferRatio,
1701                                                int aChamferPositions, double aPadOrient,
1702                                                OUTLINE_MODE aPlotMode, void* aData )
1703 
1704 {
1705     GBR_METADATA gbr_metadata;
1706 
1707     if( aData )
1708         gbr_metadata = *static_cast<GBR_METADATA*>( aData );
1709 
1710     DPOINT pos_dev = userToDeviceCoordinates( aShapePos );
1711 
1712     SHAPE_POLY_SET outline;
1713     // polygon corners list
1714     std::vector<wxPoint> cornerList;
1715 
1716     bool hasRoundedCorner = aCornerRadius != 0 && aChamferPositions != 15;
1717 
1718 #ifdef GBR_USE_MACROS_FOR_CHAMFERED_RECT
1719     // Sketch mode or round rect shape or Apert Macros disabled
1720     if( aPlotMode != FILLED || hasRoundedCorner || m_gerberDisableApertMacros )
1721 #endif
1722     {
1723         TransformRoundChamferedRectToPolygon( outline, aShapePos, aPadSize, aPadOrient,
1724                                               aCornerRadius, aChamferRatio, aChamferPositions, 0,
1725                                               GetPlotterArcHighDef(), ERROR_INSIDE );
1726 
1727         // Build the corner list
1728         const SHAPE_LINE_CHAIN& corners = outline.Outline(0);
1729 
1730         for( int ii = 0; ii < corners.PointCount(); ii++ )
1731             cornerList.emplace_back( corners.CPoint( ii ).x, corners.CPoint( ii ).y );
1732 
1733         // Close the polygon
1734         cornerList.push_back( cornerList[0] );
1735 
1736         if( aPlotMode == SKETCH )
1737             PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), &gbr_metadata );
1738         else
1739         {
1740 #ifdef GBR_USE_MACROS_FOR_CHAMFERED_ROUND_RECT
1741             if( m_gerberDisableApertMacros )
1742             {
1743                 PlotGerberRegion( cornerList, &gbr_metadata );
1744             }
1745             else
1746             {
1747                // An AM will be created. the shape must be in position 0,0 and orientation 0
1748                 // to be able to reuse the same AM for pads having the same shape
1749                 for( size_t ii = 0; ii < cornerList.size(); ii++ )
1750                 {
1751                     cornerList[ii] -= aShapePos;
1752                     RotatePoint( &cornerList[ii], -aPadOrient );
1753                 }
1754 
1755                 selectAperture( cornerList, aPadOrient/10.0,
1756                                 APERTURE::AM_FREE_POLYGON, gbr_metadata.GetApertureAttrib() );
1757                 formatNetAttribute( &gbr_metadata.m_NetlistMetadata );
1758 
1759                 emitDcode( pos_dev, 3 );
1760             }
1761 #else
1762             PlotGerberRegion( cornerList, &gbr_metadata );
1763 #endif
1764         }
1765 
1766         return;
1767     }
1768 
1769     // Build the chamfered polygon (4 to 8 corners )
1770     TransformRoundChamferedRectToPolygon( outline, wxPoint( 0, 0 ), aPadSize, 0.0, 0,
1771                                           aChamferRatio, aChamferPositions, 0,
1772                                           GetPlotterArcHighDef(), ERROR_INSIDE );
1773 
1774     // Build the corner list
1775     const SHAPE_LINE_CHAIN& corners = outline.Outline(0);
1776 
1777     // Generate the polygon (4 to 8 corners )
1778     for( int ii = 0; ii < corners.PointCount(); ii++ )
1779         cornerList.emplace_back( corners.CPoint( ii ).x, corners.CPoint( ii ).y );
1780 
1781     switch( cornerList.size() )
1782     {
1783     case 4:
1784         m_hasApertureOutline4P = true;
1785         selectAperture( cornerList, aPadOrient/10.0,
1786                         APERTURE::APER_MACRO_OUTLINE4P, gbr_metadata.GetApertureAttrib() );
1787         break;
1788 
1789     case 5:
1790         m_hasApertureChamferedRect = true;
1791         selectAperture( cornerList, aPadOrient/10.0,
1792                         APERTURE::APER_MACRO_OUTLINE5P, gbr_metadata.GetApertureAttrib() );
1793         break;
1794 
1795     case 6:
1796         m_hasApertureChamferedRect = true;
1797         selectAperture( cornerList, aPadOrient/10.0,
1798                         APERTURE::APER_MACRO_OUTLINE6P, gbr_metadata.GetApertureAttrib() );
1799         break;
1800 
1801     case 7:
1802         m_hasApertureChamferedRect = true;
1803         selectAperture( cornerList, aPadOrient/10.0,
1804                         APERTURE::APER_MACRO_OUTLINE7P, gbr_metadata.GetApertureAttrib() );
1805         break;
1806 
1807     case 8:
1808         m_hasApertureChamferedRect = true;
1809         selectAperture( cornerList, aPadOrient/10.0,
1810                         APERTURE::APER_MACRO_OUTLINE8P, gbr_metadata.GetApertureAttrib() );
1811         break;
1812 
1813     default:
1814         wxLogMessage( "FlashPadChamferRoundRect(): Unexpected number of corners (%d)",
1815                       (int)cornerList.size() );
1816         break;
1817     }
1818 
1819     formatNetAttribute( &gbr_metadata.m_NetlistMetadata );
1820 
1821     emitDcode( pos_dev, 3 );
1822 }
1823 
1824 
FlashPadTrapez(const wxPoint & aPadPos,const wxPoint * aCorners,double aPadOrient,OUTLINE_MODE aTrace_Mode,void * aData)1825 void GERBER_PLOTTER::FlashPadTrapez( const wxPoint& aPadPos,  const wxPoint* aCorners,
1826                                      double aPadOrient, OUTLINE_MODE aTrace_Mode, void* aData )
1827 
1828 {
1829     // polygon corners list
1830     std::vector<wxPoint> cornerList = { aCorners[0], aCorners[1], aCorners[2], aCorners[3] };
1831 
1832     // Draw the polygon and fill the interior as required
1833     for( unsigned ii = 0; ii < 4; ii++ )
1834     {
1835         RotatePoint( &cornerList[ii], aPadOrient );
1836         cornerList[ii] += aPadPos;
1837     }
1838 
1839     // Close the polygon
1840     cornerList.push_back( cornerList[0] );
1841     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
1842 
1843     GBR_METADATA metadata;
1844 
1845     if( gbr_metadata )
1846         metadata = *gbr_metadata;
1847 
1848     if( aTrace_Mode == SKETCH )
1849     {
1850         PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), &metadata );
1851         return;
1852     }
1853 
1854     // Plot a filled polygon:
1855     #ifdef GBR_USE_MACROS_FOR_TRAPEZOID
1856     if( !m_gerberDisableApertMacros )
1857     #endif
1858     {
1859         m_hasApertureOutline4P = true;
1860         DPOINT pos_dev = userToDeviceCoordinates( aPadPos );
1861         // polygon corners list
1862         std::vector<wxPoint> corners = { aCorners[0], aCorners[1], aCorners[2], aCorners[3] };
1863         int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
1864         selectAperture( corners, aPadOrient/10.0, APERTURE::APER_MACRO_OUTLINE4P, aperture_attrib );
1865 
1866         if( gbr_metadata )
1867             formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1868 
1869         emitDcode( pos_dev, 3 );
1870         return;
1871     }
1872 
1873     PlotGerberRegion( cornerList, &metadata );
1874 }
1875 
1876 
FlashRegularPolygon(const wxPoint & aShapePos,int aDiameter,int aCornerCount,double aOrient,OUTLINE_MODE aTraceMode,void * aData)1877 void GERBER_PLOTTER::FlashRegularPolygon( const wxPoint& aShapePos, int aDiameter,
1878                                           int aCornerCount, double aOrient,
1879                                           OUTLINE_MODE aTraceMode, void* aData )
1880 {
1881     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
1882 
1883     GBR_METADATA metadata;
1884 
1885     if( gbr_metadata )
1886         metadata = *gbr_metadata;
1887 
1888     if( aTraceMode == SKETCH )
1889     {
1890         // Build the polygon:
1891         std::vector< wxPoint > cornerList;
1892 
1893         double angle_delta = 3600.0 / aCornerCount; // in 0.1 degree
1894 
1895         for( int ii = 0; ii < aCornerCount; ii++ )
1896         {
1897             double rot = aOrient + (angle_delta*ii);
1898             wxPoint vertice( aDiameter/2, 0 );
1899             RotatePoint( &vertice, rot );
1900             vertice += aShapePos;
1901             cornerList.push_back( vertice );
1902         }
1903 
1904         cornerList.push_back( cornerList[0] );  // Close the shape
1905 
1906         PlotPoly( cornerList, FILL_T::NO_FILL, GetCurrentLineWidth(), &gbr_metadata );
1907     }
1908     else
1909     {
1910         DPOINT pos_dev = userToDeviceCoordinates( aShapePos );
1911 
1912         int aperture_attrib = gbr_metadata ? gbr_metadata->GetApertureAttrib() : 0;
1913 
1914         APERTURE::APERTURE_TYPE apert_type =
1915                 (APERTURE::APERTURE_TYPE)(APERTURE::AT_REGULAR_POLY3 + aCornerCount - 3);
1916         selectAperture( aDiameter, aOrient, apert_type, aperture_attrib );
1917 
1918         if( gbr_metadata )
1919             formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1920 
1921         emitDcode( pos_dev, 3 );
1922     }
1923 }
1924 
1925 
Text(const wxPoint & aPos,const COLOR4D & aColor,const wxString & aText,double aOrient,const wxSize & aSize,enum EDA_TEXT_HJUSTIFY_T aH_justify,enum EDA_TEXT_VJUSTIFY_T aV_justify,int aWidth,bool aItalic,bool aBold,bool aMultilineAllowed,void * aData)1926 void GERBER_PLOTTER::Text( const wxPoint& aPos, const COLOR4D& aColor,
1927                            const wxString& aText, double aOrient, const wxSize& aSize,
1928                            enum EDA_TEXT_HJUSTIFY_T aH_justify,
1929                            enum EDA_TEXT_VJUSTIFY_T aV_justify, int aWidth, bool aItalic,
1930                            bool aBold, bool aMultilineAllowed, void* aData )
1931 {
1932     GBR_METADATA* gbr_metadata = static_cast<GBR_METADATA*>( aData );
1933 
1934     if( gbr_metadata )
1935         formatNetAttribute( &gbr_metadata->m_NetlistMetadata );
1936 
1937     PLOTTER::Text( aPos, aColor, aText, aOrient, aSize, aH_justify, aV_justify, aWidth, aItalic,
1938                    aBold, aMultilineAllowed, aData );
1939 }
1940 
1941 
SetLayerPolarity(bool aPositive)1942 void GERBER_PLOTTER::SetLayerPolarity( bool aPositive )
1943 {
1944     if( aPositive )
1945         fprintf( m_outputFile, "%%LPD*%%\n" );
1946     else
1947         fprintf( m_outputFile, "%%LPC*%%\n" );
1948 }
1949 
1950 
IsSamePoly(const std::vector<wxPoint> & aPolygon) const1951 bool APER_MACRO_FREEPOLY::IsSamePoly( const std::vector<wxPoint>& aPolygon ) const
1952 {
1953     return polyCompare( m_Corners, aPolygon );
1954 }
1955 
1956 
Format(FILE * aOutput,double aIu2GbrMacroUnit)1957 void APER_MACRO_FREEPOLY::Format( FILE * aOutput, double aIu2GbrMacroUnit )
1958 {
1959    // Write aperture header
1960     fprintf( aOutput, "%%AM%s%d*\n", AM_FREEPOLY_BASENAME, m_Id );
1961     fprintf( aOutput, "4,1,%d,", (int)m_Corners.size() );
1962 
1963     // Insert a newline after curr_line_count_max coordinates.
1964     int curr_line_corner_count = 0;
1965     const int curr_line_count_max = 20;     // <= 0 to disable newlines
1966 
1967     for( size_t ii = 0; ii <= m_Corners.size(); ii++ )
1968     {
1969         int jj = ii;
1970 
1971         if( ii >= m_Corners.size() )
1972             jj = 0;
1973 
1974         // Note: parameter values are always mm or inches
1975         fprintf( aOutput, "%#f,%#f,",
1976                  m_Corners[jj].x * aIu2GbrMacroUnit, -m_Corners[jj].y * aIu2GbrMacroUnit );
1977 
1978         if( curr_line_count_max >= 0 && ++curr_line_corner_count >= curr_line_count_max )
1979         {
1980             fprintf( aOutput, "\n" );
1981             curr_line_corner_count = 0;
1982         }
1983     }
1984 
1985     // output rotation parameter
1986     fputs( "$1*%\n", aOutput );
1987 }
1988 
1989 
Format(FILE * aOutput,double aIu2GbrMacroUnit)1990 void APER_MACRO_FREEPOLY_LIST::Format( FILE * aOutput, double aIu2GbrMacroUnit )
1991 {
1992     for( int idx = 0; idx < AmCount(); idx++ )
1993         m_AMList[idx].Format( aOutput, aIu2GbrMacroUnit );
1994 }
1995 
1996 
Append(const std::vector<wxPoint> & aPolygon)1997 void APER_MACRO_FREEPOLY_LIST::Append( const std::vector<wxPoint>& aPolygon )
1998 {
1999     m_AMList.emplace_back( aPolygon, AmCount() );
2000 }
2001 
2002 
FindAm(const std::vector<wxPoint> & aPolygon) const2003 int APER_MACRO_FREEPOLY_LIST::FindAm( const std::vector<wxPoint>& aPolygon ) const
2004 {
2005     for( int idx = 0; idx < AmCount(); idx++ )
2006     {
2007         if( m_AMList[idx].IsSamePoly( aPolygon ) )
2008             return idx;
2009     }
2010 
2011     return -1;
2012 }
2013