1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2015-2017 Cirilo Bernardo <cirilo.bernardo@gmail.com>
5  * Copyright (C) 2021 KiCad Developers, see AUTHORS.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 // Note: the board's bottom side is at Z = 0
26 
27 #include <iostream>
28 #include <sstream>
29 #include <cmath>
30 #include <string>
31 #include <map>
32 #include <wx/filename.h>
33 #include <wx/log.h>
34 #include <wx/string.h>
35 
36 #include "plugins/3d/3d_plugin.h"
37 #include "plugins/3dapi/ifsg_all.h"
38 #include "idf_parser.h"
39 #include "vrml_layer.h"
40 
41 #define PLUGIN_3D_IDF_MAJOR 1
42 #define PLUGIN_3D_IDF_MINOR 0
43 #define PLUGIN_3D_IDF_PATCH 0
44 #define PLUGIN_3D_IDF_REVNO 0
45 
46 // number of colors in the palette; cycles from 1..NCOLORS;
47 // number 0 is special (the PCB board color)
48 #define NCOLORS 6
49 
50 /**
51  * Flag to enable IDF plugin trace output.
52  *
53  * @ingroup trace_env_vars
54  */
55 const wxChar* const traceIdfPlugin = wxT( "KICAD_IDF_PLUGIN" );
56 
57 
58 // read and instantiate an IDF component outline
59 static SCENEGRAPH* loadIDFOutline( const wxString& aFileName );
60 
61 
62 // read and render an IDF board assembly
63 static SCENEGRAPH* loadIDFBoard( const wxString& aFileName );
64 
65 
66 // model a single extruded outline
67 // idxColor = color index to use
68 // aParent = parent SCENEGRAPH object, if any
69 static SCENEGRAPH* addOutline( IDF3_COMP_OUTLINE* outline, int idxColor, SGNODE* aParent );
70 
71 
72 // model the board extrusion
73 static SCENEGRAPH* makeBoard( IDF3_BOARD& brd, SGNODE* aParent );
74 
75 
76 // model all included components
77 static bool makeComponents( IDF3_BOARD& brd, SGNODE* aParent );
78 
79 
80 // model any .OTHER_OUTLINE items
81 static bool makeOtherOutlines( IDF3_BOARD& brd, SGNODE* aParent );
82 
83 
84 // convert the IDF outline to VRML intermediate data
85 static bool getOutlineModel( VRML_LAYER& model, const std::list< IDF_OUTLINE* >* items );
86 
87 
88 // convert IDF segment data to VRML segment data
89 static bool addSegment( VRML_LAYER& model, IDF_SEGMENT* seg, int icont, int iseg );
90 
91 
92 // convert the VRML intermediate data into SG* data
93 static SCENEGRAPH* vrmlToSG( VRML_LAYER& vpcb, int idxColor, SGNODE* aParent, double top,
94                              double bottom );
95 
96 
97 class LOCALESWITCH
98 {
99 public:
LOCALESWITCH()100     LOCALESWITCH()
101     {
102         setlocale( LC_NUMERIC, "C" );
103     }
104 
~LOCALESWITCH()105     ~LOCALESWITCH()
106     {
107         setlocale( LC_NUMERIC, "" );
108     }
109 };
110 
111 
getColor(IFSG_SHAPE & shape,int colorIdx)112 static SGNODE* getColor( IFSG_SHAPE& shape, int colorIdx )
113 {
114     IFSG_APPEARANCE material( shape );
115 
116     static int cidx = 1;
117     int idx;
118 
119     if( colorIdx == -1 )
120         idx = cidx;
121     else
122         idx = colorIdx;
123 
124     switch( idx )
125     {
126     case 0:
127         // green for PCB
128         material.SetSpecular( 0.13f, 0.81f, 0.22f );
129         material.SetDiffuse( 0.13f, 0.81f, 0.22f );
130 
131         // default ambient intensity
132         material.SetShininess( 0.3f );
133         break;
134 
135     case 1:
136         // magenta
137         material.SetSpecular( 0.8f, 0.0f, 0.8f );
138         material.SetDiffuse( 0.6f, 0.0f, 0.6f );
139 
140         // default ambient intensity
141         material.SetShininess( 0.3f );
142         break;
143 
144     case 2:
145         // red
146         material.SetSpecular( 0.69f, 0.14f, 0.14f );
147         material.SetDiffuse( 0.69f, 0.14f, 0.14f );
148 
149         // default ambient intensity
150         material.SetShininess( 0.3f );
151         break;
152 
153     case 3:
154         // orange
155         material.SetSpecular( 1.0f, 0.44f, 0.0f );
156         material.SetDiffuse( 1.0f, 0.44f, 0.0f );
157 
158         // default ambient intensity
159         material.SetShininess( 0.3f );
160         break;
161 
162     case 4:
163         // yellow
164         material.SetSpecular( 0.93f, 0.94f, 0.16f );
165         material.SetDiffuse( 0.93f, 0.94f, 0.16f );
166 
167         // default ambient intensity
168         material.SetShininess( 0.3f );
169         break;
170 
171     case 5:
172         // blue
173         material.SetSpecular( 0.1f, 0.11f, 0.88f );
174         material.SetDiffuse( 0.1f, 0.11f, 0.88f );
175 
176         // default ambient intensity
177         material.SetShininess( 0.3f );
178         break;
179 
180     default:
181         // violet
182         material.SetSpecular( 0.32f, 0.07f, 0.64f );
183         material.SetDiffuse( 0.32f, 0.07f, 0.64f );
184 
185         // default ambient intensity
186         material.SetShininess( 0.3f );
187         break;
188     }
189 
190     if( ( colorIdx == -1 ) && ( ++cidx > NCOLORS ) )
191         cidx = 1;
192 
193     return material.GetRawPtr();
194 }
195 
196 
GetKicadPluginName(void)197 const char* GetKicadPluginName( void )
198 {
199     return "PLUGIN_3D_IDF";
200 }
201 
202 
GetPluginVersion(unsigned char * Major,unsigned char * Minor,unsigned char * Patch,unsigned char * Revision)203 void GetPluginVersion( unsigned char* Major, unsigned char* Minor, unsigned char* Patch,
204                        unsigned char* Revision )
205 {
206     if( Major )
207         *Major = PLUGIN_3D_IDF_MAJOR;
208 
209     if( Minor )
210         *Minor = PLUGIN_3D_IDF_MINOR;
211 
212     if( Patch )
213         *Patch = PLUGIN_3D_IDF_PATCH;
214 
215     if( Revision )
216         *Revision = PLUGIN_3D_IDF_REVNO;
217 }
218 
219 
220 // number of extensions supported
221 #ifdef _WIN32
222     #define NEXTS 2
223 #else
224     #define NEXTS 4
225 #endif
226 
227 
228 // number of filter sets supported
229 #define NFILS 2
230 
231 
232 static char ext0[] = "idf";
233 static char ext1[] = "emn";
234 
235 
236 #ifdef _WIN32
237     static char fil0[] = "IDF (*.idf)|*.idf";
238     static char fil1[] = "IDF BRD v2/v3 (*.emn)|*.emn";
239 #else
240     static char ext2[] = "IDF";
241     static char ext3[] = "EMN";
242     static char fil0[] = "IDF (*.idf;*.IDF)|*.idf;*.IDF";
243     static char fil1[] = "IDF BRD (*.emn;*.EMN)|*.emn;*.EMN";
244 #endif
245 
246 static struct FILE_DATA
247 {
248     char const* extensions[NEXTS];
249     char const* filters[NFILS];
250 
FILE_DATAFILE_DATA251     FILE_DATA()
252     {
253         extensions[0] = ext0;
254         extensions[1] = ext1;
255         filters[0] = fil0;
256         filters[1] = fil1;
257 
258 #ifndef _WIN32
259         extensions[2] = ext2;
260         extensions[3] = ext3;
261 #endif
262     }
263 
264 } file_data;
265 
266 
GetNExtensions(void)267 int GetNExtensions( void )
268 {
269     return NEXTS;
270 }
271 
272 
GetModelExtension(int aIndex)273 char const* GetModelExtension( int aIndex )
274 {
275     if( aIndex < 0 || aIndex >= NEXTS )
276         return nullptr;
277 
278     return file_data.extensions[aIndex];
279 }
280 
281 
GetNFilters(void)282 int GetNFilters( void )
283 {
284     return NFILS;
285 }
286 
287 
GetFileFilter(int aIndex)288 char const* GetFileFilter( int aIndex )
289 {
290     if( aIndex < 0 || aIndex >= NFILS )
291         return nullptr;
292 
293     return file_data.filters[aIndex];
294 }
295 
296 
CanRender(void)297 bool CanRender( void )
298 {
299     // this plugin supports rendering of IDF component outlines
300     return true;
301 }
302 
303 
Load(char const * aFileName)304 SCENEGRAPH* Load( char const* aFileName )
305 {
306     if( nullptr == aFileName )
307         return nullptr;
308 
309     wxFileName fname;
310     fname.Assign( wxString::FromUTF8Unchecked( aFileName ) );
311 
312     wxString ext = fname.GetExt();
313 
314     SCENEGRAPH* data = nullptr;
315 
316     if( !ext.Cmp( wxT( "idf" ) ) || !ext.Cmp( wxT( "IDF" ) ) )
317     {
318         data = loadIDFOutline( fname.GetFullPath() );
319     }
320 
321     if( !ext.Cmp( wxT( "emn" ) ) || !ext.Cmp( wxT( "EMN" ) ) )
322     {
323         data = loadIDFBoard( fname.GetFullPath() );
324     }
325 
326     // DEBUG: WRITE OUT IDF FILE TO CONFIRM NORMALS
327 #if defined( DEBUG_IDF ) && DEBUG_IDF > 3
328     if( data )
329     {
330         wxFileName fn( aFileName );
331         wxString output = wxT( "_idf-" );
332         output.append( fn.GetName() );
333         output.append( wxT( ".wrl" ) );
334         S3D::WriteVRML( output.ToUTF8(), true, (SGNODE*) ( data ), true, true );
335     }
336 #endif
337 
338     return data;
339 }
340 
341 
getOutlineModel(VRML_LAYER & model,const std::list<IDF_OUTLINE * > * items)342 static bool getOutlineModel( VRML_LAYER& model, const std::list< IDF_OUTLINE* >* items )
343 {
344     // empty outlines are not unusual so we fail quietly
345     if( items->size() < 1 )
346         return false;
347 
348     int nvcont = 0;
349     int iseg   = 0;
350 
351     std::list< IDF_OUTLINE* >::const_iterator scont = items->begin();
352     std::list< IDF_OUTLINE* >::const_iterator econt = items->end();
353     std::list<IDF_SEGMENT*>::iterator sseg;
354     std::list<IDF_SEGMENT*>::iterator eseg;
355 
356     IDF_SEGMENT lseg;
357 
358     while( scont != econt )
359     {
360         nvcont = model.NewContour();
361 
362         if( nvcont < 0 )
363         {
364             wxLogTrace( traceIdfPlugin, "%s:%s:%s\n * [INFO] cannot create an outline",
365                         __FILE__, __FUNCTION__, __LINE__ );
366 
367             return false;
368         }
369 
370         if( (*scont)->size() < 1 )
371         {
372             wxLogTrace( traceIdfPlugin, "%s:%s:%s\n  * [INFO] invalid contour: no vertices",
373                         __FILE__, __FUNCTION__, __LINE__ );
374 
375             return false;
376         }
377 
378         sseg = (*scont)->begin();
379         eseg = (*scont)->end();
380 
381         iseg = 0;
382 
383         while( sseg != eseg )
384         {
385             lseg = **sseg;
386 
387             if( !addSegment( model, &lseg, nvcont, iseg ) )
388             {
389                 wxLogTrace( traceIdfPlugin, "%s:%s:%s\n * [BUG] cannot add segment",
390                             __FILE__, __FUNCTION__, __LINE__ );
391 
392                 return false;
393             }
394 
395             ++iseg;
396             ++sseg;
397         }
398 
399         ++scont;
400     }
401 
402     return true;
403 }
404 
405 
addSegment(VRML_LAYER & model,IDF_SEGMENT * seg,int icont,int iseg)406 static bool addSegment( VRML_LAYER& model, IDF_SEGMENT* seg, int icont, int iseg )
407 {
408     // note: in all cases we must add all but the last point in the segment
409     // to avoid redundant points
410 
411     if( seg->angle != 0.0 )
412     {
413         if( seg->IsCircle() )
414         {
415             if( iseg != 0 )
416             {
417                 wxLogTrace( traceIdfPlugin, "%s:%s:%s\n * [INFO] adding a circle to an "
418                             "existing vertex list", __FILE__, __FUNCTION__, __LINE__ );
419 
420                 return false;
421             }
422 
423             return model.AppendCircle( seg->center.x, seg->center.y, seg->radius, icont );
424         }
425         else
426         {
427             return model.AppendArc( seg->center.x, seg->center.y, seg->radius,
428                                     seg->offsetAngle, seg->angle, icont );
429         }
430     }
431 
432     if( !model.AddVertex( icont, seg->startPoint.x, seg->startPoint.y ) )
433         return false;
434 
435     return true;
436 }
437 
438 
vrmlToSG(VRML_LAYER & vpcb,int idxColor,SGNODE * aParent,double top,double bottom)439 static SCENEGRAPH* vrmlToSG( VRML_LAYER& vpcb, int idxColor, SGNODE* aParent, double top,
440                              double bottom )
441 {
442     vpcb.Tesselate( nullptr );
443     std::vector< double > vertices;
444     std::vector< int > idxPlane;
445     std::vector< int > idxSide;
446 
447     if( top < bottom )
448     {
449         double tmp = top;
450         top = bottom;
451         bottom = tmp;
452     }
453 
454     if( !vpcb.Get3DTriangles( vertices, idxPlane, idxSide, top, bottom ) )
455     {
456         wxLogTrace( traceIdfPlugin, "%s:%s:%s\n * [INFO] no vertex data",
457                     __FILE__, __FUNCTION__, __LINE__ );
458 
459         return nullptr;
460     }
461 
462     if( ( idxPlane.size() % 3 ) || ( idxSide.size() % 3 ) )
463     {
464         wxLogTrace( traceIdfPlugin, "%s:%s:%s\n * [BUG] index lists are not a multiple of 3 "
465                     "(not a triangle list)", __FILE__, __FUNCTION__, __LINE__ );
466 
467         return nullptr;
468     }
469 
470     std::vector< SGPOINT > vlist;
471     size_t nvert = vertices.size() / 3;
472     size_t j = 0;
473 
474     for( size_t i = 0; i < nvert; ++i, j+= 3 )
475         vlist.emplace_back( vertices[j], vertices[j+1], vertices[j+2] );
476 
477     // create the intermediate scenegraph
478     IFSG_TRANSFORM* tx0 = new IFSG_TRANSFORM( aParent );   // tx0 = Transform for this outline
479 
480     // shape will hold (a) all vertices and (b) a local list of normals
481     IFSG_SHAPE* shape = new IFSG_SHAPE( *tx0 );
482 
483     // this face shall represent the top and bottom planes
484     IFSG_FACESET* face = new IFSG_FACESET( *shape );
485 
486     // coordinates for all faces
487     IFSG_COORDS* cp = new IFSG_COORDS( *face );
488     cp->SetCoordsList( nvert, &vlist[0] );
489 
490     // coordinate indices for top and bottom planes only.
491     IFSG_COORDINDEX* coordIdx = new IFSG_COORDINDEX( *face );
492     coordIdx->SetIndices( idxPlane.size(), &idxPlane[0] );
493 
494     // normals for the top and bottom planes.
495     IFSG_NORMALS* norms = new IFSG_NORMALS( *face );
496 
497     // number of TOP (and bottom) vertices
498     j = nvert / 2;
499 
500     // set the TOP normals
501     for( size_t i = 0; i < j; ++i )
502         norms->AddNormal( 0.0, 0.0, 1.0 );
503 
504     // set the BOTTOM normals
505     for( size_t i = 0; i < j; ++i )
506         norms->AddNormal( 0.0, 0.0, -1.0 );
507 
508     // assign a color from the palette
509     SGNODE* modelColor = getColor( *shape, idxColor );
510 
511     // create a second shape describing the vertical walls of the IDF extrusion
512     // using per-vertex-per-face-normals
513     shape->NewNode( *tx0 );
514     shape->AddRefNode( modelColor );    // set the color to be the same as the top/bottom
515     face->NewNode( *shape );
516     cp->NewNode( *face );               // new vertex list
517     norms->NewNode( *face );            // new normals list
518     coordIdx->NewNode( *face );         // new index list
519 
520     // populate the new per-face vertex list and its indices and normals
521     std::vector< int >::iterator sI = idxSide.begin();
522     std::vector< int >::iterator eI = idxSide.end();
523 
524     size_t sidx = 0;    // index to the new coord set
525     SGPOINT p1, p2, p3;
526     SGVECTOR vnorm;
527 
528     while( sI != eI )
529     {
530         p1 = vlist[*sI];
531         cp->AddCoord( p1 );
532         ++sI;
533 
534         p2 = vlist[*sI];
535         cp->AddCoord( p2 );
536         ++sI;
537 
538         p3 = vlist[*sI];
539         cp->AddCoord( p3 );
540         ++sI;
541 
542         vnorm.SetVector( S3D::CalcTriNorm( p1, p2, p3 ) );
543         norms->AddNormal( vnorm );
544         norms->AddNormal( vnorm );
545         norms->AddNormal( vnorm );
546 
547         coordIdx->AddIndex( (int)sidx );
548         ++sidx;
549         coordIdx->AddIndex( (int)sidx );
550         ++sidx;
551         coordIdx->AddIndex( (int)sidx );
552         ++sidx;
553     }
554 
555     SCENEGRAPH* data = (SCENEGRAPH*)tx0->GetRawPtr();
556 
557     // delete the API wrappers
558     delete shape;
559     delete face;
560     delete coordIdx;
561     delete cp;
562     delete tx0;
563 
564     return data;
565 }
566 
567 
addOutline(IDF3_COMP_OUTLINE * outline,int idxColor,SGNODE * aParent)568 static SCENEGRAPH* addOutline( IDF3_COMP_OUTLINE* outline, int idxColor, SGNODE* aParent )
569 {
570     VRML_LAYER vpcb;
571 
572     if( !getOutlineModel( vpcb, outline->GetOutlines() ) )
573     {
574         wxLogTrace( traceIdfPlugin, "%s:%s:%s\n * [INFO] no valid outline data",
575                     __FILE__, __FUNCTION__, __LINE__ );
576 
577         return nullptr;
578     }
579 
580     vpcb.EnsureWinding( 0, false );
581 
582     double top = outline->GetThickness();
583     double bot = 0.0;
584 
585     // note: some IDF entities permit negative heights
586     if( top < bot )
587     {
588         bot = top;
589         top = 0.0;
590     }
591 
592     SCENEGRAPH* data = vrmlToSG( vpcb, idxColor, aParent, top, bot );
593 
594     return data;
595 }
596 
597 
loadIDFOutline(const wxString & aFileName)598 static SCENEGRAPH* loadIDFOutline( const wxString& aFileName )
599 {
600     LOCALESWITCH switcher;
601     IDF3_BOARD brd( IDF3::CAD_ELEC );
602     IDF3_COMP_OUTLINE* outline = nullptr;
603 
604     outline = brd.GetComponentOutline( aFileName );
605 
606     if( nullptr == outline )
607     {
608         wxLogTrace( traceIdfPlugin, "%s:%s:%s\n * [INFO] Failed to read IDF data:\n%s\n"
609                     " * [INFO] no outline for file '%s'",  __FILE__, __FUNCTION__, __LINE__,
610                     brd.GetError(), aFileName );
611 
612         return nullptr;
613     }
614 
615     SCENEGRAPH* data = addOutline( outline, -1, nullptr );
616 
617     return data;
618 }
619 
620 
loadIDFBoard(const wxString & aFileName)621 static SCENEGRAPH* loadIDFBoard( const wxString& aFileName )
622 {
623     LOCALESWITCH switcher;
624     IDF3_BOARD brd( IDF3::CAD_ELEC );
625 
626     // note: if the IDF model is defective no outline substitutes shall be made
627     if( !brd.ReadFile( aFileName, true ) )
628     {
629         wxLogTrace( traceIdfPlugin, "%s:%s:%s\n"
630                     "* [INFO] Error '%s' occurred reading IDF file: %s",
631                     __FILE__, __FUNCTION__, __LINE__, brd.GetError(), aFileName );
632 
633         return nullptr;
634     }
635 
636     IFSG_TRANSFORM tx0( true );
637     SGNODE* topNode = tx0.GetRawPtr();
638 
639     bool noBoard = false;
640     bool noComp = false;
641     bool noOther = false;
642 
643     if( nullptr == makeBoard( brd, topNode ) )
644         noBoard = true;
645 
646     if( !makeComponents( brd, topNode ) )
647         noComp = true;
648 
649     if( !makeOtherOutlines( brd, topNode ) )
650         noOther = true;
651 
652     if( noBoard && noComp && noOther )
653     {
654         tx0.Destroy();
655         return nullptr;
656     }
657 
658     return (SCENEGRAPH*) topNode;
659 }
660 
661 
makeBoard(IDF3_BOARD & brd,SGNODE * aParent)662 static SCENEGRAPH* makeBoard( IDF3_BOARD& brd, SGNODE* aParent )
663 {
664     if( nullptr == aParent )
665         return nullptr;
666 
667     VRML_LAYER vpcb;
668 
669     // check if no board outline
670     if( brd.GetBoardOutlinesSize() < 1 )
671         return nullptr;
672 
673 
674     if( !getOutlineModel( vpcb, brd.GetBoardOutline()->GetOutlines() ) )
675         return nullptr;
676 
677     vpcb.EnsureWinding( 0, false );
678 
679     int nvcont = vpcb.GetNContours() - 1;
680 
681     while( nvcont > 0 )
682         vpcb.EnsureWinding( nvcont--, true );
683 
684     // Add the drill holes
685     const std::list<IDF_DRILL_DATA*>* drills = &brd.GetBoardDrills();
686 
687     std::list<IDF_DRILL_DATA*>::const_iterator sd = drills->begin();
688     std::list<IDF_DRILL_DATA*>::const_iterator ed = drills->end();
689 
690     while( sd != ed )
691     {
692         vpcb.AddCircle( (*sd)->GetDrillXPos(), (*sd)->GetDrillYPos(),
693             (*sd)->GetDrillDia() / 2.0, true );
694         ++sd;
695     }
696 
697     std::map< std::string, IDF3_COMPONENT* >*const comp = brd.GetComponents();
698     std::map< std::string, IDF3_COMPONENT* >::const_iterator sc = comp->begin();
699     std::map< std::string, IDF3_COMPONENT* >::const_iterator ec = comp->end();
700 
701     while( sc != ec )
702     {
703         drills = sc->second->GetDrills();
704         sd = drills->begin();
705         ed = drills->end();
706 
707         while( sd != ed )
708         {
709             vpcb.AddCircle( (*sd)->GetDrillXPos(), (*sd)->GetDrillYPos(),
710                 (*sd)->GetDrillDia() / 2.0, true );
711             ++sd;
712         }
713 
714         ++sc;
715     }
716 
717     double top = brd.GetBoardThickness();
718 
719     SCENEGRAPH* data = vrmlToSG( vpcb, 0, aParent, top, 0.0 );
720 
721     return data;
722 }
723 
724 
makeComponents(IDF3_BOARD & brd,SGNODE * aParent)725 static bool makeComponents( IDF3_BOARD& brd, SGNODE* aParent )
726 {
727     if( nullptr == aParent )
728         return false;
729 
730     int ncomponents = 0;
731 
732     double brdTop = brd.GetBoardThickness();
733 
734     // Add the component outlines
735     const std::map< std::string, IDF3_COMPONENT* >*const comp = brd.GetComponents();
736     std::map< std::string, IDF3_COMPONENT* >::const_iterator sc = comp->begin();
737     std::map< std::string, IDF3_COMPONENT* >::const_iterator ec = comp->end();
738 
739     std::list< IDF3_COMP_OUTLINE_DATA* >::const_iterator so;
740     std::list< IDF3_COMP_OUTLINE_DATA* >::const_iterator eo;
741 
742     double vX, vY, vA;
743     double tX, tY, tZ, tA;
744     bool   bottom;
745     IDF3::IDF_LAYER lyr;
746 
747     std::map< std::string, SGNODE* > dataMap;    // map data by UID
748     std::map< std::string, SGNODE* >::iterator dataItem;
749     IDF3_COMP_OUTLINE* pout;
750 
751     while( sc != ec )
752     {
753         sc->second->GetPosition( vX, vY, vA, lyr );
754 
755         if( lyr == IDF3::LYR_BOTTOM )
756             bottom = true;
757         else
758             bottom = false;
759 
760         so = sc->second->GetOutlinesData()->begin();
761         eo = sc->second->GetOutlinesData()->end();
762 
763         while( so != eo )
764         {
765             if( std::abs( (*so)->GetOutline()->GetThickness() ) < 0.001 )
766             {
767                 ++so;
768                 continue;
769             }
770 
771             (*so)->GetOffsets( tX, tY, tZ, tA );
772             tX += vX;
773             tY += vY;
774             tA += vA;
775 
776             pout = (IDF3_COMP_OUTLINE*)((*so)->GetOutline());
777 
778             if( nullptr == pout  )
779             {
780                 ++so;
781                 continue;
782             }
783 
784             dataItem = dataMap.find( pout->GetUID() );
785             SCENEGRAPH* sg = nullptr;
786 
787             if( dataItem == dataMap.end() )
788             {
789                 sg = addOutline( pout, -1, nullptr );
790 
791                 if( nullptr == sg )
792                 {
793                     ++so;
794                     continue;
795                 }
796 
797                 ++ncomponents;
798                 dataMap.insert( std::pair< std::string, SGNODE* >( pout->GetUID(), (SGNODE*)sg ) );
799             }
800             else
801             {
802                 sg = (SCENEGRAPH*) dataItem->second;
803             }
804 
805             IFSG_TRANSFORM tx0( aParent );
806             IFSG_TRANSFORM txN( false );
807             txN.Attach( (SGNODE*)sg );
808 
809             if( nullptr == txN.GetParent() )
810                 tx0.AddChildNode( txN );
811             else
812                 tx0.AddRefNode( txN );
813 
814             if( bottom )
815             {
816                 tx0.SetTranslation( SGPOINT( tX, tY, -tZ ) );
817                 // for an item on the back of the board we have a  compounded rotation,
818                 // first a flip on the Y axis as per the IDF spec and then a rotation
819                 // of -tA degrees on the Z axis. The resultant rotation axis is an
820                 // XY vector equivalent to (0,1) rotated by -(tA/2) degrees
821                 //
822                 double ang = -tA * M_PI / 360.0;
823                 double sinA = sin( ang );
824                 double cosA = cos( ang );
825                 tx0.SetRotation( SGVECTOR( -sinA, cosA , 0 ), M_PI );
826             }
827             else
828             {
829                 tx0.SetTranslation( SGPOINT( tX, tY, tZ + brdTop ) );
830                 tx0.SetRotation( SGVECTOR( 0, 0, 1 ), tA * M_PI / 180.0 );
831             }
832 
833             ++so;
834         }
835 
836         ++sc;
837     }
838 
839     if( 0 == ncomponents )
840         return false;
841 
842     return true;
843 }
844 
845 
makeOtherOutlines(IDF3_BOARD & brd,SGNODE * aParent)846 static bool makeOtherOutlines( IDF3_BOARD& brd, SGNODE* aParent )
847 {
848     if( nullptr == aParent )
849         return false;
850 
851     VRML_LAYER vpcb;
852     int ncomponents = 0;
853 
854     double brdTop = brd.GetBoardThickness();
855     double top, bot;
856 
857     // Add the component outlines
858     const std::map< std::string, OTHER_OUTLINE* >*const comp = brd.GetOtherOutlines();
859     std::map< std::string, OTHER_OUTLINE* >::const_iterator sc = comp->begin();
860     std::map< std::string, OTHER_OUTLINE* >::const_iterator ec = comp->end();
861 
862     int nvcont;
863 
864     OTHER_OUTLINE* pout;
865 
866     while( sc != ec )
867     {
868         pout = sc->second;
869 
870         if( std::abs( pout->GetThickness() ) < 0.001 )
871         {
872             ++sc;
873             continue;
874         }
875 
876         if( !getOutlineModel( vpcb, pout->GetOutlines() ) )
877         {
878             vpcb.Clear();
879             ++sc;
880             continue;
881         }
882 
883         vpcb.EnsureWinding( 0, false );
884 
885         nvcont = vpcb.GetNContours() - 1;
886 
887         while( nvcont > 0 )
888             vpcb.EnsureWinding( nvcont--, true );
889 
890         if( pout->GetSide() == IDF3::LYR_BOTTOM )
891         {
892             top = 0.0;
893             bot = -pout->GetThickness();
894         }
895         else
896         {
897             bot = brdTop;
898             top = bot + pout->GetThickness();
899         }
900 
901         if( nullptr == vrmlToSG( vpcb, -1, aParent, top, bot ) )
902         {
903             vpcb.Clear();
904             ++sc;
905             continue;
906         }
907 
908         ++ncomponents;
909 
910         vpcb.Clear();
911         ++sc;
912     }
913 
914     if( 0 == ncomponents )
915         return false;
916 
917     return true;
918 }
919