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