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