1 /**
2 * @file export_idf.cpp
3 */
4
5 /*
6 * This program source code file is part of KiCad, a free EDA CAD application.
7 *
8 * Copyright (C) 2013 Cirilo Bernardo
9 * Copyright (C) 2021 KiCad Developers, see AUTHORS.txt for contributors.
10 *
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU General Public License
13 * as published by the Free Software Foundation; either version 2
14 * of the License, or (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, you may find one here:
23 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
24 * or you may search the http://www.gnu.org website for the version 2 license,
25 * or you may write to the Free Software Foundation, Inc.,
26 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
27 */
28
29
30 #include <list>
31 #include <locale_io.h>
32 #include <macros.h>
33 #include <pcb_edit_frame.h>
34 #include <board.h>
35 #include <board_design_settings.h>
36 #include <footprint.h>
37 #include <fp_shape.h>
38 #include <idf_parser.h>
39 #include <pad.h>
40 #include <build_version.h>
41 #include <wx/msgdlg.h>
42 #include "project.h"
43 #include "kiway.h"
44 #include "3d_cache/3d_cache.h"
45 #include "filename_resolver.h"
46
47 #ifndef PCBNEW
48 #define PCBNEW // needed to define the right value of Millimeter2iu(x)
49 #endif
50 #include <convert_to_biu.h> // to define Millimeter2iu(x)
51
52
53 // assumed default graphical line thickness: == 0.1mm
54 #define LINE_WIDTH (Millimeter2iu( 0.1 ))
55
56
57 static FILENAME_RESOLVER* resolver;
58
59
60 /**
61 * Retrieve line segment information from the edge layer and compiles the data into a form
62 * which can be output as an IDFv3 compliant #BOARD_OUTLINE section.
63 */
idf_export_outline(BOARD * aPcb,IDF3_BOARD & aIDFBoard)64 static void idf_export_outline( BOARD* aPcb, IDF3_BOARD& aIDFBoard )
65 {
66 double scale = aIDFBoard.GetUserScale();
67
68 PCB_SHAPE* graphic; // KiCad graphical item
69 IDF_POINT sp, ep; // start and end points from KiCad item
70
71 std::list< IDF_SEGMENT* > lines; // IDF intermediate form of KiCad graphical item
72 IDF_OUTLINE* outline = nullptr; // graphical items forming an outline or cutout
73
74 // NOTE: IMPLEMENTATION
75 // If/when component cutouts are allowed, we must implement them separately. Cutouts
76 // must be added to the board outline section and not to the Other Outline section.
77 // The footprint cutouts should be handled via the idf_export_footprint() routine.
78
79 double offX, offY;
80 aIDFBoard.GetUserOffset( offX, offY );
81
82 // Retrieve segments and arcs from the board
83 for( BOARD_ITEM* item : aPcb->Drawings() )
84 {
85 if( item->Type() != PCB_SHAPE_T || item->GetLayer() != Edge_Cuts )
86 continue;
87
88 graphic = (PCB_SHAPE*) item;
89
90 switch( graphic->GetShape() )
91 {
92 case SHAPE_T::SEGMENT:
93 {
94 if( graphic->GetStart() == graphic->GetEnd() )
95 break;
96
97 sp.x = graphic->GetStart().x * scale + offX;
98 sp.y = -graphic->GetStart().y * scale + offY;
99 ep.x = graphic->GetEnd().x * scale + offX;
100 ep.y = -graphic->GetEnd().y * scale + offY;
101 IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep );
102
103 if( seg )
104 lines.push_back( seg );
105
106 break;
107 }
108
109 case SHAPE_T::RECT:
110 {
111 if( graphic->GetStart() == graphic->GetEnd() )
112 break;
113
114 double top = graphic->GetStart().y * scale + offY;
115 double left = graphic->GetStart().x * scale + offX;
116 double bottom = graphic->GetEnd().y * scale + offY;
117 double right = graphic->GetEnd().x * scale + offX;
118
119 IDF_POINT corners[4];
120 corners[0] = IDF_POINT( left, top );
121 corners[1] = IDF_POINT( right, top );
122 corners[2] = IDF_POINT( right, bottom );
123 corners[3] = IDF_POINT( left, bottom );
124
125 lines.push_back( new IDF_SEGMENT( corners[0], corners[1] ) );
126 lines.push_back( new IDF_SEGMENT( corners[1], corners[2] ) );
127 lines.push_back( new IDF_SEGMENT( corners[2], corners[3] ) );
128 lines.push_back( new IDF_SEGMENT( corners[3], corners[0] ) );
129 break;
130 }
131
132 case SHAPE_T::ARC:
133 {
134 if( graphic->GetCenter() == graphic->GetStart() )
135 break;
136
137 sp.x = graphic->GetCenter().x * scale + offX;
138 sp.y = -graphic->GetCenter().y * scale + offY;
139 ep.x = graphic->GetStart().x * scale + offX;
140 ep.y = -graphic->GetStart().y * scale + offY;
141 IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, -graphic->GetArcAngle() / 10.0, true );
142
143 if( seg )
144 lines.push_back( seg );
145
146 break;
147 }
148
149 case SHAPE_T::CIRCLE:
150 {
151 if( graphic->GetRadius() == 0 )
152 break;
153
154 sp.x = graphic->GetCenter().x * scale + offX;
155 sp.y = -graphic->GetCenter().y * scale + offY;
156 ep.x = sp.x - graphic->GetRadius() * scale;
157 ep.y = sp.y;
158
159 // Circles must always have an angle of +360 deg. to appease
160 // quirky MCAD implementations of IDF.
161 IDF_SEGMENT* seg = new IDF_SEGMENT( sp, ep, 360.0, true );
162
163 if( seg )
164 lines.push_back( seg );
165
166 break;
167 }
168
169 default:
170 break;
171 }
172 }
173
174 // if there is no outline then use the bounding box
175 if( lines.empty() )
176 {
177 goto UseBoundingBox;
178 }
179
180 // get the board outline and write it out
181 // note: we do not use a try/catch block here since we intend
182 // to simply ignore unclosed loops and continue processing
183 // until we're out of segments to process
184 outline = new IDF_OUTLINE;
185 IDF3::GetOutline( lines, *outline );
186
187 if( outline->empty() )
188 goto UseBoundingBox;
189
190 aIDFBoard.AddBoardOutline( outline );
191 outline = nullptr;
192
193 // get all cutouts and write them out
194 while( !lines.empty() )
195 {
196 if( !outline )
197 outline = new IDF_OUTLINE;
198
199 IDF3::GetOutline( lines, *outline );
200
201 if( outline->empty() )
202 {
203 outline->Clear();
204 continue;
205 }
206
207 aIDFBoard.AddBoardOutline( outline );
208 outline = nullptr;
209 }
210
211 return;
212
213 UseBoundingBox:
214
215 // clean up if necessary
216 while( !lines.empty() )
217 {
218 delete lines.front();
219 lines.pop_front();
220 }
221
222 if( outline )
223 outline->Clear();
224 else
225 outline = new IDF_OUTLINE;
226
227 // Fetch a rectangular bounding box for the board; there is always some uncertainty in the
228 // board dimensions computed via ComputeBoundingBox() since this depends on the individual
229 // footprint entities.
230 EDA_RECT bbbox = aPcb->GetBoardEdgesBoundingBox();
231
232 // convert to mm and compensate for an assumed LINE_WIDTH line thickness
233 double x = ( bbbox.GetOrigin().x + LINE_WIDTH / 2 ) * scale + offX;
234 double y = ( bbbox.GetOrigin().y + LINE_WIDTH / 2 ) * scale + offY;
235 double dx = ( bbbox.GetSize().x - LINE_WIDTH ) * scale;
236 double dy = ( bbbox.GetSize().y - LINE_WIDTH ) * scale;
237
238 double px[4], py[4];
239 px[0] = x;
240 py[0] = y;
241
242 px[1] = x;
243 py[1] = y + dy;
244
245 px[2] = x + dx;
246 py[2] = y + dy;
247
248 px[3] = x + dx;
249 py[3] = y;
250
251 IDF_POINT p1, p2;
252
253 p1.x = px[3];
254 p1.y = py[3];
255 p2.x = px[0];
256 p2.y = py[0];
257
258 outline->push( new IDF_SEGMENT( p1, p2 ) );
259
260 for( int i = 1; i < 4; ++i )
261 {
262 p1.x = px[i - 1];
263 p1.y = py[i - 1];
264 p2.x = px[i];
265 p2.y = py[i];
266
267 outline->push( new IDF_SEGMENT( p1, p2 ) );
268 }
269
270 aIDFBoard.AddBoardOutline( outline );
271 }
272
273
274 /**
275 * Retrieve information from all board footprints, adds drill holes to the DRILLED_HOLES or
276 * BOARD_OUTLINE section as appropriate, Compiles data for the PLACEMENT section and compiles
277 * data for the library ELECTRICAL section.
278 */
idf_export_footprint(BOARD * aPcb,FOOTPRINT * aFootprint,IDF3_BOARD & aIDFBoard)279 static void idf_export_footprint( BOARD* aPcb, FOOTPRINT* aFootprint, IDF3_BOARD& aIDFBoard )
280 {
281 // Reference Designator
282 std::string crefdes = TO_UTF8( aFootprint->Reference().GetShownText() );
283
284 if( crefdes.empty() || !crefdes.compare( "~" ) )
285 {
286 std::string cvalue = TO_UTF8( aFootprint->Value().GetShownText() );
287
288 // if both the RefDes and Value are empty or set to '~' the board owns the part,
289 // otherwise associated parts of the footprint must be marked NOREFDES.
290 if( cvalue.empty() || !cvalue.compare( "~" ) )
291 crefdes = "BOARD";
292 else
293 crefdes = "NOREFDES";
294 }
295
296 // TODO: If footprint cutouts are supported we must add code here
297 // for( EDA_ITEM* item = aFootprint->GraphicalItems(); item != NULL; item = item->Next() )
298 // {
299 // if( item->Type() != PCB_FP_SHAPE_T || item->GetLayer() != Edge_Cuts )
300 // continue;
301 // code to export cutouts
302 // }
303
304 // Export pads
305 double drill, x, y;
306 double scale = aIDFBoard.GetUserScale();
307 IDF3::KEY_PLATING kplate;
308 std::string pintype;
309 std::string tstr;
310
311 double dx, dy;
312
313 aIDFBoard.GetUserOffset( dx, dy );
314
315 for( auto pad : aFootprint->Pads() )
316 {
317 drill = (double) pad->GetDrillSize().x * scale;
318 x = pad->GetPosition().x * scale + dx;
319 y = -pad->GetPosition().y * scale + dy;
320
321 // Export the hole on the edge layer
322 if( drill > 0.0 )
323 {
324 // plating
325 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
326 kplate = IDF3::NPTH;
327 else
328 kplate = IDF3::PTH;
329
330 // hole type
331 tstr = TO_UTF8( pad->GetNumber() );
332
333 if( tstr.empty() || !tstr.compare( "0" ) || !tstr.compare( "~" )
334 || ( kplate == IDF3::NPTH )
335 || ( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG ) )
336 pintype = "MTG";
337 else
338 pintype = "PIN";
339
340 // fields:
341 // 1. hole dia. : float
342 // 2. X coord : float
343 // 3. Y coord : float
344 // 4. plating : PTH | NPTH
345 // 5. Assoc. part : BOARD | NOREFDES | PANEL | {"refdes"}
346 // 6. type : PIN | VIA | MTG | TOOL | { "other" }
347 // 7. owner : MCAD | ECAD | UNOWNED
348 if( ( pad->GetDrillShape() == PAD_DRILL_SHAPE_OBLONG )
349 && ( pad->GetDrillSize().x != pad->GetDrillSize().y ) )
350 {
351 // NOTE: IDF does not have direct support for slots;
352 // slots are implemented as a board cutout and we
353 // cannot represent plating or reference designators
354
355 double dlength = pad->GetDrillSize().y * scale;
356
357 // NOTE: The orientation of footprints and pads have
358 // the opposite sense due to KiCad drawing on a
359 // screen with a LH coordinate system
360 double angle = pad->GetOrientation() / 10.0;
361
362 // NOTE: Since this code assumes the scenario where
363 // GetDrillSize().y is the length but idf_parser.cpp
364 // assumes a length along the X axis, the orientation
365 // must be shifted +90 deg when GetDrillSize().y is
366 // the major axis.
367
368 if( dlength < drill )
369 {
370 std::swap( drill, dlength );
371 }
372 else
373 {
374 angle += 90.0;
375 }
376
377 // NOTE: KiCad measures a slot's length from end to end
378 // rather than between the centers of the arcs
379 dlength -= drill;
380
381 aIDFBoard.AddSlot( drill, dlength, angle, x, y );
382 }
383 else
384 {
385 IDF_DRILL_DATA *dp = new IDF_DRILL_DATA( drill, x, y, kplate, crefdes,
386 pintype, IDF3::ECAD );
387
388 if( !aIDFBoard.AddDrill( dp ) )
389 {
390 delete dp;
391
392 std::ostringstream ostr;
393 ostr << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__;
394 ostr << "(): could not add drill";
395
396 throw std::runtime_error( ostr.str() );
397 }
398 }
399 }
400 }
401
402 // add any valid models to the library item list
403 std::string refdes;
404
405 IDF3_COMPONENT* comp = nullptr;
406
407 auto sM = aFootprint->Models().begin();
408 auto eM = aFootprint->Models().end();
409 wxFileName idfFile;
410 wxString idfExt;
411
412 while( sM != eM )
413 {
414 if( !sM->m_Show )
415 {
416 ++sM;
417 continue;
418 }
419
420 idfFile.Assign( resolver->ResolvePath( sM->m_Filename ) );
421 idfExt = idfFile.GetExt();
422
423 if( idfExt.Cmp( wxT( "idf" ) ) && idfExt.Cmp( wxT( "IDF" ) ) )
424 {
425 ++sM;
426 continue;
427 }
428
429 if( refdes.empty() )
430 {
431 refdes = TO_UTF8( aFootprint->Reference().GetShownText() );
432
433 // NOREFDES cannot be used or else the software gets confused
434 // when writing out the placement data due to conflicting
435 // placement and layer specifications; to work around this we
436 // create a (hopefully) unique refdes for our exported part.
437 if( refdes.empty() || !refdes.compare( "~" ) )
438 refdes = aIDFBoard.GetNewRefDes();
439 }
440
441 IDF3_COMP_OUTLINE* outline;
442
443 outline = aIDFBoard.GetComponentOutline( idfFile.GetFullPath() );
444
445 if( !outline )
446 throw( std::runtime_error( aIDFBoard.GetError() ) );
447
448 double rotz = aFootprint->GetOrientation() / 10.0;
449 double locx = sM->m_Offset.x * 25.4; // part offsets are in inches
450 double locy = sM->m_Offset.y * 25.4;
451 double locz = sM->m_Offset.z * 25.4;
452 double lrot = sM->m_Rotation.z;
453
454 bool top = ( aFootprint->GetLayer() == B_Cu ) ? false : true;
455
456 if( top )
457 {
458 locy = -locy;
459 RotatePoint( &locx, &locy, aFootprint->GetOrientation() );
460 locy = -locy;
461 }
462
463 if( !top )
464 {
465 lrot = -lrot;
466 RotatePoint( &locx, &locy, aFootprint->GetOrientation() );
467 locy = -locy;
468
469 rotz = 180.0 - rotz;
470
471 if( rotz >= 360.0 )
472 while( rotz >= 360.0 ) rotz -= 360.0;
473
474 if( rotz <= -360.0 )
475 while( rotz <= -360.0 ) rotz += 360.0;
476 }
477
478 if( comp == nullptr )
479 comp = aIDFBoard.FindComponent( refdes );
480
481 if( comp == nullptr )
482 {
483 comp = new IDF3_COMPONENT( &aIDFBoard );
484
485 if( comp == nullptr )
486 throw( std::runtime_error( aIDFBoard.GetError() ) );
487
488 comp->SetRefDes( refdes );
489
490 if( top )
491 {
492 comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
493 -aFootprint->GetPosition().y * scale + dy,
494 rotz, IDF3::LYR_TOP );
495 }
496 else
497 {
498 comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
499 -aFootprint->GetPosition().y * scale + dy,
500 rotz, IDF3::LYR_BOTTOM );
501 }
502
503 comp->SetPlacement( IDF3::PS_ECAD );
504
505 aIDFBoard.AddComponent( comp );
506 }
507 else
508 {
509 double refX, refY, refA;
510 IDF3::IDF_LAYER side;
511
512 if( ! comp->GetPosition( refX, refY, refA, side ) )
513 {
514 // place the item
515 if( top )
516 {
517 comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
518 -aFootprint->GetPosition().y * scale + dy,
519 rotz, IDF3::LYR_TOP );
520 }
521 else
522 {
523 comp->SetPosition( aFootprint->GetPosition().x * scale + dx,
524 -aFootprint->GetPosition().y * scale + dy,
525 rotz, IDF3::LYR_BOTTOM );
526 }
527
528 comp->SetPlacement( IDF3::PS_ECAD );
529
530 }
531 else
532 {
533 // check that the retrieved component matches this one
534 refX = refX - ( aFootprint->GetPosition().x * scale + dx );
535 refY = refY - ( -aFootprint->GetPosition().y * scale + dy );
536 refA = refA - rotz;
537 refA *= refA;
538 refX *= refX;
539 refY *= refY;
540 refX += refY;
541
542 // conditions: same side, X,Y coordinates within 10 microns,
543 // angle within 0.01 degree
544 if( ( top && side == IDF3::LYR_BOTTOM ) || ( !top && side == IDF3::LYR_TOP )
545 || ( refA > 0.0001 ) || ( refX > 0.0001 ) )
546 {
547 comp->GetPosition( refX, refY, refA, side );
548
549 std::ostringstream ostr;
550 ostr << "* " << __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__ << "():\n";
551 ostr << "* conflicting Reference Designator '" << refdes << "'\n";
552 ostr << "* X loc: " << ( aFootprint->GetPosition().x * scale + dx);
553 ostr << " vs. " << refX << "\n";
554 ostr << "* Y loc: " << ( -aFootprint->GetPosition().y * scale + dy);
555 ostr << " vs. " << refY << "\n";
556 ostr << "* angle: " << rotz;
557 ostr << " vs. " << refA << "\n";
558
559 if( top )
560 ostr << "* TOP vs. ";
561 else
562 ostr << "* BOTTOM vs. ";
563
564 if( side == IDF3::LYR_TOP )
565 ostr << "TOP";
566 else
567 ostr << "BOTTOM";
568
569 throw( std::runtime_error( ostr.str() ) );
570 }
571 }
572 }
573
574 // create the local data ...
575 IDF3_COMP_OUTLINE_DATA* data = new IDF3_COMP_OUTLINE_DATA( comp, outline );
576
577 data->SetOffsets( locx, locy, locz, lrot );
578 comp->AddOutlineData( data );
579 ++sM;
580 }
581 }
582
583
584 /**
585 * Generate IDFv3 compliant board (*.emn) and library (*.emp) files representing the user's
586 * PCB design.
587 */
Export_IDF3(BOARD * aPcb,const wxString & aFullFileName,bool aUseThou,double aXRef,double aYRef)588 bool PCB_EDIT_FRAME::Export_IDF3( BOARD* aPcb, const wxString& aFullFileName,
589 bool aUseThou, double aXRef, double aYRef )
590 {
591 IDF3_BOARD idfBoard( IDF3::CAD_ELEC );
592
593 // Switch the locale to standard C (needed to print floating point numbers)
594 LOCALE_IO toggle;
595
596 resolver = Prj().Get3DCacheManager()->GetResolver();
597
598 bool ok = true;
599 double scale = MM_PER_IU; // we must scale internal units to mm for IDF
600 IDF3::IDF_UNIT idfUnit;
601
602 if( aUseThou )
603 {
604 idfUnit = IDF3::UNIT_THOU;
605 idfBoard.SetUserPrecision( 1 );
606 }
607 else
608 {
609 idfUnit = IDF3::UNIT_MM;
610 idfBoard.SetUserPrecision( 5 );
611 }
612
613 wxFileName brdName = aPcb->GetFileName();
614
615 idfBoard.SetUserScale( scale );
616 idfBoard.SetBoardThickness( aPcb->GetDesignSettings().GetBoardThickness() * scale );
617 idfBoard.SetBoardName( TO_UTF8( brdName.GetFullName() ) );
618 idfBoard.SetBoardVersion( 0 );
619 idfBoard.SetLibraryVersion( 0 );
620
621 std::ostringstream ostr;
622 ostr << "KiCad " << TO_UTF8( GetBuildVersion() );
623 idfBoard.SetIDFSource( ostr.str() );
624
625 try
626 {
627 // set up the board reference point
628 idfBoard.SetUserOffset( -aXRef, aYRef );
629
630 // Export the board outline
631 idf_export_outline( aPcb, idfBoard );
632
633 // Output the drill holes and footprint (library) data.
634 for( FOOTPRINT* footprint : aPcb->Footprints() )
635 idf_export_footprint( aPcb, footprint, idfBoard );
636
637 if( !idfBoard.WriteFile( aFullFileName, idfUnit, false ) )
638 {
639 wxString msg;
640 msg << _( "IDF Export Failed:\n" ) << FROM_UTF8( idfBoard.GetError().c_str() );
641 wxMessageBox( msg );
642
643 ok = false;
644 }
645 }
646 catch( const IO_ERROR& ioe )
647 {
648 wxString msg;
649 msg << _( "IDF Export Failed:\n" ) << ioe.What();
650 wxMessageBox( msg );
651
652 ok = false;
653 }
654 catch( const std::exception& e )
655 {
656 wxString msg;
657 msg << _( "IDF Export Failed:\n" ) << FROM_UTF8( e.what() );
658 wxMessageBox( msg );
659 ok = false;
660 }
661
662 return ok;
663 }
664