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