1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
5  * Copyright (C) 2020-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  * Some code lifted from FreeCAD, copyright (c) 2018 Zheng, Lei (realthunder) under GPLv2
26  */
27 
28 #include <iostream>
29 #include <fstream>
30 #include <sstream>
31 #include <string>
32 #include <cstring>
33 #include <map>
34 #include <vector>
35 #include <wx/filename.h>
36 #include <wx/log.h>
37 #include <wx/stdpaths.h>
38 #include <wx/string.h>
39 #include <wx/utils.h>
40 #include <wx/wfstream.h>
41 #include <wx/zipstrm.h>
42 
43 #include <decompress.hpp>
44 
45 #include <TDocStd_Document.hxx>
46 #include <TopoDS.hxx>
47 #include <TopoDS_Shape.hxx>
48 #include <Quantity_Color.hxx>
49 #include <XCAFApp_Application.hxx>
50 
51 #include <AIS_Shape.hxx>
52 
53 #include <IGESControl_Reader.hxx>
54 #include <IGESCAFControl_Reader.hxx>
55 #include <Interface_Static.hxx>
56 
57 #include <STEPControl_Reader.hxx>
58 #include <STEPCAFControl_Reader.hxx>
59 
60 #include <XCAFDoc_DocumentTool.hxx>
61 #include <XCAFDoc_ColorTool.hxx>
62 #include <XCAFDoc_ShapeTool.hxx>
63 
64 #include <BRep_Tool.hxx>
65 #include <BRepMesh_IncrementalMesh.hxx>
66 
67 #include <TopoDS.hxx>
68 #include <TopoDS_Shape.hxx>
69 #include <TopoDS_Face.hxx>
70 #include <TopoDS_Compound.hxx>
71 #include <TopExp_Explorer.hxx>
72 
73 #include <Quantity_Color.hxx>
74 #include <Poly_Triangulation.hxx>
75 #include <Poly_PolygonOnTriangulation.hxx>
76 #include <Precision.hxx>
77 
78 #include <TDF_LabelSequence.hxx>
79 #include <TDF_ChildIterator.hxx>
80 #include <TDF_Tool.hxx>
81 #include <TDataStd_Name.hxx>
82 
83 #include "plugins/3dapi/ifsg_all.h"
84 
85 
86 // log mask for wxLogTrace
87 #define MASK_OCE "PLUGIN_OCE"
88 #define MASK_OCE_EXTRA "PLUGIN_OCE_EXTRA"
89 
90 // precision for mesh creation; 0.07 should be good enough for ECAD viewing
91 #define USER_PREC (0.14)
92 
93 // angular deflection for meshing
94 // 10 deg (36 faces per circle) = 0.17453293
95 // 20 deg (18 faces per circle) = 0.34906585
96 // 30 deg (12 faces per circle) = 0.52359878
97 #define USER_ANGLE (0.52359878)
98 
99 typedef std::map< Standard_Real, SGNODE* > COLORMAP;
100 typedef std::map< std::string, SGNODE* >   FACEMAP;
101 typedef std::map< std::string, std::vector< SGNODE* > > NODEMAP;
102 typedef std::pair< std::string, std::vector< SGNODE* > > NODEITEM;
103 
104 struct DATA;
105 
106 bool processLabel( const TDF_Label& aLabel, DATA& aData, SGNODE* aParent,
107                   std::vector< SGNODE* >* aItems );
108 
109 
110 bool processFace( const TopoDS_Face& face, DATA& data, SGNODE* parent,
111                   std::vector< SGNODE* >* items, Quantity_Color* color );
112 
113 
114 struct DATA
115 {
116     Handle( TDocStd_Document ) m_doc;
117     Handle( XCAFDoc_ColorTool ) m_color;
118     Handle( XCAFDoc_ShapeTool ) m_assy;
119     SGNODE* scene;
120     SGNODE* defaultColor;
121     Quantity_Color refColor;
122     NODEMAP  shapes;    // SGNODE lists representing a TopoDS_SOLID / COMPOUND
123     COLORMAP colors;    // SGAPPEARANCE nodes
124     FACEMAP  faces;     // SGSHAPE items representing a TopoDS_FACE
125     bool renderBoth;    // set TRUE if we're processing IGES
126     bool hasSolid;      // set TRUE if there is no parent SOLID
127 
DATADATA128     DATA()
129     {
130         scene = nullptr;
131         defaultColor = nullptr;
132         refColor.SetValues( Quantity_NOC_BLACK );
133         renderBoth = false;
134         hasSolid = false;
135     }
136 
~DATADATA137     ~DATA()
138     {
139         // destroy any colors with no parent
140         if( !colors.empty() )
141         {
142             COLORMAP::iterator sC = colors.begin();
143             COLORMAP::iterator eC = colors.end();
144 
145             while( sC != eC )
146             {
147                 if( nullptr == S3D::GetSGNodeParent( sC->second ) )
148                     S3D::DestroyNode( sC->second );
149 
150                 ++sC;
151             }
152 
153             colors.clear();
154         }
155 
156         if( defaultColor && nullptr == S3D::GetSGNodeParent( defaultColor ) )
157             S3D::DestroyNode(defaultColor);
158 
159         // destroy any faces with no parent
160         if( !faces.empty() )
161         {
162             FACEMAP::iterator sF = faces.begin();
163             FACEMAP::iterator eF = faces.end();
164 
165             while( sF != eF )
166             {
167                 if( nullptr == S3D::GetSGNodeParent( sF->second ) )
168                     S3D::DestroyNode( sF->second );
169 
170                 ++sF;
171             }
172 
173             faces.clear();
174         }
175 
176         // destroy any shapes with no parent
177         if( !shapes.empty() )
178         {
179             NODEMAP::iterator sS = shapes.begin();
180             NODEMAP::iterator eS = shapes.end();
181 
182             while( sS != eS )
183             {
184                 std::vector< SGNODE* >::iterator sV = sS->second.begin();
185                 std::vector< SGNODE* >::iterator eV = sS->second.end();
186 
187                 while( sV != eV )
188                 {
189                     if( nullptr == S3D::GetSGNodeParent( *sV ) )
190                         S3D::DestroyNode( *sV );
191 
192                     ++sV;
193                 }
194 
195                 sS->second.clear();
196                 ++sS;
197             }
198 
199             shapes.clear();
200         }
201 
202         if( scene )
203             S3D::DestroyNode(scene);
204     }
205 
206     // find collection of tagged nodes
GetShapeDATA207     bool GetShape( const std::string& id, std::vector< SGNODE* >*& listPtr )
208     {
209         listPtr = nullptr;
210         NODEMAP::iterator item;
211         item = shapes.find( id );
212 
213         if( item == shapes.end() )
214             return false;
215 
216         listPtr = &item->second;
217         return true;
218     }
219 
220     // find collection of tagged nodes
GetFaceDATA221     SGNODE* GetFace( const std::string& id )
222     {
223         FACEMAP::iterator item;
224         item = faces.find( id );
225 
226         if( item == faces.end() )
227             return nullptr;
228 
229         return item->second;
230     }
231 
232     // return color if found; if not found, create SGAPPEARANCE
GetColorDATA233     SGNODE* GetColor( Quantity_Color* colorObj )
234     {
235         if( nullptr == colorObj )
236         {
237             if( defaultColor )
238                 return defaultColor;
239 
240             IFSG_APPEARANCE app( true );
241             app.SetShininess( 0.05f );
242             app.SetSpecular( 0.04f, 0.04f, 0.04f );
243             app.SetAmbient( 0.1f, 0.1f, 0.1f );
244             app.SetDiffuse( 0.6f, 0.6f, 0.6f );
245 
246             defaultColor = app.GetRawPtr();
247             return defaultColor;
248         }
249 
250         Standard_Real id = colorObj->Distance( refColor );
251         std::map< Standard_Real, SGNODE* >::iterator item;
252         item = colors.find( id );
253 
254         if( item != colors.end() )
255             return item->second;
256 
257         IFSG_APPEARANCE app( true );
258         app.SetShininess( 0.1f );
259         app.SetSpecular( 0.12f, 0.12f, 0.12f );
260         app.SetAmbient( 0.1f, 0.1f, 0.1f );
261         app.SetDiffuse( colorObj->Red(), colorObj->Green(), colorObj->Blue() );
262         colors.insert( std::pair< Standard_Real, SGNODE* >( id, app.GetRawPtr() ) );
263 
264         return app.GetRawPtr();
265     }
266 };
267 
268 
269 enum FormatType
270 {
271     FMT_NONE = 0,
272     FMT_STEP,
273     FMT_STPZ,
274     FMT_IGES
275 };
276 
277 
fileType(const char * aFileName)278 FormatType fileType( const char* aFileName )
279 {
280     wxFileName fname( wxString::FromUTF8Unchecked( aFileName ) );
281     wxFFileInputStream ifile( fname.GetFullPath() );
282 
283     if( !ifile.IsOk() )
284         return FMT_NONE;
285 
286     if( fname.GetExt().MakeUpper().EndsWith( "STPZ" ) ||
287         fname.GetExt().MakeUpper().EndsWith( "GZ" ) )
288         return FMT_STPZ;
289 
290     char iline[82];
291     memset( iline, 0, 82 );
292     ifile.Read( iline, 82 );
293     iline[81] = 0;  // ensure NULL termination when string is too long
294 
295     // check for STEP in Part 21 format
296     // (this can give false positives since Part 21 is not exclusively STEP)
297     if( !strncmp( iline, "ISO-10303-21;", 13 ) )
298         return FMT_STEP;
299 
300     std::string fstr = iline;
301 
302     // check for STEP in XML format
303     // (this can give both false positive and false negatives)
304     if( fstr.find( "urn:oid:1.0.10303." ) != std::string::npos )
305         return FMT_STEP;
306 
307     // Note: this is a very simple test which can yield false positives; the only
308     // sure method for determining if a file *not* an IGES model is to attempt
309     // to load it.
310     if( iline[72] == 'S' && ( iline[80] == 0 || iline[80] == 13 || iline[80] == 10 ) )
311         return FMT_IGES;
312 
313     return FMT_NONE;
314 }
315 
316 
317 /**
318  * Gets the absolute tag string for a given label in the form of ##:##:##:##
319  *
320  * @param aLabel is the label to get the string for
321  * @param aTag is the resulting tag string based by reference
322  */
getTag(const TDF_Label & aLabel,std::string & aTag)323 void getTag( const TDF_Label& aLabel, std::string& aTag )
324 {
325     std::ostringstream ostr;
326 
327     if( aLabel.IsNull() )
328     {
329         wxLogTrace( MASK_OCE, "Null label passed to getTag" );
330         return;
331     }
332 
333     TColStd_ListOfInteger tagList;
334     TDF_Tool::TagList( aLabel, tagList );
335 
336     for( TColStd_ListOfInteger::Iterator it( tagList ); it.More(); it.Next() )
337     {
338         ostr << it.Value();
339         ostr << ":";
340     }
341 
342     aTag = ostr.str();
343     aTag.pop_back();    // kill the last colon
344 }
345 
346 
getLabelName(const TDF_Label & aLabel)347 static wxString getLabelName( const TDF_Label& aLabel )
348 {
349     wxString txt;
350     Handle( TDataStd_Name ) name;
351     if( !aLabel.IsNull() && aLabel.FindAttribute( TDataStd_Name::GetID(), name ) )
352     {
353         TCollection_ExtendedString extstr = name->Get();
354         char*                      str = new char[extstr.LengthOfCString() + 1];
355         extstr.ToUTF8CString( str );
356 
357         txt = wxString::FromUTF8( str );
358         delete[] str;
359         txt = txt.Trim();
360     }
361     return txt;
362 }
363 
364 
365 /**
366  * Gets a string for a given TopAbs_ShapeEnum element
367  *
368  * @param aShape enum value to convert
369  */
getShapeName(TopAbs_ShapeEnum aShape)370 std::string getShapeName( TopAbs_ShapeEnum aShape )
371 {
372     switch( aShape )
373     {
374     case TopAbs_COMPOUND: return "COMPOUND";
375     case TopAbs_COMPSOLID: return "COMPSOLID";
376     case TopAbs_SOLID: return "SOLID";
377     case TopAbs_SHELL: return "SHELL";
378     case TopAbs_FACE: return "FACE";
379     case TopAbs_WIRE: return "WIRE";
380     case TopAbs_EDGE: return "EDGE";
381     case TopAbs_VERTEX: return "VERTEX";
382     case TopAbs_SHAPE: return "SHAPE";
383     }
384 
385     return "UNKNOWN";
386 }
387 
colorFloatToDecimal(float aVal)388 static int colorFloatToDecimal( float aVal )
389 {
390     return aVal * 255;
391 }
392 
393 
operator <<(std::ostream & aOStream,const Quantity_ColorRGBA & aColor)394 static inline std::ostream& operator<<( std::ostream& aOStream, const Quantity_ColorRGBA& aColor )
395 {
396     Quantity_Color rgb = aColor.GetRGB();
397 
398     return aOStream << "rgba(" << colorFloatToDecimal( rgb.Red() ) << ","
399                     << colorFloatToDecimal( rgb.Green() ) << ","
400                     << colorFloatToDecimal( rgb.Blue() ) << ","
401                     << colorFloatToDecimal( aColor.Alpha() )
402                     << ")";
403 }
404 
405 
406 /**
407  * Gets a string for a given TopAbs_ShapeEnum element
408  *
409  * @param aLabel Label to convert
410  * @param aShapeTool Handle to shape tool being used
411  * @param aColorTool Handle to color tool being used
412  * @param aPregMsg Any prefixed message to insert (used for indentation in dump)
413  */
printLabel(TDF_Label aLabel,Handle (XCAFDoc_ShapeTool)aShapeTool,Handle (XCAFDoc_ColorTool)aColorTool,const char * aPreMsg=nullptr)414 static void printLabel( TDF_Label aLabel, Handle( XCAFDoc_ShapeTool ) aShapeTool,
415                         Handle( XCAFDoc_ColorTool ) aColorTool, const char* aPreMsg = nullptr )
416 {
417     if( aLabel.IsNull() )
418         return;
419 
420     if( !aPreMsg )
421         aPreMsg = "Label: ";
422 
423     TCollection_AsciiString entry;
424     TDF_Tool::Entry( aLabel, entry );
425     std::ostringstream ss;
426     ss << aPreMsg << entry << ", " << getLabelName( aLabel )
427        << ( aShapeTool->IsShape( aLabel ) ? ", shape" : "" )
428        << ( aShapeTool->IsTopLevel( aLabel ) ? ", topLevel" : "" )
429        << ( aShapeTool->IsFree( aLabel ) ? ", free" : "" )
430        << ( aShapeTool->IsAssembly( aLabel ) ? ", assembly" : "" )
431        << ( aShapeTool->IsSimpleShape( aLabel ) ? ", simple" : "" )
432        << ( aShapeTool->IsCompound( aLabel ) ? ", compound" : "" )
433        << ( aShapeTool->IsReference( aLabel ) ? ", reference" : "" )
434        << ( aShapeTool->IsComponent( aLabel ) ? ", component" : "" )
435        << ( aShapeTool->IsSubShape( aLabel ) ? ", subshape" : "" );
436 
437     if( aShapeTool->IsSubShape( aLabel ) )
438     {
439         auto shape = aShapeTool->GetShape( aLabel );
440         if( !shape.IsNull() )
441             ss << ", " << getShapeName( shape.ShapeType() );
442     }
443 
444     if( aShapeTool->IsShape( aLabel ) )
445     {
446         Quantity_ColorRGBA c;
447         if( aColorTool->GetColor( aLabel, XCAFDoc_ColorGen, c ) )
448             ss << ", gc: " << c;
449         if( aColorTool->GetColor( aLabel, XCAFDoc_ColorSurf, c ) )
450             ss << ", sc: " << c;
451         if( aColorTool->GetColor( aLabel, XCAFDoc_ColorCurv, c ) )
452             ss << ", cc: " << c;
453     }
454 
455     wxLogTrace( MASK_OCE, ss.str().c_str() );
456 }
457 
458 
459 /**
460  * Dumps a label and the entire tree underneath it
461  *
462  * @param aLabel Label to convert
463  * @param aShapeTool Handle to shape tool being used
464  * @param aColorTool Handle to color tool being used
465  * @param aDepth Indentation level to offset labels (used recursively by dumpLabels)
466  */
dumpLabels(TDF_Label aLabel,Handle (XCAFDoc_ShapeTool)aShapeTool,Handle (XCAFDoc_ColorTool)aColorTool,int aDepth=0)467 static void dumpLabels( TDF_Label aLabel, Handle( XCAFDoc_ShapeTool ) aShapeTool,
468                         Handle( XCAFDoc_ColorTool ) aColorTool, int aDepth = 0 )
469 {
470     std::string indent( aDepth * 2, ' ' );
471     printLabel( aLabel, aShapeTool, aColorTool, indent.c_str() );
472     TDF_ChildIterator it;
473     for( it.Initialize( aLabel ); it.More(); it.Next() )
474         dumpLabels( it.Value(), aShapeTool, aColorTool, aDepth + 1 );
475 }
476 
477 
getColor(DATA & data,TDF_Label label,Quantity_Color & color)478 bool getColor( DATA& data, TDF_Label label, Quantity_Color& color )
479 {
480     while( true )
481     {
482         if( data.m_color->GetColor( label, XCAFDoc_ColorGen, color ) )
483             return true;
484         else if( data.m_color->GetColor( label, XCAFDoc_ColorSurf, color ) )
485             return true;
486         else if( data.m_color->GetColor( label, XCAFDoc_ColorCurv, color ) )
487             return true;
488 
489         label = label.Father();
490 
491         if( label.IsNull() )
492             break;
493     };
494 
495     return false;
496 }
497 
498 
addItems(SGNODE * parent,std::vector<SGNODE * > * lp)499 void addItems( SGNODE* parent, std::vector< SGNODE* >* lp )
500 {
501     if( nullptr == lp )
502         return;
503 
504     std::vector< SGNODE* >::iterator sL = lp->begin();
505     std::vector< SGNODE* >::iterator eL = lp->end();
506     SGNODE* item;
507 
508     while( sL != eL )
509     {
510         item = *sL;
511 
512         if( nullptr == S3D::GetSGNodeParent( item ) )
513             S3D::AddSGNodeChild( parent, item );
514         else
515             S3D::AddSGNodeRef( parent, item );
516 
517         ++sL;
518     }
519 }
520 
521 
readIGES(Handle (TDocStd_Document)& m_doc,const char * fname)522 bool readIGES( Handle( TDocStd_Document ) & m_doc, const char* fname )
523 {
524     IGESCAFControl_Reader reader;
525     IFSelect_ReturnStatus stat  = reader.ReadFile( fname );
526     reader.PrintCheckLoad( Standard_False, IFSelect_ItemsByEntity );
527 
528     if( stat != IFSelect_RetDone )
529         return false;
530 
531     // Enable file-defined shape precision
532     if( !Interface_Static::SetIVal( "read.precision.mode", 0 ) )
533         return false;
534 
535     // set other translation options
536     reader.SetColorMode(true);  // use model colors
537     reader.SetNameMode(false);  // don't use IGES label names
538     reader.SetLayerMode(false); // ignore LAYER data
539 
540     if ( !reader.Transfer( m_doc ) )
541         return false;
542 
543     // are there any shapes to translate?
544     if( reader.NbShapes() < 1 )
545         return false;
546 
547     return true;
548 }
549 
550 
readSTEP(Handle (TDocStd_Document)& m_doc,const char * fname)551 bool readSTEP( Handle(TDocStd_Document)& m_doc, const char* fname )
552 {
553     wxLogTrace( MASK_OCE, "Reading step file %s", fname );
554 
555     STEPCAFControl_Reader reader;
556     IFSelect_ReturnStatus stat  = reader.ReadFile( fname );
557 
558     if( stat != IFSelect_RetDone )
559         return false;
560 
561     // Enable user-defined shape precision
562     if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
563         return false;
564 
565     // Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
566     if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
567         return false;
568 
569     // set other translation options
570     reader.SetColorMode( true );  // use model colors
571     reader.SetNameMode( false );  // don't use label names
572     reader.SetLayerMode( false ); // ignore LAYER data
573 
574     if ( !reader.Transfer( m_doc ) )
575     {
576         m_doc->Close();
577         return false;
578     }
579 
580     // are there any shapes to translate?
581     if( reader.NbRootsForTransfer() < 1 )
582         return false;
583 
584     return true;
585 }
586 
587 
readSTEPZ(Handle (TDocStd_Document)& m_doc,const char * aFileName)588 bool readSTEPZ( Handle(TDocStd_Document)& m_doc, const char* aFileName )
589 {
590     wxFileName fname( wxString::FromUTF8Unchecked( aFileName ) );
591     wxFFileInputStream ifile( fname.GetFullPath() );
592 
593     wxFileName outFile( fname );
594 
595     outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
596     outFile.SetExt( "STEP" );
597 
598     wxFileOffset size = ifile.GetLength();
599     wxBusyCursor busycursor;
600 
601     if( size == wxInvalidOffset )
602         return false;
603 
604     {
605         bool success = false;
606         wxFFileOutputStream ofile( outFile.GetFullPath() );
607 
608         if( !ofile.IsOk() )
609             return false;
610 
611         char *buffer = new char[size];
612 
613         ifile.Read( buffer, size);
614         std::string expanded;
615 
616         try
617         {
618             expanded = gzip::decompress( buffer, size );
619             success = true;
620         }
621         catch(...)
622         {}
623 
624         if( expanded.empty() )
625         {
626             ifile.Reset();
627             ifile.SeekI( 0 );
628             wxZipInputStream izipfile( ifile );
629             std::unique_ptr<wxZipEntry> zip_file( izipfile.GetNextEntry() );
630 
631             if( zip_file && !zip_file->IsDir() && izipfile.CanRead() )
632             {
633                 izipfile.Read( ofile );
634                 success = true;
635             }
636         }
637         else
638         {
639             ofile.Write( expanded.data(), expanded.size() );
640         }
641 
642         delete[] buffer;
643         ofile.Close();
644 
645         if( !success )
646             return false;
647     }
648 
649     bool retval = readSTEP( m_doc, outFile.GetFullPath().mb_str() );
650 
651     // Cleanup our temporary file
652     wxRemoveFile( outFile.GetFullPath() );
653 
654     return retval;
655 }
656 
657 
LoadModel(char const * filename)658 SCENEGRAPH* LoadModel( char const* filename )
659 {
660     DATA data;
661 
662     Handle(XCAFApp_Application) m_app = XCAFApp_Application::GetApplication();
663     m_app->NewDocument( "MDTV-XCAF", data.m_doc );
664     FormatType modelFmt = fileType( filename );
665 
666     switch( modelFmt )
667     {
668     case FMT_IGES:
669         data.renderBoth = true;
670 
671         if( !readIGES( data.m_doc, filename ) )
672             return nullptr;
673 
674         break;
675 
676     case FMT_STEP:
677         if( !readSTEP( data.m_doc, filename ) )
678             return nullptr;
679 
680         break;
681 
682     case FMT_STPZ:
683         if( !readSTEPZ( data.m_doc, filename ) )
684             return nullptr;
685 
686         break;
687 
688 
689     default:
690         return nullptr;
691         break;
692     }
693 
694     data.m_assy = XCAFDoc_DocumentTool::ShapeTool( data.m_doc->Main() );
695     data.m_color = XCAFDoc_DocumentTool::ColorTool( data.m_doc->Main() );
696 
697     // Check if the log mask is enabled otherwise the dump routine may be expensive before the wxLog call
698     if( wxLog::IsAllowedTraceMask( MASK_OCE ) )
699     {
700         dumpLabels( data.m_doc->Main(), data.m_assy, data.m_color );
701     }
702 
703     // retrieve all free shapes
704     TDF_LabelSequence frshapes;
705     data.m_assy->GetFreeShapes( frshapes );
706 
707     bool ret = false;
708 
709     // create the top level SG node
710     IFSG_TRANSFORM topNode( true );
711     data.scene = topNode.GetRawPtr();
712 
713     for( Standard_Integer i = 1; i <= frshapes.Length(); i++ )
714     {
715         const TDF_Label& label = frshapes.Value( i );
716 
717         if( data.m_color->IsVisible( label ) )
718         {
719             if( processLabel( label, data, data.scene, nullptr ) )
720                 ret = true;
721         }
722     }
723 
724     if( !ret )
725         return nullptr;
726 
727     SCENEGRAPH* scene = (SCENEGRAPH*)data.scene;
728 
729     // DEBUG: WRITE OUT VRML2 FILE TO CONFIRM STRUCTURE
730 #if ( defined( DEBUG_OCE ) && DEBUG_OCE > 3 )
731     if( data.scene )
732     {
733         wxFileName fn( wxString::FromUTF8Unchecked( filename ) );
734         wxString output;
735 
736         if( FMT_STEP == modelFmt )
737             output = wxT( "_step-" );
738         else
739             output = wxT( "_iges-" );
740 
741         output.append( fn.GetName() );
742         output.append( wxT( ".wrl" ) );
743         S3D::WriteVRML( output.ToUTF8(), true, data.scene, true, true );
744     }
745 #endif
746 
747     // set to NULL to prevent automatic destruction of the scene data
748     data.scene = nullptr;
749 
750     return scene;
751 }
752 
753 
processShell(const TopoDS_Shape & shape,DATA & data,SGNODE * parent,std::vector<SGNODE * > * items,Quantity_Color * color)754 bool processShell( const TopoDS_Shape& shape, DATA& data, SGNODE* parent,
755                    std::vector< SGNODE* >* items, Quantity_Color* color )
756 {
757     TopoDS_Iterator it;
758     bool ret = false;
759 
760     wxLogTrace( MASK_OCE, "Processing shell" );
761     for( it.Initialize( shape, false, false ); it.More(); it.Next() )
762     {
763         const TopoDS_Face& face = TopoDS::Face( it.Value() );
764 
765         if( processFace( face, data, parent, items, color ) )
766             ret = true;
767     }
768 
769     return ret;
770 }
771 
772 
processSolid(const TopoDS_Shape & shape,DATA & data,SGNODE * parent,std::vector<SGNODE * > * items)773 bool processSolid( const TopoDS_Shape& shape, DATA& data, SGNODE* parent,
774                    std::vector< SGNODE* >* items )
775 {
776     TDF_Label label;
777     data.hasSolid = true;
778     std::string partID;
779     Quantity_Color col;
780     Quantity_Color* lcolor = nullptr;
781 
782     wxLogTrace( MASK_OCE, "Processing solid" );
783 
784     // Search the whole model first to make sure something exists (may or may not have color)
785     if( !data.m_assy->Search( shape, label ) )
786     {
787         static int i = 0;
788         std::ostringstream ostr;
789         ostr << "KMISC_" << i++;
790         partID = ostr.str();
791     }
792     else
793     {
794         bool found_color = false;
795 
796         if( getColor( data, label, col ) )
797         {
798             found_color = true;
799             lcolor = &col;
800         }
801 
802         // If the top-level label doesn't have the color information, search components
803         if( !found_color )
804         {
805             if( data.m_assy->Search( shape, label, Standard_False, Standard_True, Standard_True ) &&
806                 getColor( data, label, col ) )
807             {
808                 found_color = true;
809                 lcolor = &col;
810             }
811         }
812 
813         // If the components do not have color information, search all components without location
814         if( !found_color )
815         {
816             if( data.m_assy->Search( shape, label, Standard_False, Standard_False,
817                                      Standard_True ) &&
818                 getColor( data, label, col ) )
819             {
820                 found_color = true;
821                 lcolor = &col;
822             }
823         }
824 
825         // Our last chance to find the color looks for color as a subshape of top-level simple
826         // shapes.
827         if( !found_color )
828         {
829             if( data.m_assy->Search( shape, label, Standard_False, Standard_False,
830                                      Standard_False ) &&
831                 getColor( data, label, col ) )
832             {
833                 found_color = true;
834                 lcolor = &col;
835             }
836         }
837 
838         getTag( label, partID );
839     }
840 
841     TopoDS_Iterator it;
842     IFSG_TRANSFORM childNode( parent );
843     SGNODE* pptr = childNode.GetRawPtr();
844     bool ret = false;
845 
846 
847     std::vector< SGNODE* >* component = nullptr;
848 
849     if( !partID.empty() )
850         data.GetShape( partID, component );
851 
852     if( component )
853     {
854         addItems( pptr, component );
855 
856         if( nullptr != items )
857             items->push_back( pptr );
858     }
859 
860     // instantiate the solid
861     std::vector< SGNODE* > itemList;
862 
863     for( it.Initialize( shape, false, false ); it.More(); it.Next() )
864     {
865         const TopoDS_Shape& subShape = it.Value();
866 
867         if( subShape.ShapeType() == TopAbs_SHELL )
868         {
869             if( processShell( subShape, data, pptr, &itemList, lcolor ) )
870                 ret = true;
871         }
872         else
873         {
874             wxLogTrace( MASK_OCE, "Unsupported subshape in solid" );
875         }
876     }
877 
878     if( !ret )
879         childNode.Destroy();
880     else if( nullptr != items )
881         items->push_back( pptr );
882 
883     return ret;
884 }
885 
886 
processLabel(const TDF_Label & aLabel,DATA & aData,SGNODE * aParent,std::vector<SGNODE * > * aItems)887 bool processLabel( const TDF_Label& aLabel, DATA& aData, SGNODE* aParent,
888                    std::vector<SGNODE*>* aItems )
889 {
890     std::string labelTag;
891 
892     if( wxLog::IsAllowedTraceMask( MASK_OCE ) )
893     {
894         // can be expensive, guard it if we arent logging
895         getTag( aLabel, labelTag );
896     }
897 
898     wxLogTrace( MASK_OCE, "Processing label %s", labelTag );
899 
900     TopoDS_Shape originalShape;
901     TDF_Label shapeLabel = aLabel;
902 
903     if( !aData.m_assy->GetShape( shapeLabel, originalShape ) )
904     {
905         return false;
906     }
907 
908     TopoDS_Shape shape = originalShape;
909 
910     if( aData.m_assy->IsReference( aLabel ) )
911     {
912         wxLogTrace( MASK_OCE, "Label %s is ref, trying to pull up referred label", labelTag );
913 
914         if( !aData.m_assy->GetReferredShape( aLabel, shapeLabel ) )
915         {
916             return false;
917         }
918 
919         labelTag = static_cast<int>( shapeLabel.Tag() );
920        // wxLogTrace( MASK_OCE, "Label %s referred", labelTag );
921 
922         if( !aData.m_assy->GetShape( shapeLabel, shape ) )
923         {
924             return false;
925         }
926     }
927 
928     // Now let's see if the original label has a location
929     // Labels can be used to place copies of other labels at a specific location
930     IFSG_TRANSFORM         childNode( aParent );
931     SGNODE*                pptr = childNode.GetRawPtr();
932     const TopLoc_Location& loc = originalShape.Location();
933 
934     if( !loc.IsIdentity() )
935     {
936         wxLogTrace( MASK_OCE, "Label %d has location", static_cast<int>( aLabel.Tag() ) );
937         gp_Trsf T = loc.Transformation();
938         gp_XYZ  coord = T.TranslationPart();
939         childNode.SetTranslation( SGPOINT( coord.X(), coord.Y(), coord.Z() ) );
940         wxLogTrace( MASK_OCE, "Translation %f, %f, %f", coord.X(), coord.Y(), coord.Z() );
941         gp_XYZ        axis;
942         Standard_Real angle;
943 
944         if( T.GetRotation( axis, angle ) )
945         {
946             childNode.SetRotation( SGVECTOR( axis.X(), axis.Y(), axis.Z() ), angle );
947             wxLogTrace( MASK_OCE, "Rotation %f, %f, %f, angle %f", axis.X(), axis.Y(), axis.Z(),
948                         angle );
949         }
950     }
951 
952     TopAbs_ShapeEnum stype = shape.ShapeType();
953     bool             ret = false;
954     aData.hasSolid = false;
955 
956     switch( stype )
957     {
958     case TopAbs_COMPOUND:
959         {
960             // assemblies will report a shape type of compound which isn't what we are after
961             // we will still process the children of assemblies but they should just be label references to the actual shapes
962             if( !aData.m_assy->IsAssembly( shapeLabel ) )
963             {
964                 TopExp_Explorer xp;
965 
966                 for( xp.Init( shape, TopAbs_SOLID ); xp.More(); xp.Next() )
967                 {
968                     processSolid( xp.Current(), aData, pptr, aItems );
969                     ret = true;
970                 }
971 
972                 for( xp.Init( shape, TopAbs_SHELL, TopAbs_SOLID ); xp.More(); xp.Next() )
973                 {
974                     processShell( xp.Current(), aData, pptr, aItems, nullptr );
975                     ret = true;
976                 }
977             }
978         }
979         break;
980 
981     case TopAbs_SOLID:
982         if( processSolid( shape, aData, pptr, aItems ) )
983             ret = true;
984 
985         break;
986 
987     case TopAbs_SHELL:
988         if( processShell( shape, aData, pptr, aItems, nullptr ) )
989             ret = true;
990 
991         break;
992 
993     case TopAbs_FACE:
994         if( processFace( TopoDS::Face( shape ), aData, pptr, aItems, nullptr ) )
995             ret = true;
996 
997         break;
998 
999     default:
1000         break;
1001     }
1002 
1003     if( nullptr != aItems )
1004         aItems->push_back( pptr );
1005 
1006     if( shapeLabel.HasChild() )
1007     {
1008         wxLogTrace( MASK_OCE, "Label %s has children", labelTag );
1009         TDF_ChildIterator it;
1010         for( it.Initialize( shapeLabel ); it.More(); it.Next() )
1011         {
1012             if( processLabel( it.Value(), aData, pptr, aItems ) )
1013                 ret = true;
1014         }
1015     }
1016 
1017     return ret;
1018 }
1019 
1020 
processFace(const TopoDS_Face & face,DATA & data,SGNODE * parent,std::vector<SGNODE * > * items,Quantity_Color * color)1021 bool processFace( const TopoDS_Face& face, DATA& data, SGNODE* parent,
1022                   std::vector< SGNODE* >* items, Quantity_Color* color )
1023 {
1024     if( Standard_True == face.IsNull() )
1025         return false;
1026 
1027     bool reverse = ( face.Orientation() == TopAbs_REVERSED );
1028     SGNODE* ashape = nullptr;
1029     std::string partID;
1030     TDF_Label label;
1031 
1032     bool useBothSides = false;
1033 
1034     // for IGES renderBoth = TRUE; for STEP if a shell or face is not a descendant
1035     // of a SOLID then hasSolid = false and we must render both sides
1036     if( data.renderBoth || !data.hasSolid )
1037         useBothSides = true;
1038 
1039     if( data.m_assy->FindShape( face, label, Standard_False ) )
1040         getTag( label, partID );
1041 
1042     if( !partID.empty() )
1043         ashape = data.GetFace( partID );
1044 
1045     if( ashape )
1046     {
1047         if( nullptr == S3D::GetSGNodeParent( ashape ) )
1048             S3D::AddSGNodeChild( parent, ashape );
1049         else
1050             S3D::AddSGNodeRef( parent, ashape );
1051 
1052         if( nullptr != items )
1053             items->push_back( ashape );
1054 
1055         if( useBothSides )
1056         {
1057             std::string id2 = partID;
1058             id2.append( "b" );
1059             SGNODE* shapeB = data.GetFace( id2 );
1060 
1061             if( nullptr == S3D::GetSGNodeParent( shapeB ) )
1062                 S3D::AddSGNodeChild( parent, shapeB );
1063             else
1064                 S3D::AddSGNodeRef( parent, shapeB );
1065 
1066             if( nullptr != items )
1067                 items->push_back( shapeB );
1068         }
1069 
1070         return true;
1071     }
1072 
1073     TopLoc_Location loc;
1074     Standard_Boolean isTessellate (Standard_False);
1075     Handle( Poly_Triangulation ) triangulation = BRep_Tool::Triangulation( face, loc );
1076 
1077     if( triangulation.IsNull() || triangulation->Deflection() > USER_PREC + Precision::Confusion() )
1078         isTessellate = Standard_True;
1079 
1080     if( isTessellate )
1081     {
1082         BRepMesh_IncrementalMesh IM(face, USER_PREC, Standard_False, USER_ANGLE );
1083         triangulation = BRep_Tool::Triangulation( face, loc );
1084     }
1085 
1086     if( triangulation.IsNull() == Standard_True )
1087         return false;
1088 
1089     Quantity_Color lcolor;
1090 
1091     // check for a face color; this has precedence over SOLID colors
1092     do
1093     {
1094         TDF_Label L;
1095 
1096         if( data.m_color->ShapeTool()->Search( face, L ) )
1097         {
1098             if( data.m_color->GetColor( L, XCAFDoc_ColorGen, lcolor )
1099                 || data.m_color->GetColor( L, XCAFDoc_ColorCurv, lcolor )
1100                 || data.m_color->GetColor( L, XCAFDoc_ColorSurf, lcolor ) )
1101                 color = &lcolor;
1102         }
1103     } while( 0 );
1104 
1105     SGNODE* ocolor = data.GetColor( color );
1106 
1107     // create a SHAPE and attach the color and data,
1108     // then attach the shape to the parent and return TRUE
1109     IFSG_SHAPE vshape( true );
1110     IFSG_FACESET vface( vshape );
1111     IFSG_COORDS vcoords( vface );
1112     IFSG_COORDINDEX coordIdx( vface );
1113 
1114     if( nullptr == S3D::GetSGNodeParent( ocolor ) )
1115         S3D::AddSGNodeChild( vshape.GetRawPtr(), ocolor );
1116     else
1117         S3D::AddSGNodeRef( vshape.GetRawPtr(), ocolor );
1118 
1119     std::vector< SGPOINT > vertices;
1120     std::vector< int > indices;
1121     std::vector< int > indices2;
1122     gp_Trsf tx;
1123 
1124     for( int i = 1; i <= triangulation->NbNodes(); i++ )
1125     {
1126         gp_XYZ v( triangulation->Node(i).Coord() );
1127         vertices.emplace_back( v.X(), v.Y(), v.Z() );
1128     }
1129 
1130     for( int i = 1; i <= triangulation->NbTriangles(); i++ )
1131     {
1132         int a, b, c;
1133         triangulation->Triangle(i).Get(a, b, c);
1134         a--;
1135 
1136         if( reverse )
1137         {
1138             int tmp = b - 1;
1139             b = c - 1;
1140             c = tmp;
1141         }
1142         else
1143         {
1144             b--;
1145             c--;
1146         }
1147 
1148         indices.push_back( a );
1149         indices.push_back( b );
1150         indices.push_back( c );
1151 
1152         if( useBothSides )
1153         {
1154             indices2.push_back( b );
1155             indices2.push_back( a );
1156             indices2.push_back( c );
1157         }
1158     }
1159 
1160     vcoords.SetCoordsList( vertices.size(), &vertices[0] );
1161     coordIdx.SetIndices( indices.size(), &indices[0] );
1162     vface.CalcNormals( nullptr );
1163     vshape.SetParent( parent );
1164 
1165     if( !partID.empty() )
1166         data.faces.insert( std::pair< std::string, SGNODE* >( partID, vshape.GetRawPtr() ) );
1167 
1168     // The outer surface of an IGES model is indeterminate so
1169     // we must render both sides of a surface.
1170     if( useBothSides )
1171     {
1172         std::string id2 = partID;
1173         id2.append( "b" );
1174         IFSG_SHAPE vshape2( true );
1175         IFSG_FACESET vface2( vshape2 );
1176         IFSG_COORDS vcoords2( vface2 );
1177         IFSG_COORDINDEX coordIdx2( vface2 );
1178         S3D::AddSGNodeRef( vshape2.GetRawPtr(), ocolor );
1179 
1180         vcoords2.SetCoordsList( vertices.size(), &vertices[0] );
1181         coordIdx2.SetIndices( indices2.size(), &indices2[0] );
1182         vface2.CalcNormals( nullptr );
1183         vshape2.SetParent( parent );
1184 
1185         if( !partID.empty() )
1186             data.faces.insert( std::pair< std::string, SGNODE* >( id2, vshape2.GetRawPtr() ) );
1187     }
1188 
1189     return true;
1190 }
1191