kicad-source/plugins/3d/oce/loadmodel.cpp
Seth Hillbrand a38c2aad1f ADDED: Support compressed STEP and VRML files
This adds support for opening .stpZ, step.gz and .wrz files where the
files have been compressed using ZIP or gzip according to the "standard"
published by the MBx volunteer forum at
https://www.cax-if.org/documents/rec_prac_file_compression_v12.pdf

Fixes https://gitlab.com/kicad/code/kicad/issues/2479
2020-08-19 03:20:30 +00:00

959 lines
25 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
* Copyright (C) 2020 KiCad Developers, see CHANGELOG.TXT for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <cstring>
#include <map>
#include <vector>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <wx/string.h>
#include <wx/wfstream.h>
#include <decompress.hpp>
#include <TDocStd_Document.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Shape.hxx>
#include <Quantity_Color.hxx>
#include <XCAFApp_Application.hxx>
#include <AIS_Shape.hxx>
#include <IGESControl_Reader.hxx>
#include <IGESCAFControl_Reader.hxx>
#include <Interface_Static.hxx>
#include <STEPControl_Reader.hxx>
#include <STEPCAFControl_Reader.hxx>
#include <XCAFDoc_DocumentTool.hxx>
#include <XCAFDoc_ColorTool.hxx>
#include <XCAFDoc_ShapeTool.hxx>
#include <BRep_Tool.hxx>
#include <BRepMesh_IncrementalMesh.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Shape.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Compound.hxx>
#include <TopExp_Explorer.hxx>
#include <Quantity_Color.hxx>
#include <Poly_Triangulation.hxx>
#include <Poly_PolygonOnTriangulation.hxx>
#include <Precision.hxx>
#include <TDF_LabelSequence.hxx>
#include <TDF_ChildIterator.hxx>
#include "plugins/3dapi/ifsg_all.h"
// log mask for wxLogTrace
#define MASK_OCE "PLUGIN_OCE"
// precision for mesh creation; 0.07 should be good enough for ECAD viewing
#define USER_PREC (0.14)
// angular deflection for meshing
// 10 deg (36 faces per circle) = 0.17453293
// 20 deg (18 faces per circle) = 0.34906585
// 30 deg (12 faces per circle) = 0.52359878
#define USER_ANGLE (0.52359878)
typedef std::map< Standard_Real, SGNODE* > COLORMAP;
typedef std::map< std::string, SGNODE* > FACEMAP;
typedef std::map< std::string, std::vector< SGNODE* > > NODEMAP;
typedef std::pair< std::string, std::vector< SGNODE* > > NODEITEM;
struct DATA;
bool processNode( const TopoDS_Shape& shape, DATA& data, SGNODE* parent,
std::vector< SGNODE* >* items );
bool processComp( const TopoDS_Shape& shape, DATA& data, SGNODE* parent,
std::vector< SGNODE* >* items );
bool processFace( const TopoDS_Face& face, DATA& data, SGNODE* parent,
std::vector< SGNODE* >* items, Quantity_Color* color );
struct DATA
{
Handle( TDocStd_Document ) m_doc;
Handle( XCAFDoc_ColorTool ) m_color;
Handle( XCAFDoc_ShapeTool ) m_assy;
SGNODE* scene;
SGNODE* defaultColor;
Quantity_Color refColor;
NODEMAP shapes; // SGNODE lists representing a TopoDS_SOLID / COMPOUND
COLORMAP colors; // SGAPPEARANCE nodes
FACEMAP faces; // SGSHAPE items representing a TopoDS_FACE
bool renderBoth; // set TRUE if we're processing IGES
bool hasSolid; // set TRUE if there is no parent SOLID
DATA()
{
scene = NULL;
defaultColor = NULL;
refColor.SetValues( Quantity_NOC_BLACK );
renderBoth = false;
hasSolid = false;
}
~DATA()
{
// destroy any colors with no parent
if( !colors.empty() )
{
COLORMAP::iterator sC = colors.begin();
COLORMAP::iterator eC = colors.end();
while( sC != eC )
{
if( NULL == S3D::GetSGNodeParent( sC->second ) )
S3D::DestroyNode( sC->second );
++sC;
}
colors.clear();
}
if( defaultColor && NULL == S3D::GetSGNodeParent( defaultColor ) )
S3D::DestroyNode(defaultColor);
// destroy any faces with no parent
if( !faces.empty() )
{
FACEMAP::iterator sF = faces.begin();
FACEMAP::iterator eF = faces.end();
while( sF != eF )
{
if( NULL == S3D::GetSGNodeParent( sF->second ) )
S3D::DestroyNode( sF->second );
++sF;
}
faces.clear();
}
// destroy any shapes with no parent
if( !shapes.empty() )
{
NODEMAP::iterator sS = shapes.begin();
NODEMAP::iterator eS = shapes.end();
while( sS != eS )
{
std::vector< SGNODE* >::iterator sV = sS->second.begin();
std::vector< SGNODE* >::iterator eV = sS->second.end();
while( sV != eV )
{
if( NULL == S3D::GetSGNodeParent( *sV ) )
S3D::DestroyNode( *sV );
++sV;
}
sS->second.clear();
++sS;
}
shapes.clear();
}
if( scene )
S3D::DestroyNode(scene);
return;
}
// find collection of tagged nodes
bool GetShape( const std::string& id, std::vector< SGNODE* >*& listPtr )
{
listPtr = NULL;
NODEMAP::iterator item;
item = shapes.find( id );
if( item == shapes.end() )
return false;
listPtr = &item->second;
return true;
}
// find collection of tagged nodes
SGNODE* GetFace( const std::string& id )
{
FACEMAP::iterator item;
item = faces.find( id );
if( item == faces.end() )
return NULL;
return item->second;
}
// return color if found; if not found, create SGAPPEARANCE
SGNODE* GetColor( Quantity_Color* colorObj )
{
if( NULL == colorObj )
{
if( defaultColor )
return defaultColor;
IFSG_APPEARANCE app( true );
app.SetShininess( 0.05 );
app.SetSpecular( 0.04, 0.04, 0.04 );
app.SetAmbient( 0.1, 0.1, 0.1 );
app.SetDiffuse( 0.6,0.6, 0.6 );
defaultColor = app.GetRawPtr();
return defaultColor;
}
Standard_Real id = colorObj->Distance( refColor );
std::map< Standard_Real, SGNODE* >::iterator item;
item = colors.find( id );
if( item != colors.end() )
return item->second;
IFSG_APPEARANCE app( true );
app.SetShininess( 0.1 );
app.SetSpecular( 0.12, 0.12, 0.12 );
app.SetAmbient( 0.1, 0.1, 0.1 );
app.SetDiffuse( colorObj->Red(), colorObj->Green(), colorObj->Blue() );
colors.insert( std::pair< Standard_Real, SGNODE* >( id, app.GetRawPtr() ) );
return app.GetRawPtr();
}
};
enum FormatType
{
FMT_NONE = 0,
FMT_STEP,
FMT_STPZ,
FMT_IGES
};
FormatType fileType( const char* aFileName )
{
wxFileName fname( wxString::FromUTF8Unchecked( aFileName ) );
wxFileInputStream ifile( fname.GetFullPath() );
if( !ifile.IsOk() )
return FMT_NONE;
if( fname.GetExt().MakeUpper().EndsWith( "STPZ" ) ||
fname.GetExt().MakeUpper().EndsWith( "GZ" ) )
return FMT_STPZ;
char iline[82];
memset( iline, 0, 82 );
ifile.Read( iline, 82 );
iline[81] = 0; // ensure NULL termination when string is too long
// check for STEP in Part 21 format
// (this can give false positives since Part 21 is not exclusively STEP)
if( !strncmp( iline, "ISO-10303-21;", 13 ) )
return FMT_STEP;
std::string fstr = iline;
// check for STEP in XML format
// (this can give both false positive and false negatives)
if( fstr.find( "urn:oid:1.0.10303." ) != std::string::npos )
return FMT_STEP;
// Note: this is a very simple test which can yield false positives; the only
// sure method for determining if a file *not* an IGES model is to attempt
// to load it.
if( iline[72] == 'S' && ( iline[80] == 0 || iline[80] == 13 || iline[80] == 10 ) )
return FMT_IGES;
return FMT_NONE;
}
void getTag( TDF_Label& label, std::string& aTag )
{
if( label.IsNull() )
return;
std::string rtag; // tag in reverse
aTag.clear();
int id = label.Tag();
std::ostringstream ostr;
ostr << id;
rtag = ostr.str();
ostr.str( "" );
ostr.clear();
TDF_Label nlab = label.Father();
while( !nlab.IsNull() )
{
rtag.append( 1, ':' );
id = nlab.Tag();
ostr << id;
rtag.append( ostr.str() );
ostr.str( "" );
ostr.clear();
nlab = nlab.Father();
};
std::string::reverse_iterator bI = rtag.rbegin();
std::string::reverse_iterator eI = rtag.rend();
while( bI != eI )
{
aTag.append( 1, *bI );
++bI;
}
return;
}
bool getColor( DATA& data, TDF_Label label, Quantity_Color& color )
{
while( true )
{
if( data.m_color->GetColor( label, XCAFDoc_ColorGen, color ) )
return true;
else if( data.m_color->GetColor( label, XCAFDoc_ColorSurf, color ) )
return true;
else if( data.m_color->GetColor( label, XCAFDoc_ColorCurv, color ) )
return true;
label = label.Father();
if( label.IsNull() )
break;
};
return false;
}
void addItems( SGNODE* parent, std::vector< SGNODE* >* lp )
{
if( NULL == lp )
return;
std::vector< SGNODE* >::iterator sL = lp->begin();
std::vector< SGNODE* >::iterator eL = lp->end();
SGNODE* item;
while( sL != eL )
{
item = *sL;
if( NULL == S3D::GetSGNodeParent( item ) )
S3D::AddSGNodeChild( parent, item );
else
S3D::AddSGNodeRef( parent, item );
++sL;
}
return;
}
bool readIGES( Handle(TDocStd_Document)& m_doc, const char* fname )
{
IGESCAFControl_Reader reader;
IFSelect_ReturnStatus stat = reader.ReadFile( fname );
reader.PrintCheckLoad( Standard_False, IFSelect_ItemsByEntity );
if( stat != IFSelect_RetDone )
return false;
// Enable file-defined shape precision
if( !Interface_Static::SetIVal( "read.precision.mode", 0 ) )
return false;
// set other translation options
reader.SetColorMode(true); // use model colors
reader.SetNameMode(false); // don't use IGES label names
reader.SetLayerMode(false); // ignore LAYER data
if ( !reader.Transfer( m_doc ) )
return false;
// are there any shapes to translate?
if( reader.NbShapes() < 1 )
return false;
return true;
}
bool readSTEP( Handle(TDocStd_Document)& m_doc, const char* fname )
{
STEPCAFControl_Reader reader;
IFSelect_ReturnStatus stat = reader.ReadFile( fname );
if( stat != IFSelect_RetDone )
return false;
// Enable user-defined shape precision
if( !Interface_Static::SetIVal( "read.precision.mode", 1 ) )
return false;
// Set the shape conversion precision to USER_PREC (default 0.0001 has too many triangles)
if( !Interface_Static::SetRVal( "read.precision.val", USER_PREC ) )
return false;
// set other translation options
reader.SetColorMode(true); // use model colors
reader.SetNameMode(false); // don't use label names
reader.SetLayerMode(false); // ignore LAYER data
if ( !reader.Transfer( m_doc ) )
{
m_doc->Close();
return false;
}
// are there any shapes to translate?
if( reader.NbRootsForTransfer() < 1 )
return false;
return true;
}
bool readSTEPZ( Handle(TDocStd_Document)& m_doc, const char* aFileName )
{
wxFileName fname( wxString::FromUTF8Unchecked( aFileName ) );
wxFileInputStream ifile( fname.GetFullPath() );
wxFileName outFile( fname );
outFile.SetPath( wxStandardPaths::Get().GetTempDir() );
outFile.SetExt( "STEP" );
wxFileOffset size = ifile.GetLength();
if( size == wxInvalidOffset )
return false;
{
wxFileOutputStream ofile( outFile.GetFullPath() );
if( !ofile.IsOk() )
return false;
char *buffer = new char[size];
ifile.Read( buffer, size);
std::string expanded = gzip::decompress( buffer, size );
delete buffer;
ofile.Write( expanded.data(), expanded.size() );
ofile.Close();
}
bool retval = readSTEP( m_doc, outFile.GetFullPath() );
// Cleanup our temporary file
wxRemoveFile( outFile.GetFullPath() );
return retval;
}
SCENEGRAPH* LoadModel( char const* filename )
{
DATA data;
Handle(XCAFApp_Application) m_app = XCAFApp_Application::GetApplication();
m_app->NewDocument( "MDTV-XCAF", data.m_doc );
FormatType modelFmt = fileType( filename );
switch( modelFmt )
{
case FMT_IGES:
data.renderBoth = true;
if( !readIGES( data.m_doc, filename ) )
return NULL;
break;
case FMT_STEP:
if( !readSTEP( data.m_doc, filename ) )
return NULL;
break;
case FMT_STPZ:
if( !readSTEPZ( data.m_doc, filename ) )
return NULL;
break;
default:
return NULL;
break;
}
data.m_assy = XCAFDoc_DocumentTool::ShapeTool( data.m_doc->Main() );
data.m_color = XCAFDoc_DocumentTool::ColorTool( data.m_doc->Main() );
// retrieve all free shapes
TDF_LabelSequence frshapes;
data.m_assy->GetFreeShapes( frshapes );
int nshapes = frshapes.Length();
int id = 1;
bool ret = false;
// create the top level SG node
IFSG_TRANSFORM topNode( true );
data.scene = topNode.GetRawPtr();
while( id <= nshapes )
{
TopoDS_Shape shape = data.m_assy->GetShape( frshapes.Value(id) );
if ( !shape.IsNull() && processNode( shape, data, data.scene, NULL ) )
ret = true;
++id;
};
if( !ret )
return NULL;
SCENEGRAPH* scene = (SCENEGRAPH*)data.scene;
// DEBUG: WRITE OUT VRML2 FILE TO CONFIRM STRUCTURE
#if ( defined( DEBUG_OCE ) && DEBUG_OCE > 3 )
if( data.scene )
{
wxFileName fn( wxString::FromUTF8Unchecked( filename ) );
wxString output;
if( FMT_STEP == modelFmt )
output = wxT( "_step-" );
else
output = wxT( "_iges-" );
output.append( fn.GetName() );
output.append( wxT(".wrl") );
S3D::WriteVRML( output.ToUTF8(), true, data.scene, true, true );
}
#endif
// set to NULL to prevent automatic destruction of the scene data
data.scene = NULL;
return scene;
}
bool processShell( const TopoDS_Shape& shape, DATA& data, SGNODE* parent,
std::vector< SGNODE* >* items, Quantity_Color* color )
{
TopoDS_Iterator it;
bool ret = false;
for( it.Initialize( shape, false, false ); it.More(); it.Next() )
{
const TopoDS_Face& face = TopoDS::Face( it.Value() );
if( processFace( face, data, parent, items, color ) )
ret = true;
}
return ret;
}
bool processSolid( const TopoDS_Shape& shape, DATA& data, SGNODE* parent,
std::vector< SGNODE* >* items )
{
TDF_Label label = data.m_assy->FindShape( shape, Standard_False );
data.hasSolid = true;
std::string partID;
Quantity_Color col;
Quantity_Color* lcolor = NULL;
if( label.IsNull() )
{
static int i = 0;
std::ostringstream ostr;
ostr << "KMISC_" << i++;
partID = ostr.str();
}
else
{
getTag( label, partID );
if( getColor( data, label, col ) )
lcolor = &col;
}
TopoDS_Iterator it;
IFSG_TRANSFORM childNode( parent );
SGNODE* pptr = childNode.GetRawPtr();
const TopLoc_Location& loc = shape.Location();
bool ret = false;
if( !loc.IsIdentity() )
{
gp_Trsf T = loc.Transformation();
gp_XYZ coord = T.TranslationPart();
childNode.SetTranslation( SGPOINT( coord.X(), coord.Y(), coord.Z() ) );
gp_XYZ axis;
Standard_Real angle;
if( T.GetRotation( axis, angle ) )
childNode.SetRotation( SGVECTOR( axis.X(), axis.Y(), axis.Z() ), angle );
}
std::vector< SGNODE* >* component = NULL;
if( !partID.empty() )
data.GetShape( partID, component );
if( component )
{
addItems( pptr, component );
if( NULL != items )
items->push_back( pptr );
}
// instantiate the solid
std::vector< SGNODE* > itemList;
for( it.Initialize( shape, false, false ); it.More(); it.Next() )
{
const TopoDS_Shape& subShape = it.Value();
if( processShell( subShape, data, pptr, &itemList, lcolor ) )
ret = true;
}
if( !ret )
childNode.Destroy();
else if( NULL != items )
items->push_back( pptr );
return ret;
}
bool processComp( const TopoDS_Shape& shape, DATA& data, SGNODE* parent,
std::vector< SGNODE* >* items )
{
TopoDS_Iterator it;
IFSG_TRANSFORM childNode( parent );
SGNODE* pptr = childNode.GetRawPtr();
const TopLoc_Location& loc = shape.Location();
bool ret = false;
if( !loc.IsIdentity() )
{
gp_Trsf T = loc.Transformation();
gp_XYZ coord = T.TranslationPart();
childNode.SetTranslation( SGPOINT( coord.X(), coord.Y(), coord.Z() ) );
gp_XYZ axis;
Standard_Real angle;
if( T.GetRotation( axis, angle ) )
childNode.SetRotation( SGVECTOR( axis.X(), axis.Y(), axis.Z() ), angle );
}
for( it.Initialize( shape, false, false ); it.More(); it.Next() )
{
const TopoDS_Shape& subShape = it.Value();
TopAbs_ShapeEnum stype = subShape.ShapeType();
data.hasSolid = false;
switch( stype )
{
case TopAbs_COMPOUND:
case TopAbs_COMPSOLID:
if( processComp( subShape, data, pptr, items ) )
ret = true;
break;
case TopAbs_SOLID:
if( processSolid( subShape, data, pptr, items ) )
ret = true;
break;
case TopAbs_SHELL:
if( processShell( subShape, data, pptr, items, NULL ) )
ret = true;
break;
case TopAbs_FACE:
if( processFace( TopoDS::Face( subShape ), data, pptr, items, NULL ) )
ret = true;
break;
default:
break;
}
}
if( !ret )
childNode.Destroy();
else if( NULL != items )
items->push_back( pptr );
return ret;
}
bool processNode( const TopoDS_Shape& shape, DATA& data, SGNODE* parent,
std::vector< SGNODE* >* items )
{
TopAbs_ShapeEnum stype = shape.ShapeType();
bool ret = false;
data.hasSolid = false;
switch( stype )
{
case TopAbs_COMPOUND:
case TopAbs_COMPSOLID:
if( processComp( shape, data, parent, items ) )
ret = true;
break;
case TopAbs_SOLID:
if( processSolid( shape, data, parent, items ) )
ret = true;
break;
case TopAbs_SHELL:
if( processShell( shape, data, parent, items, NULL ) )
ret = true;
break;
case TopAbs_FACE:
if( processFace( TopoDS::Face( shape ), data, parent, items, NULL ) )
ret = true;
break;
default:
break;
}
return ret;
}
bool processFace( const TopoDS_Face& face, DATA& data, SGNODE* parent,
std::vector< SGNODE* >* items, Quantity_Color* color )
{
if( Standard_True == face.IsNull() )
return false;
bool reverse = ( face.Orientation() == TopAbs_REVERSED );
SGNODE* ashape = NULL;
std::string partID;
TDF_Label label;
bool useBothSides = false;
// for IGES renderBoth = TRUE; for STEP if a shell or face is not a descendant
// of a SOLID then hasSolid = false and we must render both sides
if( data.renderBoth || !data.hasSolid )
useBothSides = true;
if( data.m_assy->FindShape( face, label, Standard_False ) )
getTag( label, partID );
if( !partID.empty() )
ashape = data.GetFace( partID );
if( ashape )
{
if( NULL == S3D::GetSGNodeParent( ashape ) )
S3D::AddSGNodeChild( parent, ashape );
else
S3D::AddSGNodeRef( parent, ashape );
if( NULL != items )
items->push_back( ashape );
if( useBothSides )
{
std::string id2 = partID;
id2.append( "b" );
SGNODE* shapeB = data.GetFace( id2 );
if( NULL == S3D::GetSGNodeParent( shapeB ) )
S3D::AddSGNodeChild( parent, shapeB );
else
S3D::AddSGNodeRef( parent, shapeB );
if( NULL != items )
items->push_back( shapeB );
}
return true;
}
TopLoc_Location loc;
Standard_Boolean isTessellate (Standard_False);
Handle(Poly_Triangulation) triangulation = BRep_Tool::Triangulation( face, loc );
if( triangulation.IsNull() || triangulation->Deflection() > USER_PREC + Precision::Confusion() )
isTessellate = Standard_True;
if (isTessellate)
{
BRepMesh_IncrementalMesh IM(face, USER_PREC, Standard_False, USER_ANGLE );
triangulation = BRep_Tool::Triangulation( face, loc );
}
if( triangulation.IsNull() == Standard_True )
return false;
Quantity_Color lcolor;
// check for a face color; this has precedence over SOLID colors
do
{
TDF_Label L;
if( data.m_color->ShapeTool()->Search( face, L ) )
{
if( data.m_color->GetColor( L, XCAFDoc_ColorGen, lcolor )
|| data.m_color->GetColor( L, XCAFDoc_ColorCurv, lcolor )
|| data.m_color->GetColor( L, XCAFDoc_ColorSurf, lcolor ) )
color = &lcolor;
}
} while( 0 );
SGNODE* ocolor = data.GetColor( color );
// create a SHAPE and attach the color and data,
// then attach the shape to the parent and return TRUE
IFSG_SHAPE vshape( true );
IFSG_FACESET vface( vshape );
IFSG_COORDS vcoords( vface );
IFSG_COORDINDEX coordIdx( vface );
if( NULL == S3D::GetSGNodeParent( ocolor ) )
S3D::AddSGNodeChild( vshape.GetRawPtr(), ocolor );
else
S3D::AddSGNodeRef( vshape.GetRawPtr(), ocolor );
const TColgp_Array1OfPnt& arrPolyNodes = triangulation->Nodes();
const Poly_Array1OfTriangle& arrTriangles = triangulation->Triangles();
std::vector< SGPOINT > vertices;
std::vector< int > indices;
std::vector< int > indices2;
gp_Trsf tx;
for(int i = 1; i <= triangulation->NbNodes(); i++)
{
gp_XYZ v( arrPolyNodes(i).Coord() );
vertices.emplace_back( v.X(), v.Y(), v.Z() );
}
for(int i = 1; i <= triangulation->NbTriangles(); i++)
{
int a, b, c;
arrTriangles( i ).Get( a, b, c );
a--;
if( reverse )
{
int tmp = b - 1;
b = c - 1;
c = tmp;
} else {
b--;
c--;
}
indices.push_back( a );
indices.push_back( b );
indices.push_back( c );
if( useBothSides )
{
indices2.push_back( b );
indices2.push_back( a );
indices2.push_back( c );
}
}
vcoords.SetCoordsList( vertices.size(), &vertices[0] );
coordIdx.SetIndices( indices.size(), &indices[0] );
vface.CalcNormals( NULL );
vshape.SetParent( parent );
if( !partID.empty() )
data.faces.insert( std::pair< std::string,
SGNODE* >( partID, vshape.GetRawPtr() ) );
// The outer surface of an IGES model is indeterminate so
// we must render both sides of a surface.
if( useBothSides )
{
std::string id2 = partID;
id2.append( "b" );
IFSG_SHAPE vshape2( true );
IFSG_FACESET vface2( vshape2 );
IFSG_COORDS vcoords2( vface2 );
IFSG_COORDINDEX coordIdx2( vface2 );
S3D::AddSGNodeRef( vshape2.GetRawPtr(), ocolor );
vcoords2.SetCoordsList( vertices.size(), &vertices[0] );
coordIdx2.SetIndices( indices2.size(), &indices2[0] );
vface2.CalcNormals( NULL );
vshape2.SetParent( parent );
if( !partID.empty() )
data.faces.insert( std::pair< std::string,
SGNODE* >( id2, vshape2.GetRawPtr() ) );
}
return true;
}