1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2007-2014 Jean-Pierre Charras  jp.charras at wanadoo.fr
5  * Copyright (C) 1992-2021 KiCad Developers, see change_log.txt for contributors.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, you may find one here:
19  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20  * or you may search the http://www.gnu.org website for the version 2 license,
21  * or you may write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
23  */
24 
25 #include <vector>
26 
27 #include <export_to_pcbnew.h>
28 
29 #include <confirm.h>
30 #include <string_utils.h>
31 #include <locale_io.h>
32 #include <macros.h>
33 #include <trigo.h>
34 #include <gerbview_frame.h>
35 #include <gerber_file_image.h>
36 #include <gerber_file_image_list.h>
37 #include <build_version.h>
38 #include <wildcards_and_files_ext.h>
39 #include "excellon_image.h"
40 
41 // Imported function
42 extern const wxString GetPCBDefaultLayerName( LAYER_NUM aLayerNumber );
43 
44 
GBR_TO_PCB_EXPORTER(GERBVIEW_FRAME * aFrame,const wxString & aFileName)45 GBR_TO_PCB_EXPORTER::GBR_TO_PCB_EXPORTER( GERBVIEW_FRAME* aFrame, const wxString& aFileName )
46 {
47     m_gerbview_frame    = aFrame;
48     m_pcb_file_name     = aFileName;
49     m_fp                = nullptr;
50     m_pcbCopperLayersCount = 2;
51 }
52 
53 
~GBR_TO_PCB_EXPORTER()54 GBR_TO_PCB_EXPORTER::~GBR_TO_PCB_EXPORTER()
55 {
56 }
57 
58 
ExportPcb(const LAYER_NUM * aLayerLookUpTable,int aCopperLayers)59 bool GBR_TO_PCB_EXPORTER::ExportPcb( const LAYER_NUM* aLayerLookUpTable, int aCopperLayers )
60 {
61     LOCALE_IO   toggle;     // toggles on, then off, the C locale.
62 
63     m_fp = wxFopen( m_pcb_file_name, wxT( "wt" ) );
64 
65     if( m_fp == nullptr )
66     {
67         wxString msg;
68         msg.Printf( _( "Failed to create file '%s'." ), m_pcb_file_name );
69         DisplayError( m_gerbview_frame, msg );
70         return false;
71     }
72 
73     m_pcbCopperLayersCount = aCopperLayers;
74 
75     writePcbHeader( aLayerLookUpTable );
76 
77     // create an image of gerber data
78     const int pcbCopperLayerMax = 31;
79     GERBER_FILE_IMAGE_LIST* images = m_gerbview_frame->GetGerberLayout()->GetImagesList();
80 
81     // First collect all the holes.  We'll use these to generate pads, vias, etc.
82     for( unsigned layer = 0; layer < images->ImagesMaxCount(); ++layer )
83     {
84         EXCELLON_IMAGE* excellon = dynamic_cast<EXCELLON_IMAGE*>( images->GetGbrImage( layer ) );
85 
86         if( excellon == nullptr )    // Layer not yet used or not a drill image
87             continue;
88 
89         for(  GERBER_DRAW_ITEM* gerb_item : excellon->GetItems() )
90             collect_hole( gerb_item );
91     }
92 
93     // Next: non copper layers:
94     for( unsigned layer = 0; layer < images->ImagesMaxCount(); ++layer )
95     {
96         GERBER_FILE_IMAGE* gerber = images->GetGbrImage( layer );
97 
98         if( gerber == nullptr )    // Graphic layer not yet used
99             continue;
100 
101         LAYER_NUM pcb_layer_number = aLayerLookUpTable[layer];
102 
103         if( !IsPcbLayer( pcb_layer_number ) )
104             continue;
105 
106         if( pcb_layer_number <= pcbCopperLayerMax ) // copper layer
107             continue;
108 
109         for(  GERBER_DRAW_ITEM* gerb_item : gerber->GetItems() )
110             export_non_copper_item( gerb_item, pcb_layer_number );
111     }
112 
113     // Copper layers
114     for( unsigned layer = 0; layer < images->ImagesMaxCount(); ++layer )
115     {
116         GERBER_FILE_IMAGE* gerber = images->GetGbrImage( layer );
117 
118         if( gerber == nullptr )    // Graphic layer not yet used
119             continue;
120 
121         LAYER_NUM pcb_layer_number = aLayerLookUpTable[layer];
122 
123         if( pcb_layer_number < 0 || pcb_layer_number > pcbCopperLayerMax )
124             continue;
125 
126         for( GERBER_DRAW_ITEM* gerb_item : gerber->GetItems() )
127             export_copper_item( gerb_item, pcb_layer_number );
128     }
129 
130     // Now write out the holes we collected earlier as vias
131     for( const EXPORT_VIA& via : m_vias )
132         export_via( via );
133 
134     fprintf( m_fp, ")\n" );
135 
136     fclose( m_fp );
137     m_fp = nullptr;
138     return true;
139 }
140 
141 
export_non_copper_item(const GERBER_DRAW_ITEM * aGbrItem,LAYER_NUM aLayer)142 void GBR_TO_PCB_EXPORTER::export_non_copper_item( const GERBER_DRAW_ITEM* aGbrItem,
143                                                   LAYER_NUM aLayer )
144 {
145     // used when a D_CODE is not found. default D_CODE to draw a flashed item
146     static D_CODE  dummyD_CODE( 0 );
147 
148     wxPoint        seg_start   = aGbrItem->m_Start;
149     wxPoint        seg_end     = aGbrItem->m_End;
150     D_CODE*        d_codeDescr = aGbrItem->GetDcodeDescr();
151     SHAPE_POLY_SET polygon;
152 
153     if( d_codeDescr == nullptr )
154         d_codeDescr = &dummyD_CODE;
155 
156     switch( aGbrItem->m_Shape )
157     {
158     case GBR_POLYGON:
159         writePcbPolygon( aGbrItem->m_Polygon, aLayer );
160         break;
161 
162     case GBR_SPOT_CIRCLE:
163     {
164         VECTOR2I center = aGbrItem->GetABPosition( seg_start );
165         int radius = d_codeDescr->m_Size.x / 2;
166         writePcbFilledCircle( center, radius, aLayer );
167     }
168         break;
169 
170     case GBR_SPOT_RECT:
171     case GBR_SPOT_OVAL:
172     case GBR_SPOT_POLY:
173     case GBR_SPOT_MACRO:
174         d_codeDescr->ConvertShapeToPolygon();
175         writePcbPolygon( d_codeDescr->m_Polygon, aLayer, aGbrItem->GetABPosition( seg_start ) );
176         break;
177 
178     case GBR_ARC:
179     {
180         double a = atan2( (double) ( aGbrItem->m_Start.y - aGbrItem->m_ArcCentre.y ),
181                           (double) ( aGbrItem->m_Start.x - aGbrItem->m_ArcCentre.x ) );
182         double b = atan2( (double) ( aGbrItem->m_End.y - aGbrItem->m_ArcCentre.y ),
183                           (double) ( aGbrItem->m_End.x - aGbrItem->m_ArcCentre.x ) );
184 
185         double angle = RAD2DEG(b - a);
186         seg_start = aGbrItem->m_ArcCentre;
187 
188         // Ensure arc orientation is CCW
189         if( angle < 0 )
190             angle += 360.0;
191 
192         // Reverse Y axis:
193         seg_start.y = -seg_start.y;
194         seg_end.y = -seg_end.y;
195 
196         if( angle == 360.0 ||  angle == 0 )
197         {
198             fprintf( m_fp, "(gr_circle (center %s %s) (end %s %s) (layer %s) (width %s))\n",
199                      Double2Str( MapToPcbUnits(seg_start.x) ).c_str(),
200                      Double2Str( MapToPcbUnits(seg_start.y) ).c_str(),
201                      Double2Str( MapToPcbUnits(seg_end.x) ).c_str(),
202                      Double2Str( MapToPcbUnits(seg_end.y) ).c_str(),
203                      TO_UTF8( GetPCBDefaultLayerName( aLayer ) ),
204                      Double2Str( MapToPcbUnits( aGbrItem->m_Size.x ) ).c_str()
205                      );
206         }
207         else
208         {
209             fprintf( m_fp, "(gr_arc (start %s %s) (end %s %s) (angle %s) (layer %s) (width %s))\n",
210                      Double2Str( MapToPcbUnits(seg_start.x) ).c_str(),
211                      Double2Str( MapToPcbUnits(seg_start.y) ).c_str(),
212                      Double2Str( MapToPcbUnits(seg_end.x) ).c_str(),
213                      Double2Str( MapToPcbUnits(seg_end.y) ).c_str(),
214                      Double2Str( angle ).c_str(),
215                      TO_UTF8( GetPCBDefaultLayerName( aLayer ) ),
216                      Double2Str( MapToPcbUnits( aGbrItem->m_Size.x ) ).c_str()
217                      );
218         }
219     }
220         break;
221 
222     case GBR_CIRCLE:
223         // Reverse Y axis:
224         seg_start.y = -seg_start.y;
225         seg_end.y = -seg_end.y;
226 
227         fprintf( m_fp, "(gr_circle (start %s %s) (end %s %s) (layer %s) (width %s))\n",
228                  Double2Str( MapToPcbUnits( seg_start.x ) ).c_str(),
229                  Double2Str( MapToPcbUnits( seg_start.y ) ).c_str(),
230                  Double2Str( MapToPcbUnits( seg_end.x ) ).c_str(),
231                  Double2Str( MapToPcbUnits( seg_end.y ) ).c_str(),
232                  TO_UTF8( GetPCBDefaultLayerName( aLayer ) ),
233                  Double2Str( MapToPcbUnits( aGbrItem->m_Size.x ) ).c_str() );
234         break;
235 
236     case GBR_SEGMENT:
237         // Reverse Y axis:
238         seg_start.y = -seg_start.y;
239         seg_end.y = -seg_end.y;
240 
241         fprintf( m_fp, "(gr_line (start %s %s) (end %s %s) (layer %s) (width %s))\n",
242                  Double2Str( MapToPcbUnits( seg_start.x ) ).c_str(),
243                  Double2Str( MapToPcbUnits( seg_start.y ) ).c_str(),
244                  Double2Str( MapToPcbUnits( seg_end.x ) ).c_str(),
245                  Double2Str( MapToPcbUnits( seg_end.y ) ).c_str(),
246                  TO_UTF8( GetPCBDefaultLayerName( aLayer ) ),
247                  Double2Str( MapToPcbUnits( aGbrItem->m_Size.x ) ).c_str() );
248         break;
249     }
250 }
251 
252 
collect_hole(const GERBER_DRAW_ITEM * aGbrItem)253 void GBR_TO_PCB_EXPORTER::collect_hole( const GERBER_DRAW_ITEM* aGbrItem )
254 {
255     int size = std::min( aGbrItem->m_Size.x, aGbrItem->m_Size.y );
256     m_vias.emplace_back( aGbrItem->m_Start, size + 1, size );
257 }
258 
259 
export_via(const EXPORT_VIA & aVia)260 void GBR_TO_PCB_EXPORTER::export_via( const EXPORT_VIA& aVia )
261 {
262     wxPoint via_pos = aVia.m_Pos;
263 
264     // Reverse Y axis:
265     via_pos.y = -via_pos.y;
266 
267     // Layers are Front to Back
268     fprintf( m_fp, " (via (at %s %s) (size %s) (drill %s)",
269                   Double2Str( MapToPcbUnits( via_pos.x ) ).c_str(),
270                   Double2Str( MapToPcbUnits( via_pos.y ) ).c_str(),
271                   Double2Str( MapToPcbUnits( aVia.m_Size ) ).c_str(),
272                   Double2Str( MapToPcbUnits( aVia.m_Drill ) ).c_str() );
273 
274     fprintf( m_fp, " (layers %s %s))\n",
275                   TO_UTF8( GetPCBDefaultLayerName( F_Cu ) ),
276                   TO_UTF8( GetPCBDefaultLayerName( B_Cu ) ) );
277 }
278 
279 
export_copper_item(const GERBER_DRAW_ITEM * aGbrItem,LAYER_NUM aLayer)280 void GBR_TO_PCB_EXPORTER::export_copper_item( const GERBER_DRAW_ITEM* aGbrItem, LAYER_NUM aLayer )
281 {
282     switch( aGbrItem->m_Shape )
283     {
284     case GBR_SPOT_CIRCLE:
285     case GBR_SPOT_RECT:
286     case GBR_SPOT_OVAL:
287         export_flashed_copper_item( aGbrItem, aLayer );
288         break;
289 
290     case GBR_ARC:
291         export_segarc_copper_item( aGbrItem, aLayer );
292         break;
293 
294     case GBR_POLYGON:
295         // One can use a polygon or a zone to output a Gerber region.
296         // none are perfect.
297         // The current way is use a polygon, as the zone export
298         // is experimental and only for tests.
299 #if 1
300         writePcbPolygon( aGbrItem->m_Polygon, aLayer );
301 #else
302         // Only for tests:
303         writePcbZoneItem( aGbrItem, aLayer );
304 #endif
305         break;
306 
307     default:
308         export_segline_copper_item( aGbrItem, aLayer );
309         break;
310     }
311 }
312 
313 
export_segline_copper_item(const GERBER_DRAW_ITEM * aGbrItem,LAYER_NUM aLayer)314 void GBR_TO_PCB_EXPORTER::export_segline_copper_item( const GERBER_DRAW_ITEM* aGbrItem,
315                                                       LAYER_NUM aLayer )
316 {
317     wxPoint seg_start, seg_end;
318 
319     seg_start   = aGbrItem->m_Start;
320     seg_end     = aGbrItem->m_End;
321 
322     // Reverse Y axis:
323     seg_start.y = -seg_start.y;
324     seg_end.y = -seg_end.y;
325 
326     writeCopperLineItem( seg_start, seg_end, aGbrItem->m_Size.x, aLayer );
327 }
328 
329 
writeCopperLineItem(const wxPoint & aStart,const wxPoint & aEnd,int aWidth,LAYER_NUM aLayer)330 void GBR_TO_PCB_EXPORTER::writeCopperLineItem( const wxPoint& aStart,
331                                                const wxPoint& aEnd,
332                                                int aWidth, LAYER_NUM aLayer )
333 {
334   fprintf( m_fp, "(segment (start %s %s) (end %s %s) (width %s) (layer %s) (net 0))\n",
335                   Double2Str( MapToPcbUnits(aStart.x) ).c_str(),
336                   Double2Str( MapToPcbUnits(aStart.y) ).c_str(),
337                   Double2Str( MapToPcbUnits(aEnd.x) ).c_str(),
338                   Double2Str( MapToPcbUnits(aEnd.y) ).c_str(),
339                   Double2Str( MapToPcbUnits( aWidth ) ).c_str(),
340                   TO_UTF8( GetPCBDefaultLayerName( aLayer ) ) );
341 }
342 
343 
export_segarc_copper_item(const GERBER_DRAW_ITEM * aGbrItem,LAYER_NUM aLayer)344 void GBR_TO_PCB_EXPORTER::export_segarc_copper_item( const GERBER_DRAW_ITEM* aGbrItem,
345                                                      LAYER_NUM aLayer )
346 {
347     double  a = atan2( (double) ( aGbrItem->m_Start.y - aGbrItem->m_ArcCentre.y ),
348                        (double) ( aGbrItem->m_Start.x - aGbrItem->m_ArcCentre.x ) );
349     double  b = atan2( (double) ( aGbrItem->m_End.y - aGbrItem->m_ArcCentre.y ),
350                        (double) ( aGbrItem->m_End.x - aGbrItem->m_ArcCentre.x ) );
351 
352     wxPoint start   = aGbrItem->m_Start;
353     wxPoint end     = aGbrItem->m_End;
354 
355     /* Because Pcbnew does not know arcs in tracks,
356      * approximate arc by segments (SEG_COUNT__CIRCLE segment per 360 deg)
357      * The arc is drawn anticlockwise from the start point to the end point.
358      */
359     #define SEG_COUNT_CIRCLE    16
360     #define DELTA_ANGLE         2 * M_PI / SEG_COUNT_CIRCLE
361 
362     // calculate the number of segments from a to b.
363     // we want CNT_PER_360 segments fo a circle
364     if( a > b )
365         b += 2 * M_PI;
366 
367     wxPoint curr_start = start;
368     wxPoint seg_start, seg_end;
369 
370     int     ii = 1;
371 
372     for( double rot = a; rot < (b - DELTA_ANGLE); rot += DELTA_ANGLE, ii++ )
373     {
374         seg_start = curr_start;
375         wxPoint curr_end = start;
376         RotatePoint( &curr_end, aGbrItem->m_ArcCentre,
377                      -RAD2DECIDEG( DELTA_ANGLE * ii ) );
378         seg_end = curr_end;
379 
380         // Reverse Y axis:
381         seg_start.y = -seg_start.y;
382         seg_end.y = -seg_end.y;
383         writeCopperLineItem( seg_start, seg_end, aGbrItem->m_Size.x, aLayer );
384         curr_start = curr_end;
385     }
386 
387     if( end != curr_start )
388     {
389         seg_start   = curr_start;
390         seg_end     = end;
391 
392         // Reverse Y axis:
393         seg_start.y = -seg_start.y;
394         seg_end.y = -seg_end.y;
395         writeCopperLineItem( seg_start, seg_end, aGbrItem->m_Size.x, aLayer );
396     }
397 }
398 
399 
export_flashed_copper_item(const GERBER_DRAW_ITEM * aGbrItem,LAYER_NUM aLayer)400 void GBR_TO_PCB_EXPORTER::export_flashed_copper_item( const GERBER_DRAW_ITEM* aGbrItem,
401                                                       LAYER_NUM aLayer )
402 {
403     static D_CODE  flashed_item_D_CODE( 0 );
404 
405     D_CODE*        d_codeDescr = aGbrItem->GetDcodeDescr();
406     SHAPE_POLY_SET polygon;
407 
408     if( d_codeDescr == nullptr )
409         d_codeDescr = &flashed_item_D_CODE;
410 
411     if( aGbrItem->m_Shape == GBR_SPOT_CIRCLE )
412     {
413         // See if there's a via that we can enlarge to fit this flashed item
414         for( EXPORT_VIA& via : m_vias )
415         {
416             if( via.m_Pos == aGbrItem->m_Start )
417             {
418                 via.m_Size = std::max( via.m_Size, aGbrItem->m_Size.x );
419                 return;
420             }
421         }
422     }
423 
424     wxPoint offset = aGbrItem->GetABPosition( aGbrItem->m_Start );
425 
426     if( aGbrItem->m_Shape == GBR_SPOT_CIRCLE ) // export it as filled circle
427     {
428         VECTOR2I center = offset;
429         int radius = d_codeDescr->m_Size.x / 2;
430         writePcbFilledCircle( center, radius, aLayer );
431         return;
432     }
433 
434     d_codeDescr->ConvertShapeToPolygon();
435     writePcbPolygon( d_codeDescr->m_Polygon, aLayer, offset );
436 }
437 
438 
writePcbFilledCircle(const VECTOR2I & aCenterPosition,int aRadius,LAYER_NUM aLayer)439 void GBR_TO_PCB_EXPORTER::writePcbFilledCircle( const VECTOR2I& aCenterPosition, int aRadius,
440                                                 LAYER_NUM aLayer )
441 {
442 
443     fprintf( m_fp, "(gr_circle (center %s %s) (end %s %s)",
444              Double2Str( MapToPcbUnits( aCenterPosition.x ) ).c_str(),
445              Double2Str( MapToPcbUnits( aCenterPosition.y ) ).c_str(),
446              Double2Str( MapToPcbUnits( aCenterPosition.x + aRadius ) ).c_str(),
447              Double2Str( MapToPcbUnits( aCenterPosition.y ) ).c_str() );
448 
449 
450     fprintf( m_fp, "(layer %s) (width 0) (fill solid) )\n",
451              TO_UTF8( GetPCBDefaultLayerName( aLayer ) ) );
452 }
453 
454 
writePcbHeader(const LAYER_NUM * aLayerLookUpTable)455 void GBR_TO_PCB_EXPORTER::writePcbHeader( const LAYER_NUM* aLayerLookUpTable )
456 {
457     fprintf( m_fp, "(kicad_pcb (version 4) (generator gerbview)\n\n" );
458 
459     // Write layers section
460     fprintf( m_fp, "  (layers \n" );
461 
462     for( int ii = 0; ii < m_pcbCopperLayersCount; ii++ )
463     {
464         int id = ii;
465 
466         if( ii == m_pcbCopperLayersCount-1)
467             id = B_Cu;
468 
469         fprintf( m_fp, "    (%d %s signal)\n", id, TO_UTF8( GetPCBDefaultLayerName( id ) ) );
470     }
471 
472     for( int ii = B_Adhes; ii < PCB_LAYER_ID_COUNT; ii++ )
473     {
474         if( GetPCBDefaultLayerName( ii ).IsEmpty() )    // Layer not available for export
475             continue;
476 
477         fprintf( m_fp, "    (%d %s user)\n", ii, TO_UTF8( GetPCBDefaultLayerName( ii ) ) );
478     }
479 
480     fprintf( m_fp, "  )\n\n" );
481 }
482 
483 
writePcbPolygon(const SHAPE_POLY_SET & aPolys,LAYER_NUM aLayer,const wxPoint & aOffset)484 void GBR_TO_PCB_EXPORTER::writePcbPolygon( const SHAPE_POLY_SET& aPolys, LAYER_NUM aLayer,
485                                            const wxPoint& aOffset )
486 {
487     SHAPE_POLY_SET polys = aPolys;
488 
489     // Cleanup the polygon
490     polys.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
491 
492     // Ensure the polygon is valid:
493     if( polys.OutlineCount() == 0 )
494         return;
495 
496     polys.Fracture( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
497 
498     SHAPE_LINE_CHAIN& poly = polys.Outline( 0 );
499 
500     fprintf( m_fp, "(gr_poly (pts " );
501 
502     #define MAX_COORD_CNT 4
503     int jj = MAX_COORD_CNT;
504     int cnt_max = poly.PointCount() -1;
505 
506     // Do not generate last corner, if it is the same point as the first point:
507     if( poly.CPoint( 0 ) == poly.CPoint( cnt_max ) )
508         cnt_max--;
509 
510     for( int ii = 0; ii <= cnt_max; ii++ )
511     {
512         if( --jj == 0 )
513         {
514             jj = MAX_COORD_CNT;
515             fprintf( m_fp, "\n" );
516         }
517 
518         fprintf( m_fp, " (xy %s %s)",
519                  Double2Str( MapToPcbUnits( poly.CPoint( ii ).x + aOffset.x ) ).c_str(),
520                  Double2Str( MapToPcbUnits( -poly.CPoint( ii ).y + aOffset.y ) ).c_str() );
521     }
522 
523     fprintf( m_fp, ")" );
524 
525     if( jj != MAX_COORD_CNT )
526         fprintf( m_fp, "\n" );
527 
528     fprintf( m_fp, "(layer %s) (width 0) )\n", TO_UTF8( GetPCBDefaultLayerName( aLayer ) ) );
529 }
530 
531 
writePcbZoneItem(const GERBER_DRAW_ITEM * aGbrItem,LAYER_NUM aLayer)532 void GBR_TO_PCB_EXPORTER::writePcbZoneItem( const GERBER_DRAW_ITEM* aGbrItem, LAYER_NUM aLayer )
533 {
534     SHAPE_POLY_SET polys = aGbrItem->m_Polygon;
535     polys.Simplify( SHAPE_POLY_SET::PM_STRICTLY_SIMPLE );
536 
537     if( polys.OutlineCount() == 0 )
538         return;
539 
540     fprintf( m_fp, "(zone (net 0) (net_name \"\") (layer %s) (tstamp 0000000) (hatch edge 0.508)\n",
541              TO_UTF8( GetPCBDefaultLayerName( aLayer ) ) );
542 
543     fprintf( m_fp, "  (connect_pads (clearance 0.0))\n" );
544 
545     fprintf( m_fp, "  (min_thickness 0.1) (filled_areas_thickness no)\n"
546                    "  (fill (thermal_gap 0.3) (thermal_bridge_width 0.3))\n" );
547 
548     // Now, write the zone outlines with holes.
549     // first polygon is the main outline, next are holes
550     // One cannot know the initial zone outline.
551     // However most of (if not all) holes are just items with clearance,
552     // not really a hole in the initial zone outline.
553     // So we build a zone outline only with no hole.
554     fprintf( m_fp, "  (polygon\n    (pts" );
555 
556     SHAPE_LINE_CHAIN& poly = polys.Outline( 0 );
557 
558     #define MAX_COORD_CNT 4
559     int jj = MAX_COORD_CNT;
560     int cnt_max = poly.PointCount() -1;
561 
562     // Do not generate last corner, if it is the same point as the first point:
563     if( poly.CPoint( 0 ) == poly.CPoint( cnt_max ) )
564         cnt_max--;
565 
566     for( int ii = 0; ii <= cnt_max; ii++ )
567     {
568         if( --jj == 0 )
569         {
570             jj = MAX_COORD_CNT;
571             fprintf( m_fp, "\n   " );
572         }
573 
574         fprintf( m_fp, " (xy %s %s)", Double2Str( MapToPcbUnits( poly.CPoint( ii ).x ) ).c_str(),
575                  Double2Str( MapToPcbUnits( -poly.CPoint( ii ).y ) ).c_str() );
576     }
577 
578     fprintf( m_fp, ")\n" );
579 
580     fprintf( m_fp, "  )\n)\n" );
581 }
582