RWObj_Reader and RWObj_CafReader - added new classes reading triangulation from OBJ file.
t TKRWMesh
n RWGltf
n RWMesh
+n RWObj
--- /dev/null
+RWObj.cxx
+RWObj.hxx
+RWObj_CafReader.cxx
+RWObj_CafReader.hxx
+RWObj_Material.hxx
+RWObj_MtlReader.cxx
+RWObj_MtlReader.hxx
+RWObj_Reader.cxx
+RWObj_Reader.hxx
+RWObj_SubMesh.hxx
+RWObj_SubMeshReason.hxx
+RWObj_Tools.hxx
+RWObj_TriangulationReader.cxx
+RWObj_TriangulationReader.hxx
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <RWObj.hxx>
+
+#include <RWObj_TriangulationReader.hxx>
+
+//=============================================================================
+//function : Read
+//purpose :
+//=============================================================================
+Handle(Poly_Triangulation) RWObj::ReadFile (const Standard_CString theFile,
+ const Handle(Message_ProgressIndicator)& theProgress)
+{
+ RWObj_TriangulationReader aReader;
+ aReader.SetCreateShapes (Standard_False);
+ aReader.Read (theFile, theProgress);
+ // note that returned bool value is ignored intentionally -- even if something went wrong,
+ // but some data have been read, we at least will return these data
+ return aReader.GetTriangulation();
+}
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWObj_HeaderFile
+#define _RWObj_HeaderFile
+
+#include <Message_ProgressIndicator.hxx>
+#include <OSD_Path.hxx>
+#include <Poly_Triangulation.hxx>
+#include <Standard_Macro.hxx>
+
+//! This class provides methods to read and write triangulation from / to the OBJ files.
+class RWObj
+{
+public:
+
+ //! Read specified OBJ file and returns its content as triangulation.
+ //! In case of error, returns Null handle.
+ Standard_EXPORT static Handle(Poly_Triangulation) ReadFile (const Standard_CString theFile,
+ const Handle(Message_ProgressIndicator)& aProgInd = NULL);
+
+};
+
+#endif // _RWObj_HeaderFile
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <RWObj_CafReader.hxx>
+
+#include <Message_ProgressSentry.hxx>
+
+IMPLEMENT_STANDARD_RTTIEXT(RWObj_CafReader, RWMesh_CafReader)
+
+//================================================================
+// Function : Constructor
+// Purpose :
+//================================================================
+RWObj_CafReader::RWObj_CafReader()
+: myIsSinglePrecision (Standard_False)
+{
+ //myCoordSysConverter.SetInputLengthUnit (-1.0); // length units are undefined within OBJ file
+ // OBJ format does not define coordinate system (apart from mentioning that it is right-handed),
+ // however most files are stored Y-up
+ myCoordSysConverter.SetInputCoordinateSystem (RWMesh_CoordinateSystem_glTF);
+}
+
+//================================================================
+// Function : BindNamedShape
+// Purpose :
+//================================================================
+void RWObj_CafReader::BindNamedShape (const TopoDS_Shape& theShape,
+ const TCollection_AsciiString& theName,
+ const RWObj_Material* theMaterial,
+ const Standard_Boolean theIsRootShape)
+{
+ if (theShape.IsNull())
+ {
+ return;
+ }
+
+ RWMesh_NodeAttributes aShapeAttribs;
+ aShapeAttribs.Name = theName;
+ if (theMaterial != NULL)
+ {
+ aShapeAttribs.Style.SetColorSurf (Quantity_ColorRGBA (theMaterial->DiffuseColor, 1.0f - theMaterial->Transparency));
+ }
+ myAttribMap.Bind (theShape, aShapeAttribs);
+
+ if (theIsRootShape)
+ {
+ myRootShapes.Append (theShape);
+ }
+}
+
+//================================================================
+// Function : createReaderContext
+// Purpose :
+//================================================================
+Handle(RWObj_TriangulationReader) RWObj_CafReader::createReaderContext()
+{
+ Handle(RWObj_TriangulationReader) aReader = new RWObj_TriangulationReader();
+ return aReader;
+}
+
+//================================================================
+// Function : performMesh
+// Purpose :
+//================================================================
+Standard_Boolean RWObj_CafReader::performMesh (const TCollection_AsciiString& theFile,
+ const Handle(Message_ProgressIndicator)& theProgress,
+ const Standard_Boolean theToProbe)
+{
+ Handle(RWObj_TriangulationReader) aCtx = createReaderContext();
+ aCtx->SetSinglePrecision (myIsSinglePrecision);
+ aCtx->SetCreateShapes (Standard_True);
+ aCtx->SetShapeReceiver (this);
+ aCtx->SetTransformation (myCoordSysConverter);
+ aCtx->SetMemoryLimit (myMemoryLimitMiB == -1 ? Standard_Size(-1) : Standard_Size(myMemoryLimitMiB * 1024 * 1024));
+ Standard_Boolean isDone = Standard_False;
+ if (theToProbe)
+ {
+ isDone = aCtx->Probe (theFile.ToCString(), theProgress);
+ }
+ else
+ {
+ isDone = aCtx->Read (theFile.ToCString(), theProgress);
+ }
+ if (!aCtx->FileComments().IsEmpty())
+ {
+ myMetadata.Add ("Comments", aCtx->FileComments());
+ }
+ for (NCollection_IndexedMap<TCollection_AsciiString>::Iterator aFileIter (aCtx->ExternalFiles()); aFileIter.More(); aFileIter.Next())
+ {
+ myExternalFiles.Add (aFileIter.Value());
+ }
+ return isDone;
+}
--- /dev/null
+// Copyright (c) 2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWObj_CafReader_HeaderFile
+#define _RWObj_CafReader_HeaderFile
+
+#include <RWMesh_CafReader.hxx>
+#include <RWObj_TriangulationReader.hxx>
+
+//! The OBJ mesh reader into XDE document.
+class RWObj_CafReader : public RWMesh_CafReader, protected RWObj_IShapeReceiver
+{
+ DEFINE_STANDARD_RTTIEXT(RWObj_CafReader, RWMesh_CafReader)
+public:
+
+ //! Empty constructor.
+ Standard_EXPORT RWObj_CafReader();
+
+ //! Return single precision flag for reading vertex data (coordinates); FALSE by default.
+ Standard_Boolean IsSinglePrecision() const { return myIsSinglePrecision; }
+
+ //! Setup single/double precision flag for reading vertex data (coordinates).
+ void SetSinglePrecision (Standard_Boolean theIsSinglePrecision) { myIsSinglePrecision = theIsSinglePrecision; }
+
+protected:
+
+ //! Read the mesh from specified file.
+ Standard_EXPORT virtual Standard_Boolean performMesh (const TCollection_AsciiString& theFile,
+ const Handle(Message_ProgressIndicator)& theProgress,
+ const Standard_Boolean theToProbe) Standard_OVERRIDE;
+
+protected:
+
+ //! Create reader context.
+ //! Can be overridden by sub-class to read triangulation into application-specific data structures instead of Poly_Triangulation.
+ virtual Handle(RWObj_TriangulationReader) createReaderContext();
+
+ //! @param theShape shape to register
+ //! @param theName shape name
+ //! @param theMaterial shape material
+ //! @param theIsRootShape indicates that this is a root object (free shape)
+ virtual void BindNamedShape (const TopoDS_Shape& theShape,
+ const TCollection_AsciiString& theName,
+ const RWObj_Material* theMaterial,
+ const Standard_Boolean theIsRootShape) Standard_OVERRIDE;
+
+protected:
+
+ Standard_Boolean myIsSinglePrecision; //!< flag for reading vertex data with single or double floating point precision
+
+};
+
+#endif // _RWObj_CafReader_HeaderFile
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2015-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWObj_Material_HeaderFile
+#define _RWObj_Material_HeaderFile
+
+#include <Quantity_Color.hxx>
+#include <TCollection_AsciiString.hxx>
+
+//! Material definition for OBJ file format.
+struct RWObj_Material
+{
+ TCollection_AsciiString Name; //!< material name (identifier) as defined in MTL file
+ TCollection_AsciiString DiffuseTexture; //!< path to the texture image file defining diffuse color
+ TCollection_AsciiString SpecularTexture; //!< path to the texture image file defining specular color
+ TCollection_AsciiString BumpTexture; //!< path to the texture image file defining normal map
+ Quantity_Color AmbientColor;
+ Quantity_Color DiffuseColor;
+ Quantity_Color SpecularColor;
+ Standard_ShortReal Shininess;
+ Standard_ShortReal Transparency;
+
+ RWObj_Material()
+ : AmbientColor (0.1, 0.1, 0.1, Quantity_TOC_RGB),
+ DiffuseColor (0.8, 0.8, 0.8, Quantity_TOC_RGB),
+ SpecularColor(0.2, 0.2, 0.2, Quantity_TOC_RGB),
+ Shininess (1.0f),
+ Transparency (0.0f) {}
+
+};
+
+#endif // _RWObj_Material_HeaderFile
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <RWObj_MtlReader.hxx>
+
+#include <RWObj_Tools.hxx>
+
+#include <Message.hxx>
+#include <Message_Messenger.hxx>
+#include <OSD_File.hxx>
+#include <OSD_OpenFile.hxx>
+#include <OSD_Path.hxx>
+
+namespace
+{
+ //! Try to find a new location of the file relative to specified folder from absolute path.
+ //! @param theAbsolutePath original absolute file path
+ //! @param theNewFoler the new folder to look for the file
+ //! @param theRelativePath result file path relative to theNewFoler
+ //! @return true if relative file has been found
+ static bool findRelativePath (const TCollection_AsciiString& theAbsolutePath,
+ const TCollection_AsciiString& theNewFoler,
+ TCollection_AsciiString& theRelativePath)
+ {
+ TCollection_AsciiString aNewFoler = (theNewFoler.EndsWith ("\\") || theNewFoler.EndsWith ("/"))
+ ? theNewFoler
+ : (theNewFoler + "/");
+
+ TCollection_AsciiString aRelPath;
+ TCollection_AsciiString aPath = theAbsolutePath;
+ for (;;)
+ {
+ TCollection_AsciiString aFolder, aFileName;
+ OSD_Path::FolderAndFileFromPath (aPath, aFolder, aFileName);
+ if (aFolder.IsEmpty()
+ || aFileName.IsEmpty())
+ {
+ return false;
+ }
+
+ if (aRelPath.IsEmpty())
+ {
+ aRelPath = aFileName;
+ }
+ else
+ {
+ aRelPath = aFileName + "/" + aRelPath;
+ }
+
+ if (OSD_File (aNewFoler + aRelPath).Exists())
+ {
+ theRelativePath = aRelPath;
+ return true;
+ }
+ aPath = aFolder;
+ }
+ }
+}
+
+// =======================================================================
+// function : RWObj_MtlReader
+// purpose :
+// =======================================================================
+RWObj_MtlReader::RWObj_MtlReader (NCollection_DataMap<TCollection_AsciiString, RWObj_Material>& theMaterials)
+: myFile (NULL),
+ myMaterials (&theMaterials),
+ myNbLines (0)
+{
+ //
+}
+
+// =======================================================================
+// function : ~RWObj_MtlReader
+// purpose :
+// =======================================================================
+RWObj_MtlReader::~RWObj_MtlReader()
+{
+ if (myFile != NULL)
+ {
+ ::fclose (myFile);
+ }
+}
+
+// =======================================================================
+// function : Read
+// purpose :
+// =======================================================================
+bool RWObj_MtlReader::Read (const TCollection_AsciiString& theFolder,
+ const TCollection_AsciiString& theFile)
+{
+ myPath = theFolder + theFile;
+ myFile = OSD_OpenFile (myPath.ToCString(), "rb");
+ if (myFile == NULL)
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString ("OBJ material file '") + myPath + "' is not found!", Message_Warning);
+ return Standard_False;
+ }
+
+ char aLine[256] = {};
+ TCollection_AsciiString aMatName;
+ RWObj_Material aMat;
+ const Standard_Integer aNbMatOld = myMaterials->Extent();
+ bool hasAspect = false;
+ for (; ::feof (myFile) == 0 && ::fgets (aLine, 255, myFile) != NULL; )
+ {
+ ++myNbLines;
+
+ const char* aPos = aLine;
+
+ // skip spaces
+ for (; IsSpace(*aPos);)
+ {
+ ++aPos;
+ }
+
+ if (*aPos == '#'
+ || *aPos == '\n'
+ || *aPos == '\0')
+ {
+ continue;
+ }
+
+ if (::memcmp (aPos, "newmtl", 6) == 0)
+ {
+ aPos += 7;
+ if (!aMatName.IsEmpty())
+ {
+ if (hasAspect)
+ {
+ aMat.Name = aMatName;
+ }
+ else
+ {
+ // reset incomplete material definition
+ aMat = RWObj_Material();
+ }
+ myMaterials->Bind (aMatName, aMat);
+ hasAspect = false;
+ }
+
+ aMatName = TCollection_AsciiString(aPos);
+ aMat = RWObj_Material();
+ if (!RWObj_Tools::ReadName (aPos, aMatName))
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Empty OBJ material at line ") + myNbLines + " in file " + myPath, Message_Warning);
+ }
+ }
+ else if (::memcmp (aPos, "Ka", 2) == 0
+ && IsSpace (aPos[2]))
+ {
+ aPos += 3;
+ char* aNext = NULL;
+ Graphic3d_Vec3 aColor;
+ RWObj_Tools::ReadVec3 (aPos, aNext, aColor);
+ aPos = aNext;
+ if (validateColor (aColor))
+ {
+ aMat.AmbientColor = Quantity_Color (aColor.r(), aColor.g(), aColor.b(), Quantity_TOC_RGB);
+ hasAspect = true;
+ }
+ }
+ else if (::memcmp (aPos, "Kd", 2) == 0
+ && IsSpace (aPos[2]))
+ {
+ aPos += 3;
+ char* aNext = NULL;
+ Graphic3d_Vec3 aColor;
+ RWObj_Tools::ReadVec3 (aPos, aNext, aColor);
+ aPos = aNext;
+ if (validateColor (aColor))
+ {
+ aMat.DiffuseColor = Quantity_Color (aColor.r(), aColor.g(), aColor.b(), Quantity_TOC_RGB);
+ hasAspect = true;
+ }
+ }
+ else if (::memcmp (aPos, "Ks", 2) == 0
+ && IsSpace (aPos[2]))
+ {
+ aPos += 3;
+ char* aNext = NULL;
+ Graphic3d_Vec3 aColor;
+ RWObj_Tools::ReadVec3 (aPos, aNext, aColor);
+ aPos = aNext;
+ if (validateColor (aColor))
+ {
+ aMat.SpecularColor = Quantity_Color (aColor.r(), aColor.g(), aColor.b(), Quantity_TOC_RGB);
+ }
+ }
+ else if (::memcmp (aPos, "Ns", 2) == 0
+ && IsSpace (aPos[2]))
+ {
+ aPos += 3;
+ char* aNext = NULL;
+ double aSpecular = Strtod (aPos, &aNext);
+ aPos = aNext;
+ if (aSpecular >= 0.0)
+ {
+ aMat.Shininess = (float )Min (aSpecular / 1000.0, 1.0);
+ }
+ }
+ else if (::memcmp (aPos, "Tr", 2) == 0
+ && IsSpace (aPos[2]))
+ {
+ aPos += 3;
+ char* aNext = NULL;
+ double aTransp = Strtod (aPos, &aNext);
+ aPos = aNext;
+ if (validateScalar (aTransp)
+ && aTransp <= 0.99)
+ {
+ aMat.Transparency = (float )aTransp;
+ }
+ }
+ else if (*aPos == 'd' && IsSpace (aPos[1]))
+ {
+ // dissolve
+ aPos += 2;
+ char* aNext = NULL;
+ double anAlpha = Strtod (aPos, &aNext);
+ aPos = aNext;
+ if (validateScalar (anAlpha)
+ && anAlpha >= 0.01)
+ {
+ aMat.Transparency = float(1.0 - anAlpha);
+ }
+ }
+ else if (::memcmp (aPos, "map_Kd", 6) == 0
+ && IsSpace (aPos[6]))
+ {
+ aPos += 7;
+ if (RWObj_Tools::ReadName (aPos, aMat.DiffuseTexture))
+ {
+ processTexturePath (aMat.DiffuseTexture, theFolder);
+ }
+ }
+ else if (::memcmp (aPos, "map_Ks", 6) == 0
+ && IsSpace (aPos[6]))
+ {
+ aPos += 7;
+ if (RWObj_Tools::ReadName (aPos, aMat.SpecularTexture))
+ {
+ processTexturePath (aMat.SpecularTexture, theFolder);
+ }
+ }
+ else if (::memcmp (aPos, "map_Bump", 8) == 0
+ && IsSpace (aPos[8]))
+ {
+ aPos += 9;
+ if (RWObj_Tools::ReadName (aPos, aMat.BumpTexture))
+ {
+ processTexturePath (aMat.BumpTexture, theFolder);
+ }
+ }
+ /*else if (::memcmp (aPos, "illum", 5) == 0)
+ {
+ aPos += 6;
+ char* aNext = NULL;
+ const int aModel = strtol (aPos, &aNext, 10);
+ aPos = aNext;
+ if (aModel < 0 || aModel > 10)
+ {
+ // unknown model
+ }
+ }*/
+ }
+
+ if (!aMatName.IsEmpty())
+ {
+ if (hasAspect)
+ {
+ aMat.Name = aMatName;
+ }
+ else
+ {
+ // reset incomplete material definition
+ aMat = RWObj_Material();
+ }
+ myMaterials->Bind (aMatName, aMat);
+ }
+
+ return myMaterials->Extent() != aNbMatOld;
+}
+
+// =======================================================================
+// function : processTexturePath
+// purpose :
+// =======================================================================
+void RWObj_MtlReader::processTexturePath (TCollection_AsciiString& theTexturePath,
+ const TCollection_AsciiString& theFolder)
+{
+ if (OSD_Path::IsAbsolutePath (theTexturePath.ToCString()))
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("OBJ file specifies absolute path to the texture image file which may be inaccessible on another device\n")
+ + theTexturePath, Message_Warning);
+ if (!OSD_File (theTexturePath).Exists())
+ {
+ // workaround absolute filenames - try to find the same file at the OBJ file location
+ TCollection_AsciiString aRelativePath;
+ if (findRelativePath (theTexturePath, theFolder, aRelativePath))
+ {
+ theTexturePath = theFolder + aRelativePath;
+ }
+ }
+ }
+ else
+ {
+ theTexturePath = theFolder + theTexturePath;
+ }
+}
+
+// =======================================================================
+// function : validateScalar
+// purpose :
+// =======================================================================
+bool RWObj_MtlReader::validateScalar (const Standard_Real theValue)
+{
+ if (theValue < 0.0
+ || theValue > 1.0)
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Invalid scalar in OBJ material at line ") + myNbLines + " in file " + myPath, Message_Warning);
+ return false;
+ }
+ return true;
+}
+
+// =======================================================================
+// function : validateColor
+// purpose :
+// =======================================================================
+bool RWObj_MtlReader::validateColor (const Graphic3d_Vec3& theVec)
+{
+ if (theVec.r() < 0.0f || theVec.r() > 1.0f
+ || theVec.g() < 0.0f || theVec.g() > 1.0f
+ || theVec.b() < 0.0f || theVec.b() > 1.0f)
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Invalid color in OBJ material at line ") + myNbLines + " in file " + myPath, Message_Warning);
+ return false;
+ }
+ return true;
+}
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2015-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWObj_MtlReader_HeaderFile
+#define _RWObj_MtlReader_HeaderFile
+
+#include <Graphic3d_Vec3.hxx>
+#include <RWObj_Material.hxx>
+#include <NCollection_DataMap.hxx>
+
+//! Reader of mtl files.
+class RWObj_MtlReader
+{
+public:
+
+ //! Main constructor.
+ RWObj_MtlReader (NCollection_DataMap<TCollection_AsciiString, RWObj_Material>& theMaterials);
+
+ //! Destructor.
+ ~RWObj_MtlReader();
+
+ //! Read the file.
+ bool Read (const TCollection_AsciiString& theFolder,
+ const TCollection_AsciiString& theFile);
+
+private:
+
+ //! Validate scalar value
+ bool validateScalar (const Standard_Real theValue);
+
+ //! Validate RGB color
+ bool validateColor (const Graphic3d_Vec3& theVec);
+
+ //! Process texture path.
+ void processTexturePath (TCollection_AsciiString& theTexturePath,
+ const TCollection_AsciiString& theFolder);
+
+private:
+
+ FILE* myFile;
+ TCollection_AsciiString myPath;
+ NCollection_DataMap<TCollection_AsciiString, RWObj_Material>* myMaterials;
+ int myNbLines;
+
+};
+
+#endif // _RWObj_MtlReader_HeaderFile
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <RWObj_Reader.hxx>
+
+#include <RWObj_MtlReader.hxx>
+
+#include <BRepMesh_DataStructureOfDelaun.hxx>
+#include <BRepMesh_Delaun.hxx>
+#include <gp_XY.hxx>
+#include <Message.hxx>
+#include <Message_Messenger.hxx>
+#include <Message_ProgressSentry.hxx>
+#include <NCollection_IncAllocator.hxx>
+#include <OSD_OpenFile.hxx>
+#include <OSD_Path.hxx>
+#include <OSD_Timer.hxx>
+#include <Precision.hxx>
+#include <Standard_CLocaleSentry.hxx>
+
+#include <algorithm>
+#include <limits>
+
+#if defined(_WIN32)
+ #define ftell64(a) _ftelli64(a)
+ #define fseek64(a,b,c) _fseeki64(a,b,c)
+#else
+ #define ftell64(a) ftello(a)
+ #define fseek64(a,b,c) fseeko(a,b,c)
+#endif
+
+IMPLEMENT_STANDARD_RTTIEXT(RWObj_Reader, Standard_Transient)
+
+namespace
+{
+
+ //! Simple wrapper.
+ struct RWObj_ReaderFile
+ {
+ FILE* File;
+ NCollection_Array1<char> Line;
+ Standard_Integer LineBuffLen;
+ Standard_Integer MaxLineLen;
+ int64_t Position;
+ int64_t FileLen;
+
+ //! Constructor opening the file.
+ RWObj_ReaderFile (const TCollection_AsciiString& theFile,
+ const Standard_Integer theMaxLineLen = 256)
+ : File (OSD_OpenFile (theFile.ToCString(), "rb")),
+ Line (0, theMaxLineLen - 1),
+ LineBuffLen (theMaxLineLen),
+ MaxLineLen (theMaxLineLen),
+ Position (0),
+ FileLen (0)
+ {
+ if (this->File != NULL)
+ {
+ // determine length of file
+ ::fseek64 (this->File, 0, SEEK_END);
+ FileLen = ::ftell64 (this->File);
+ ::fseek64 (this->File, 0, SEEK_SET);
+ }
+ }
+
+ //! Destructor closing the file.
+ ~RWObj_ReaderFile()
+ {
+ if (File != NULL)
+ {
+ ::fclose (File);
+ }
+ }
+
+ //! Read line, also considers multi-line syntax (when last line symbol is slash).
+ bool ReadLine()
+ {
+ int64_t aPosPrev = this->Position;
+ char* aLine = &Line.ChangeFirst();
+ for (; ::feof (this->File) == 0 && ::fgets (aLine, MaxLineLen - 1, this->File) != NULL; )
+ {
+ const int64_t aPosNew = ::ftell64 (this->File);
+ if (aLine[0] == '#')
+ {
+ Position = aPosNew;
+ return true;
+ }
+
+ const Standard_Integer aNbRead = Standard_Integer(aPosNew - aPosPrev);
+ bool toReadMore = false;
+ for (int aTailIter = aNbRead - 1; aTailIter >= 0; --aTailIter)
+ {
+ if (aLine[aTailIter] != '\n'
+ && aLine[aTailIter] != '\r'
+ && aLine[aTailIter] != '\0')
+ {
+ if (aLine[aTailIter] == '\\')
+ {
+ // multi-line syntax
+ aLine[aTailIter] = ' ';
+ const ptrdiff_t aFullLen = aLine + aTailIter + 1 - &this->Line.First();
+ if (LineBuffLen < aNbRead + MaxLineLen)
+ {
+ LineBuffLen += MaxLineLen;
+ this->Line.Resize (0, LineBuffLen - 1, true);
+ }
+ aLine = &this->Line.ChangeFirst() + aFullLen;
+ toReadMore = true;
+ break;
+ }
+ break;
+ }
+ }
+
+ if (toReadMore)
+ {
+ aPosPrev = aPosNew;
+ continue;
+ }
+
+ Position = aPosNew;
+ return true;
+ }
+ return false;
+ }
+
+ };
+
+ //! Return TRUE if given polygon has clockwise node order.
+ static bool isClockwisePolygon (const Handle(BRepMesh_DataStructureOfDelaun)& theMesh,
+ const IMeshData::VectorOfInteger& theIndexes)
+ {
+ double aPtSum = 0;
+ const int aNbElemNodes = theIndexes.Size();
+ for (int aNodeIter = theIndexes.Lower(); aNodeIter <= theIndexes.Upper(); ++aNodeIter)
+ {
+ int aNodeNext = theIndexes.Lower() + ((aNodeIter + 1) % aNbElemNodes);
+ const BRepMesh_Vertex& aVert1 = theMesh->GetNode (theIndexes.Value (aNodeIter));
+ const BRepMesh_Vertex& aVert2 = theMesh->GetNode (theIndexes.Value (aNodeNext));
+ aPtSum += (aVert2.Coord().X() - aVert1.Coord().X())
+ * (aVert2.Coord().Y() + aVert1.Coord().Y());
+ }
+ return aPtSum < 0.0;
+ }
+}
+
+// ================================================================
+// Function : Read
+// Purpose :
+// ================================================================
+RWObj_Reader::RWObj_Reader()
+: myMemLimitBytes (Standard_Size(-1)),
+ myMemEstim (0),
+ myNbLines (0),
+ myNbProbeNodes (0),
+ myNbProbeElems (0),
+ myNbElemsBig (0),
+ myToAbort (false)
+{
+ //
+}
+
+// ================================================================
+// Function : read
+// Purpose :
+// ================================================================
+Standard_Boolean RWObj_Reader::read (const TCollection_AsciiString& theFile,
+ const Handle(Message_ProgressIndicator)& theProgress,
+ const Standard_Boolean theToProbe)
+{
+ myMemEstim = 0;
+ myNbLines = 0;
+ myNbProbeNodes = 0;
+ myNbProbeElems = 0;
+ myNbElemsBig = 0;
+ myToAbort = false;
+ myObjVerts.Reset();
+ myObjVertsUV.Clear();
+ myObjNorms.Clear();
+ myPackedIndices.Clear();
+ myMaterials.Clear();
+ myFileComments.Clear();
+ myExternalFiles.Clear();
+ myActiveSubMesh = RWObj_SubMesh();
+
+ // determine file location to load associated files
+ TCollection_AsciiString aFileName;
+ OSD_Path::FolderAndFileFromPath (theFile, myFolder, aFileName);
+ myCurrElem.resize (1024, -1);
+
+ Standard_CLocaleSentry aLocaleSentry;
+ RWObj_ReaderFile aFile (theFile);
+ if (aFile.File == NULL)
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: file '") + theFile + "' is not found!", Message_Fail);
+ return Standard_False;
+ }
+
+ // determine length of file
+ const int64_t aFileLen = aFile.FileLen;
+ if (aFileLen <= 0L)
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: file '") + theFile + "' is empty!", Message_Fail);
+ return Standard_False;
+ }
+
+ const Standard_Integer aNbMiBTotal = Standard_Integer(aFileLen / (1024 * 1024));
+ Standard_Integer aNbMiBPassed = 0;
+ Message_ProgressSentry aPSentry (theProgress, "Reading text OBJ file", 0, aNbMiBTotal, 1);
+ OSD_Timer aTimer;
+ aTimer.Start();
+
+ bool isStart = true;
+ for (; aFile.ReadLine(); )
+ {
+ ++myNbLines;
+ const char* aLine = &aFile.Line.First();
+ if (aTimer.ElapsedTime() > 1.0)
+ {
+ if (!aPSentry.More())
+ {
+ return false;
+ }
+
+ const Standard_Integer aNbMiBRead = Standard_Integer(aFile.Position / (1024 * 1024));
+ for (; aNbMiBPassed < aNbMiBRead; ++aNbMiBPassed) { aPSentry.Next(); }
+ aTimer.Reset();
+ aTimer.Start();
+ }
+
+ if (*aLine == '#')
+ {
+ if (isStart)
+ {
+ TCollection_AsciiString aComment (aLine + 1);
+ aComment.LeftAdjust();
+ aComment.RightAdjust();
+ if (!aComment.IsEmpty())
+ {
+ if (!myFileComments.IsEmpty())
+ {
+ myFileComments += "\n";
+ }
+ myFileComments += aComment;
+ }
+ }
+ continue;
+ }
+ else if (*aLine == '\n'
+ || *aLine == '\0')
+ {
+
+ continue;
+ }
+ isStart = false;
+
+ if (theToProbe)
+ {
+ if (::memcmp (aLine, "mtllib", 6) == 0)
+ {
+ readMaterialLib (aLine + 7);
+ }
+ else if (aLine[0] == 'v' && RWObj_Tools::isSpaceChar (aLine[1]))
+ {
+ ++myNbProbeNodes;
+ }
+ else if (aLine[0] == 'f' && RWObj_Tools::isSpaceChar (aLine[1]))
+ {
+ ++myNbProbeElems;
+ }
+ continue;
+ }
+
+ if (aLine[0] == 'v' && RWObj_Tools::isSpaceChar (aLine[1]))
+ {
+ ++myNbProbeNodes;
+ pushVertex (aLine + 2);
+ }
+ else if (aLine[0] == 'v'
+ && aLine[1] == 'n'
+ && RWObj_Tools::isSpaceChar (aLine[2]))
+ {
+ pushNormal (aLine + 3);
+ }
+ else if (aLine[0] == 'v'
+ && aLine[1] == 't'
+ && RWObj_Tools::isSpaceChar (aLine[2]))
+ {
+ pushTexel (aLine + 3);
+ }
+ else if (aLine[0] == 'f' && RWObj_Tools::isSpaceChar (aLine[1]))
+ {
+ ++myNbProbeElems;
+ pushIndices (aLine + 2);
+ }
+ else if (aLine[0] == 'g' && IsSpace (aLine[1]))
+ {
+ pushGroup (aLine + 2);
+ }
+ else if (aLine[0] == 's' && IsSpace (aLine[1]))
+ {
+ pushSmoothGroup (aLine + 2);
+ }
+ else if (aLine[0] == 'o' && IsSpace (aLine[1]))
+ {
+ pushObject (aLine + 2);
+ }
+ else if (::memcmp (aLine, "mtllib", 6) == 0)
+ {
+ readMaterialLib (aLine + 7);
+ }
+ else if (::memcmp (aLine, "usemtl", 6) == 0)
+ {
+ pushMaterial (aLine + 7);
+ }
+
+ if (!checkMemory())
+ {
+ addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject);
+ return false;
+ }
+ }
+
+ // collect external references
+ for (NCollection_DataMap<TCollection_AsciiString, RWObj_Material>::Iterator aMatIter (myMaterials); aMatIter.More(); aMatIter.Next())
+ {
+ const RWObj_Material& aMat = aMatIter.Value();
+ if (!aMat.DiffuseTexture.IsEmpty())
+ {
+ myExternalFiles.Add (aMat.DiffuseTexture);
+ }
+ if (!aMat.SpecularTexture.IsEmpty())
+ {
+ myExternalFiles.Add (aMat.SpecularTexture);
+ }
+ if (!aMat.BumpTexture.IsEmpty())
+ {
+ myExternalFiles.Add (aMat.BumpTexture);
+ }
+ }
+
+ // flush the last group
+ if (!theToProbe)
+ {
+ addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject);
+ }
+ if (myNbElemsBig != 0)
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: OBJ reader, ") + myNbElemsBig
+ + " polygon(s) have been split into triangles.", Message_Warning);
+ }
+
+ for (; aNbMiBPassed < aNbMiBTotal; ++aNbMiBPassed) { aPSentry.Next(); }
+ return true;
+}
+
+// =======================================================================
+// function : pushIndices
+// purpose :
+// =======================================================================
+void RWObj_Reader::pushIndices (const char* thePos)
+{
+ char* aNext = NULL;
+
+ Standard_Integer aNbElemNodes = 0;
+ for (Standard_Integer aNode = 0;; ++aNode)
+ {
+ Graphic3d_Vec3i a3Indices (-1, -1, -1);
+ a3Indices[0] = strtol (thePos, &aNext, 10) - 1;
+ if (aNext == thePos)
+ {
+ break;
+ }
+
+ // parse UV index
+ thePos = aNext;
+ if (*thePos == '/')
+ {
+ ++thePos;
+ a3Indices[1] = strtol (thePos, &aNext, 10) - 1;
+ thePos = aNext;
+
+ // parse Normal index
+ if (*thePos == '/')
+ {
+ ++thePos;
+ a3Indices[2] = strtol (thePos, &aNext, 10) - 1;
+ thePos = aNext;
+ }
+ }
+
+ // handle negative indices
+ if (a3Indices[0] < -1)
+ {
+ a3Indices[0] += myObjVerts.Upper() + 2;
+ }
+ if (a3Indices[1] < -1)
+ {
+ a3Indices[1] += myObjVertsUV.Upper() + 2;
+ }
+ if (a3Indices[2] < -1)
+ {
+ a3Indices[2] += myObjNorms.Upper() + 2;
+ }
+
+ Standard_Integer anIndex = -1;
+ if (!myPackedIndices.Find (a3Indices, anIndex))
+ {
+ if (a3Indices[0] >= 0)
+ {
+ myMemEstim += sizeof(Graphic3d_Vec3);
+ }
+ if (a3Indices[1] >= 0)
+ {
+ myMemEstim += sizeof(Graphic3d_Vec2);
+ }
+ if (a3Indices[2] >= 0)
+ {
+ myMemEstim += sizeof(Graphic3d_Vec3);
+ }
+ myMemEstim += sizeof(Graphic3d_Vec4i) + sizeof(Standard_Integer); // naive map
+ if (a3Indices[0] < myObjVerts.Lower() || a3Indices[0] > myObjVerts.Upper())
+ {
+ myToAbort = true;
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: invalid OBJ syntax at line ") + myNbLines
+ + ": vertex index is out of range.", Message_Fail);
+ return;
+ }
+
+ anIndex = addNode (myObjVerts.Value (a3Indices[0]));
+ myPackedIndices.Bind (a3Indices, anIndex);
+ if (a3Indices[1] >= 0)
+ {
+ if (myObjVertsUV.IsEmpty())
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
+ + ": UV index is specified but no UV nodes are defined.", Message_Warning);
+ }
+ else if (a3Indices[1] < myObjVertsUV.Lower() || a3Indices[1] > myObjVertsUV.Upper())
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
+ + ": UV index is out of range.", Message_Warning);
+ setNodeUV (anIndex,Graphic3d_Vec2 (0.0f, 0.0f));
+ }
+ else
+ {
+ setNodeUV (anIndex, myObjVertsUV.Value (a3Indices[1]));
+ }
+ }
+ if (a3Indices[2] >= 0)
+ {
+ if (myObjNorms.IsEmpty())
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
+ + ": Normal index is specified but no Normals nodes are defined.", Message_Warning);
+ }
+ else if (a3Indices[2] < myObjNorms.Lower() || a3Indices[2] > myObjNorms.Upper())
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
+ + ": Normal index is out of range.", Message_Warning);
+ setNodeNormal (anIndex, Graphic3d_Vec3 (0.0f, 0.0f, 1.0f));
+ }
+ else
+ {
+ setNodeNormal (anIndex, myObjNorms.Value (a3Indices[2]));
+ }
+ }
+ }
+
+ if (myCurrElem.size() < size_t(aNode))
+ {
+ myCurrElem.resize (aNode * 2, -1);
+ }
+ myCurrElem[aNode] = anIndex;
+ aNbElemNodes = aNode + 1;
+
+ if (*thePos == '\n'
+ || *thePos == '\0')
+ {
+ break;
+ }
+
+ if (*thePos != ' ')
+ {
+ ++thePos;
+ }
+ }
+
+ if (myCurrElem[0] < 0
+ || myCurrElem[1] < 0
+ || myCurrElem[2] < 0
+ || aNbElemNodes < 3)
+ {
+ return;
+ }
+
+ if (aNbElemNodes == 3)
+ {
+ myMemEstim += sizeof(Graphic3d_Vec4i);
+ addElement (myCurrElem[0], myCurrElem[1], myCurrElem[2], -1);
+ }
+ else if (aNbElemNodes == 4)
+ {
+ myMemEstim += sizeof(Graphic3d_Vec4i);
+ addElement (myCurrElem[0], myCurrElem[1], myCurrElem[2], myCurrElem[3]);
+ }
+ else
+ {
+ const NCollection_Array1<Standard_Integer> aCurrElemArray1 (myCurrElem[0], 1, aNbElemNodes);
+ const Standard_Integer aNbAdded = triangulatePolygon (aCurrElemArray1);
+ if (aNbAdded < 1)
+ {
+ return;
+ }
+ ++myNbElemsBig;
+ myMemEstim += sizeof(Graphic3d_Vec4i) * aNbAdded;
+ }
+}
+
+//================================================================
+// Function : triangulatePolygonFan
+// Purpose :
+//================================================================
+Standard_Integer RWObj_Reader::triangulatePolygonFan (const NCollection_Array1<Standard_Integer>& theIndices)
+{
+ const Standard_Integer aNbElemNodes = theIndices.Size();
+ for (Standard_Integer aNodeIter = 0; aNodeIter < aNbElemNodes - 2; ++aNodeIter)
+ {
+ Graphic3d_Vec4i aTriNodes (-1, -1, -1, -1);
+ for (Standard_Integer aNodeInSubTriIter = 0; aNodeInSubTriIter < 3; ++aNodeInSubTriIter)
+ {
+ const Standard_Integer aCurrNodeIndex = (aNodeInSubTriIter == 0) ? 0 : (aNodeIter + aNodeInSubTriIter);
+ aTriNodes[aNodeInSubTriIter] = theIndices.Value (theIndices.Lower() + aCurrNodeIndex);
+ }
+ addElement (aTriNodes[0], aTriNodes[1], aTriNodes[2], -1);
+ }
+ return aNbElemNodes - 2;
+}
+
+//================================================================
+// Function : polygonCenter
+// Purpose :
+//================================================================
+gp_XYZ RWObj_Reader::polygonCenter (const NCollection_Array1<Standard_Integer>& theIndices)
+{
+ if (theIndices.Size() < 3)
+ {
+ return gp_XYZ (0.0, 0.0, 0.0);
+ }
+ else if (theIndices.Size() == 4)
+ {
+ gp_XYZ aCenter = getNode (theIndices.Value (theIndices.Lower() + 0)).XYZ()
+ + getNode (theIndices.Value (theIndices.Lower() + 2)).XYZ();
+ aCenter /= 2.0;
+ return aCenter;
+ }
+
+ gp_XYZ aCenter (0, 0, 0);
+ for (NCollection_Array1<Standard_Integer>::Iterator aPntIter (theIndices); aPntIter.More(); aPntIter.Next())
+ {
+ aCenter += getNode (aPntIter.Value()).XYZ();
+ }
+
+ aCenter /= (Standard_Real )theIndices.Size();
+ return aCenter;
+}
+
+//================================================================
+// Function : polygonNormal
+// Purpose :
+//================================================================
+gp_XYZ RWObj_Reader::polygonNormal (const NCollection_Array1<Standard_Integer>& theIndices)
+{
+ const gp_XYZ aCenter = polygonCenter (theIndices);
+ gp_XYZ aMaxDir = getNode (theIndices.First()).XYZ() - aCenter;
+ gp_XYZ aNormal = (getNode (theIndices.Last()).XYZ() - aCenter).Crossed (aMaxDir);
+ for (int aPntIter = theIndices.Lower(); aPntIter < theIndices.Upper(); ++aPntIter)
+ {
+ const gp_XYZ aTmpDir2 = getNode (theIndices.Value (aPntIter + 1)).XYZ() - aCenter;
+ if (aTmpDir2.SquareModulus() > aMaxDir.SquareModulus())
+ {
+ aMaxDir = aTmpDir2;
+ }
+
+ const gp_XYZ aTmpDir1 = getNode (theIndices.Value (aPntIter)).XYZ() - aCenter;
+ gp_XYZ aDelta = aTmpDir1.Crossed (aTmpDir2);
+ if (aNormal.Dot (aDelta) < 0.0)
+ {
+ aDelta *= -1.0;
+ }
+ aNormal += aDelta;
+ }
+
+ const Standard_Real aMod = aNormal.Modulus();
+ if (aMod > gp::Resolution())
+ {
+ aNormal /= aMod;
+ }
+ return aNormal;
+}
+
+//================================================================
+// Function : triangulatePolygon
+// Purpose :
+//================================================================
+Standard_Integer RWObj_Reader::triangulatePolygon (const NCollection_Array1<Standard_Integer>& theIndices)
+{
+ const Standard_Integer aNbElemNodes = theIndices.Size();
+ if (aNbElemNodes < 3)
+ {
+ return 0;
+ }
+
+ const gp_XYZ aPolygonNorm = polygonNormal (theIndices);
+
+ // map polygon onto plane
+ gp_XYZ aXDir;
+ {
+ const double aAbsXYZ[] = { Abs(aPolygonNorm.X()), Abs(aPolygonNorm.Y()), Abs(aPolygonNorm.Z()) };
+ Standard_Integer aMinI = (aAbsXYZ[0] < aAbsXYZ[1]) ? 0 : 1;
+ aMinI = (aAbsXYZ[aMinI] < aAbsXYZ[2]) ? aMinI : 2;
+ const Standard_Integer aI1 = (aMinI + 1) % 3 + 1;
+ const Standard_Integer aI2 = (aMinI + 2) % 3 + 1;
+ aXDir.ChangeCoord (aMinI + 1) = 0;
+ aXDir.ChangeCoord (aI1) = aPolygonNorm.Coord (aI2);
+ aXDir.ChangeCoord (aI2) = -aPolygonNorm.Coord (aI1);
+ }
+ const gp_XYZ aYDir = aPolygonNorm ^ aXDir;
+
+ Handle(NCollection_IncAllocator) anAllocator = new NCollection_IncAllocator();
+ Handle(BRepMesh_DataStructureOfDelaun) aMeshStructure = new BRepMesh_DataStructureOfDelaun (anAllocator);
+ IMeshData::VectorOfInteger anIndexes (aNbElemNodes, anAllocator);
+ for (Standard_Integer aNodeIter = 0; aNodeIter < aNbElemNodes; ++aNodeIter)
+ {
+ const Standard_Integer aNodeIndex = theIndices.Value (theIndices.Lower() + aNodeIter);
+ const gp_XYZ aPnt3d = getNode (aNodeIndex).XYZ();
+ gp_XY aPnt2d (aXDir * aPnt3d, aYDir * aPnt3d);
+ BRepMesh_Vertex aVertex (aPnt2d, aNodeIndex, BRepMesh_Frontier);
+ anIndexes.Append (aMeshStructure->AddNode (aVertex));
+ }
+
+ const bool isClockwiseOrdered = isClockwisePolygon (aMeshStructure, anIndexes);
+ for (Standard_Integer aIdx = anIndexes.Lower(); aIdx <= anIndexes.Upper(); ++aIdx)
+ {
+ const Standard_Integer aPtIdx = isClockwiseOrdered ? aIdx : (aIdx + 1) % anIndexes.Length();
+ const Standard_Integer aNextPtIdx = isClockwiseOrdered ? (aIdx + 1) % anIndexes.Length() : aIdx;
+ BRepMesh_Edge anEdge (anIndexes.Value (aPtIdx),
+ anIndexes.Value (aNextPtIdx),
+ BRepMesh_Frontier);
+ aMeshStructure->AddLink (anEdge);
+ }
+
+ try
+ {
+ BRepMesh_Delaun aTriangulation (aMeshStructure, anIndexes);
+ const IMeshData::MapOfInteger& aTriangles = aMeshStructure->ElementsOfDomain();
+ if (aTriangles.Extent() < 1)
+ {
+ return triangulatePolygonFan (theIndices);
+ }
+
+ Standard_Integer aNbTrisAdded = 0;
+ for (IMeshData::MapOfInteger::Iterator aTriIter (aTriangles); aTriIter.More(); aTriIter.Next())
+ {
+ const Standard_Integer aTriangleId = aTriIter.Key();
+ const BRepMesh_Triangle& aTriangle = aMeshStructure->GetElement (aTriangleId);
+ if (aTriangle.Movability() == BRepMesh_Deleted)
+ {
+ continue;
+ }
+
+ int aTri2d[3];
+ aMeshStructure->ElementNodes (aTriangle, aTri2d);
+ if (!isClockwiseOrdered)
+ {
+ std::swap (aTri2d[1], aTri2d[2]);
+ }
+ const BRepMesh_Vertex& aVertex1 = aMeshStructure->GetNode (aTri2d[0]);
+ const BRepMesh_Vertex& aVertex2 = aMeshStructure->GetNode (aTri2d[1]);
+ const BRepMesh_Vertex& aVertex3 = aMeshStructure->GetNode (aTri2d[2]);
+ addElement (aVertex1.Location3d(), aVertex2.Location3d(), aVertex3.Location3d(), -1);
+ ++aNbTrisAdded;
+ }
+ return aNbTrisAdded;
+ }
+ catch (Standard_Failure const& theFailure)
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: exception raised during polygon split\n[")
+ + theFailure.GetMessageString() + "]", Message_Warning);
+ }
+ return triangulatePolygonFan (theIndices);
+}
+
+// =======================================================================
+// function : pushObject
+// purpose :
+// =======================================================================
+void RWObj_Reader::pushObject (const char* theObjectName)
+{
+ TCollection_AsciiString aNewObject;
+ if (!RWObj_Tools::ReadName (theObjectName, aNewObject))
+ {
+ // empty group name is OK
+ }
+ if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject))
+ {
+ myPackedIndices.Clear(); // vertices might be duplicated after this point...
+ }
+ myActiveSubMesh.Object = aNewObject;
+}
+
+// =======================================================================
+// function : pushGroup
+// purpose :
+// =======================================================================
+void RWObj_Reader::pushGroup (const char* theGroupName)
+{
+ TCollection_AsciiString aNewGroup;
+ if (!RWObj_Tools::ReadName (theGroupName, aNewGroup))
+ {
+ // empty group name is OK
+ }
+ if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewGroup))
+ {
+ myPackedIndices.Clear(); // vertices might be duplicated after this point...
+ }
+ myActiveSubMesh.Group = aNewGroup;
+}
+
+// =======================================================================
+// function : pushSmoothGroup
+// purpose :
+// =======================================================================
+void RWObj_Reader::pushSmoothGroup (const char* theSmoothGroupIndex)
+{
+ TCollection_AsciiString aNewSmoothGroup;
+ RWObj_Tools::ReadName (theSmoothGroupIndex, aNewSmoothGroup);
+ if (aNewSmoothGroup == "off"
+ || aNewSmoothGroup == "0")
+ {
+ aNewSmoothGroup.Clear();
+ }
+ if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewSmoothGroup))
+ {
+ myPackedIndices.Clear(); // vertices might be duplicated after this point...
+ }
+ myActiveSubMesh.SmoothGroup = aNewSmoothGroup;
+}
+
+// =======================================================================
+// function : pushMaterial
+// purpose :
+// =======================================================================
+void RWObj_Reader::pushMaterial (const char* theMaterialName)
+{
+ TCollection_AsciiString aNewMat;
+ if (!RWObj_Tools::ReadName (theMaterialName, aNewMat))
+ {
+ // empty material name is allowed by specs
+ }
+ else if (!myMaterials.IsBound (aNewMat))
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: use of undefined OBJ material at line ")
+ + myNbLines, Message_Warning);
+ return;
+ }
+ if (myActiveSubMesh.Material.IsEqual (aNewMat))
+ {
+ return; // ignore
+ }
+
+ // implicitly create a new group to split materials
+ if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewMaterial))
+ {
+ myPackedIndices.Clear(); // vertices might be duplicated after this point...
+ }
+ myActiveSubMesh.Material = aNewMat;
+}
+
+// =======================================================================
+// function : readMaterialLib
+// purpose :
+// =======================================================================
+void RWObj_Reader::readMaterialLib (const char* theFileName)
+{
+ TCollection_AsciiString aMatPath;
+ if (!RWObj_Tools::ReadName (theFileName, aMatPath))
+ {
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ")
+ + myNbLines, Message_Warning);
+ return;
+ }
+
+ RWObj_MtlReader aMatReader (myMaterials);
+ if (aMatReader.Read (myFolder, aMatPath))
+ {
+ myExternalFiles.Add (myFolder + aMatPath);
+ }
+}
+
+// =======================================================================
+// function : checkMemory
+// purpose :
+// =======================================================================
+bool RWObj_Reader::checkMemory()
+{
+ if (myMemEstim < myMemLimitBytes
+ || myToAbort)
+ {
+ return true;
+ }
+
+ Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: OBJ file content does not fit into ")
+ + Standard_Integer(myMemLimitBytes / (1024 * 1024)) + " MiB limit."
+ + "\nMesh data will be truncated.", Message_Fail);
+ myToAbort = true;
+ return false;
+}
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2015-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWObj_Reader_HeaderFile
+#define _RWObj_Reader_HeaderFile
+
+#include <gp_XYZ.hxx>
+#include <Graphic3d_Vec2.hxx>
+#include <Graphic3d_Vec4.hxx>
+#include <Message.hxx>
+#include <Message_Messenger.hxx>
+#include <Message_ProgressIndicator.hxx>
+#include <NCollection_Array1.hxx>
+#include <NCollection_DataMap.hxx>
+#include <NCollection_IndexedMap.hxx>
+#include <NCollection_Vector.hxx>
+#include <NCollection_Shared.hxx>
+
+#include <RWMesh_CoordinateSystemConverter.hxx>
+#include <RWObj_Material.hxx>
+#include <RWObj_SubMesh.hxx>
+#include <RWObj_SubMeshReason.hxx>
+#include <RWObj_Tools.hxx>
+
+#include <vector>
+
+//! An abstract class implementing procedure to read OBJ file.
+//!
+//! This class is not bound to particular data structure
+//! and can be used to read the file directly into arbitrary data model.
+//! To use it, create descendant class and implement interface methods.
+//!
+//! Call method Read() to read the file.
+class RWObj_Reader : public Standard_Transient
+{
+ DEFINE_STANDARD_RTTIEXT(RWObj_Reader, Standard_Transient)
+public:
+
+ //! Empty constructor.
+ Standard_EXPORT RWObj_Reader();
+
+ //! Reads data from OBJ file.
+ //! Unicode paths can be given in UTF-8 encoding.
+ //! Returns true if success, false on error or user break.
+ Standard_Boolean Read (const TCollection_AsciiString& theFile,
+ const Handle(Message_ProgressIndicator)& theProgress)
+ {
+ return read (theFile, theProgress, Standard_False);
+ }
+
+ //! Probe data from OBJ file (comments, external references) without actually reading mesh data.
+ //! Although mesh data will not be collected, the full file content will be parsed, due to OBJ format limitations.
+ //! @param theFile path to the file
+ //! @param theProgress progress indicator
+ //! @return TRUE if success, FALSE on error or user break.
+ //! @sa FileComments(), ExternalFiles(), NbProbeNodes(), NbProbeElems().
+ Standard_Boolean Probe (const TCollection_AsciiString& theFile,
+ const Handle(Message_ProgressIndicator)& theProgress)
+ {
+ return read (theFile, theProgress, Standard_True);
+ }
+
+ //! Returns file comments (lines starting with # at the beginning of file).
+ const TCollection_AsciiString& FileComments() const { return myFileComments; }
+
+ //! Return the list of external file references.
+ const NCollection_IndexedMap<TCollection_AsciiString>& ExternalFiles() const { return myExternalFiles; }
+
+ //! Number of probed nodes.
+ Standard_Integer NbProbeNodes() const { return myNbProbeNodes; }
+
+ //!< number of probed polygon elements (of unknown size).
+ Standard_Integer NbProbeElems() const { return myNbProbeElems; }
+
+ //! Returns memory limit in bytes; -1 (no limit) by default.
+ Standard_Size MemoryLimit() const { return myMemLimitBytes; }
+
+ //! Specify memory limit in bytes, so that import will be aborted
+ //! by specified limit before memory allocation error occurs.
+ void SetMemoryLimit (Standard_Size theMemLimit) { myMemLimitBytes = theMemLimit; }
+
+ //! Return transformation from one coordinate system to another; no transformation by default.
+ const RWMesh_CoordinateSystemConverter& Transformation() const { return myCSTrsf; }
+
+ //! Setup transformation from one coordinate system to another.
+ //! OBJ file might be exported following various coordinate system conventions,
+ //! so that it might be useful automatically transform data during file reading.
+ void SetTransformation (const RWMesh_CoordinateSystemConverter& theCSConverter) { myCSTrsf = theCSConverter; }
+
+ //! Return single precision flag for reading vertex data (coordinates); FALSE by default.
+ Standard_Boolean IsSinglePrecision() const { return myObjVerts.IsSinglePrecision(); }
+
+ //! Setup single/double precision flag for reading vertex data (coordinates).
+ void SetSinglePrecision (Standard_Boolean theIsSinglePrecision) { myObjVerts.SetSinglePrecision (theIsSinglePrecision); }
+
+protected:
+
+ //! Reads data from OBJ file.
+ //! Unicode paths can be given in UTF-8 encoding.
+ //! Returns true if success, false on error or user break.
+ Standard_EXPORT Standard_Boolean read (const TCollection_AsciiString& theFile,
+ const Handle(Message_ProgressIndicator)& theProgress,
+ const Standard_Boolean theToProbe);
+
+//! @name interface methods which should be implemented by sub-class
+protected:
+
+ //! Add new sub-mesh.
+ //! Basically, this method will be called multiple times for the same group with different reason,
+ //! so that implementation should decide if previously allocated sub-mesh should be used or new one to be allocated.
+ //! Sub-mesh command can be skipped if previous sub-mesh is empty,
+ //! or if the reason is out of interest for particular reader
+ //! (e.g. if materials are ignored, reader may ignore RWObj_SubMeshReason_NewMaterial reason).
+ //! @param theMesh mesh definition
+ //! @param theReason reason to create new sub-mesh
+ //! @return TRUE if new sub-mesh should be started since this point
+ virtual Standard_Boolean addMesh (const RWObj_SubMesh& theMesh,
+ const RWObj_SubMeshReason theReason) = 0;
+
+ //! Retrieve sub-mesh node position, added by addNode().
+ virtual gp_Pnt getNode (Standard_Integer theIndex) const = 0;
+
+ //! Callback function to be implemented in descendant.
+ //! Should create new node with specified coordinates in the target model, and return its ID as integer.
+ virtual Standard_Integer addNode (const gp_Pnt& thePnt) = 0;
+
+ //! Callback function to be implemented in descendant.
+ //! Should set normal coordinates for specified node.
+ //! @param theIndex node ID as returned by addNode()
+ //! @param theNorm normal vector
+ virtual void setNodeNormal (const Standard_Integer theIndex,
+ const Graphic3d_Vec3& theNorm) = 0;
+
+ //! Callback function to be implemented in descendant.
+ //! Should set texture coordinates for specified node.
+ //! @param theIndex node ID as returned by addNode()
+ //! @param theUV UV texture coordinates
+ virtual void setNodeUV (const Standard_Integer theIndex,
+ const Graphic3d_Vec2& theUV) = 0;
+
+ //! Callback function to be implemented in descendant.
+ //! Should create new element (triangle or quad if 4th index is != -1) built on specified nodes in the target model.
+ virtual void addElement (Standard_Integer theN1,
+ Standard_Integer theN2,
+ Standard_Integer theN3,
+ Standard_Integer theN4) = 0;
+
+//! @name implementation details
+private:
+
+ //! Handle "v X Y Z".
+ void pushVertex (const char* theXYZ)
+ {
+ char* aNext = NULL;
+ gp_Pnt anXYZ;
+ RWObj_Tools::ReadVec3 (theXYZ, aNext, anXYZ.ChangeCoord());
+ myCSTrsf.TransformPosition (anXYZ.ChangeCoord());
+
+ myMemEstim += myObjVerts.IsSinglePrecision() ? sizeof(Graphic3d_Vec3) : sizeof(gp_Pnt);
+ myObjVerts.Append (anXYZ);
+ }
+
+ //! Handle "vn NX NY NZ".
+ void pushNormal (const char* theXYZ)
+ {
+ char* aNext = NULL;
+ Graphic3d_Vec3 aNorm;
+ RWObj_Tools::ReadVec3 (theXYZ, aNext, aNorm);
+ myCSTrsf.TransformNormal (aNorm);
+
+ myMemEstim += sizeof(Graphic3d_Vec3);
+ myObjNorms.Append (aNorm);
+ }
+
+ //! Handle "vt U V".
+ void pushTexel (const char* theUV)
+ {
+ char* aNext = NULL;
+ Graphic3d_Vec2 anUV;
+ anUV.x() = (float )Strtod (theUV, &aNext);
+ theUV = aNext;
+ anUV.y() = (float )Strtod (theUV, &aNext);
+
+ myMemEstim += sizeof(Graphic3d_Vec2);
+ myObjVertsUV.Append (anUV);
+ }
+
+ //! Handle "f indices".
+ void pushIndices (const char* thePos);
+
+ //! Compute the center of planar polygon.
+ //! @param theIndices polygon indices
+ //! @return center of polygon
+ gp_XYZ polygonCenter (const NCollection_Array1<Standard_Integer>& theIndices);
+
+ //! Compute the normal to planar polygon.
+ //! The logic is similar to ShapeAnalysis_Curve::IsPlanar().
+ //! @param theIndices polygon indices
+ //! @return polygon normal
+ gp_XYZ polygonNormal (const NCollection_Array1<Standard_Integer>& theIndices);
+
+ //! Create triangle fan from specified polygon.
+ //! @param theIndices polygon nodes
+ //! @return number of added triangles
+ Standard_Integer triangulatePolygonFan (const NCollection_Array1<Standard_Integer>& theIndices);
+
+ //! Triangulate specified polygon.
+ //! @param theIndices polygon nodes
+ //! @return number of added triangles
+ Standard_Integer triangulatePolygon (const NCollection_Array1<Standard_Integer>& theIndices);
+
+ //! Handle "o ObjectName".
+ void pushObject (const char* theObjectName);
+
+ //! Handle "g GroupName".
+ void pushGroup (const char* theGroupName);
+
+ //! Handle "s SmoothGroupIndex".
+ void pushSmoothGroup (const char* theSmoothGroupIndex);
+
+ //! Handle "usemtl MaterialName".
+ void pushMaterial (const char* theMaterialName);
+
+ //! Handle "mtllib FileName".
+ void readMaterialLib (const char* theFileName);
+
+ //! Check memory limits.
+ //! @return FALSE on out of memory
+ bool checkMemory();
+
+protected:
+
+ //! Hasher for 3 ordered integers.
+ struct ObjVec3iHasher
+ {
+ static Standard_Integer HashCode (const Graphic3d_Vec3i& theKey,
+ const Standard_Integer theUpper)
+ {
+ return ::HashCode (::HashCodes ((Standard_CString )&theKey, sizeof(Graphic3d_Vec3i)), theUpper);
+ }
+
+ static Standard_Boolean IsEqual (const Graphic3d_Vec3i& theKey1,
+ const Graphic3d_Vec3i& theKey2)
+ {
+ return theKey1[0] == theKey2[0]
+ && theKey1[1] == theKey2[1]
+ && theKey1[2] == theKey2[2];
+ }
+ };
+
+ //! Auxiliary structure holding vertex data either with single or double floating point precision.
+ class VectorOfVertices
+ {
+ public:
+ //! Empty constructor.
+ VectorOfVertices() : myIsSinglePrecision (Standard_False) {}
+
+ //! Return single precision flag; FALSE by default.
+ bool IsSinglePrecision() const { return myIsSinglePrecision; }
+
+ //! Setup single/double precision flag.
+ void SetSinglePrecision (Standard_Boolean theIsSinglePrecision)
+ {
+ myIsSinglePrecision = theIsSinglePrecision;
+ myPntVec.Nullify();
+ myVec3Vec.Nullify();
+ }
+
+ //! Reset and (re)allocate buffer.
+ void Reset()
+ {
+ if (myIsSinglePrecision)
+ {
+ myVec3Vec = new NCollection_Shared<NCollection_Vector<Graphic3d_Vec3> >();
+ }
+ else
+ {
+ myPntVec = new NCollection_Shared<NCollection_Vector<gp_Pnt> >();
+ }
+ }
+
+ //! Return vector lower index.
+ Standard_Integer Lower() const { return 0; }
+
+ //! Return vector upper index.
+ Standard_Integer Upper() const { return myIsSinglePrecision ? myVec3Vec->Upper() : myPntVec->Upper(); }
+
+ //! Return point with the given index.
+ gp_Pnt Value (Standard_Integer theIndex) const
+ {
+ if (myIsSinglePrecision)
+ {
+ const Graphic3d_Vec3& aPnt = myVec3Vec->Value (theIndex);
+ return gp_Pnt (aPnt.x(), aPnt.y(), aPnt.z());
+ }
+ else
+ {
+ return myPntVec->Value (theIndex);
+ }
+ }
+
+ //! Append new point.
+ void Append (const gp_Pnt& thePnt)
+ {
+ if (myIsSinglePrecision)
+ {
+ myVec3Vec->Append (Graphic3d_Vec3 ((float )thePnt.X(), (float )thePnt.Y(), (float )thePnt.Z()));
+ }
+ else
+ {
+ myPntVec->Append (thePnt);
+ }
+ }
+ private:
+ Handle(NCollection_Shared<NCollection_Vector<gp_Pnt> >) myPntVec;
+ Handle(NCollection_Shared<NCollection_Vector<Graphic3d_Vec3> >) myVec3Vec;
+ Standard_Boolean myIsSinglePrecision;
+ };
+
+protected:
+
+ NCollection_IndexedMap<TCollection_AsciiString>
+ myExternalFiles; //!< list of external file references
+ TCollection_AsciiString myFileComments; //!< file header comments
+ TCollection_AsciiString myFolder; //!< folder containing the OBJ file
+ RWMesh_CoordinateSystemConverter myCSTrsf; //!< coordinate system flipper
+ Standard_Size myMemLimitBytes; //!< memory limit in bytes
+ Standard_Size myMemEstim; //!< estimated memory occupation in bytes
+ Standard_Integer myNbLines; //!< number of parsed lines (e.g. current line)
+ Standard_Integer myNbProbeNodes; //!< number of probed nodes
+ Standard_Integer myNbProbeElems; //!< number of probed elements
+ Standard_Integer myNbElemsBig; //!< number of big elements (polygons with 5+ nodes)
+ Standard_Boolean myToAbort; //!< flag indicating abort state (e.g. syntax error)
+
+ // Each node in the Element specifies independent indices of Vertex position, Texture coordinates and Normal.
+ // This scheme does not match natural definition of Primitive Array
+ // where each unique set of nodal properties defines Vertex
+ // (thus node at the same location but with different normal should be duplicated).
+ // The following code converts OBJ definition of nodal properties to Primitive Array definition.
+ VectorOfVertices myObjVerts; //!< temporary vector of vertices
+ NCollection_Vector<Graphic3d_Vec2> myObjVertsUV; //!< temporary vector of UV parameters
+ NCollection_Vector<Graphic3d_Vec3> myObjNorms; //!< temporary vector of normals
+ NCollection_DataMap<Graphic3d_Vec3i, Standard_Integer, ObjVec3iHasher>
+ myPackedIndices;
+ NCollection_DataMap<TCollection_AsciiString, RWObj_Material>
+ myMaterials; //!< map of known materials
+
+ RWObj_SubMesh myActiveSubMesh; //!< active sub-mesh definition
+ std::vector<Standard_Integer> myCurrElem; //!< indices for the current element
+
+};
+
+#endif // _RWObj_Reader_HeaderFile
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2015-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWObj_SubMesh_HeaderFile
+#define _RWObj_SubMesh_HeaderFile
+
+#include <Quantity_Color.hxx>
+#include <TCollection_AsciiString.hxx>
+
+//! Sub-mesh definition for OBJ reader.
+struct RWObj_SubMesh
+{
+ TCollection_AsciiString Object; //!< name of active object
+ TCollection_AsciiString Group; //!< name of active group
+ TCollection_AsciiString SmoothGroup; //!< name of active smoothing group
+ TCollection_AsciiString Material; //!< name of active material
+};
+
+#endif // _RWObj_SubMesh_HeaderFile
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWObj_SubMeshReason_HeaderFile
+#define _RWObj_SubMeshReason_HeaderFile
+
+//! Reason for creating a new group within OBJ reader.
+enum RWObj_SubMeshReason
+{
+ RWObj_SubMeshReason_NewObject, //!< new object, should occur only ones in valid OBJ file (at the very beginning)
+ RWObj_SubMeshReason_NewGroup, //!< new group (g item)
+ RWObj_SubMeshReason_NewMaterial, //!< new material (usemtl item)
+ RWObj_SubMeshReason_NewSmoothGroup //!< new smoothing group (s item)
+};
+
+#endif // _RWObj_SubMeshReason_HeaderFile
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2017-2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWObj_Tools_HeaderFile
+#define _RWObj_Tools_HeaderFile
+
+#include <gp_XYZ.hxx>
+#include <Graphic3d_Vec3.hxx>
+#include <TCollection_AsciiString.hxx>
+
+//! Auxiliary tools for OBJ format parser.
+namespace RWObj_Tools
+{
+ //! Read 3 float values.
+ inline bool ReadVec3 (const char* thePos,
+ char*& theNext,
+ Graphic3d_Vec3& theVec)
+ {
+ const char* aPos = thePos;
+ theVec.x() = (float )Strtod (aPos, &theNext);
+ aPos = theNext;
+ theVec.y() = (float )Strtod (aPos, &theNext);
+ aPos = theNext;
+ theVec.z() = (float )Strtod (aPos, &theNext);
+ return aPos != theNext;
+ }
+
+ //! Read 3 double values.
+ inline bool ReadVec3 (const char* thePos,
+ char*& theNext,
+ gp_XYZ& theVec)
+ {
+ const char* aPos = thePos;
+ theVec.SetX (Strtod (aPos, &theNext));
+ aPos = theNext;
+ theVec.SetY (Strtod (aPos, &theNext));
+ aPos = theNext;
+ theVec.SetZ (Strtod (aPos, &theNext));
+ return aPos != theNext;
+ }
+
+ //! Read string.
+ inline bool ReadName (const char* thePos,
+ TCollection_AsciiString& theName)
+ {
+ Standard_Integer aFrom = 0;
+ Standard_Integer aTail = (Standard_Integer )std::strlen (thePos) - 1;
+ if (aTail >= 0 && thePos[aTail] == '\n') { --aTail; }
+ if (aTail >= 0 && thePos[aTail] == '\r') { --aTail; }
+ for (; aTail >= 0 && IsSpace (thePos[aTail]); --aTail) {} // RightAdjust
+ for (; aFrom < aTail && IsSpace (thePos[aFrom]); ++aFrom) {} // LeftAdjust
+ if (aFrom > aTail)
+ {
+ theName.Clear();
+ return false;
+ }
+ theName = TCollection_AsciiString (thePos + aFrom, aTail - aFrom + 1);
+ return true;
+ }
+
+ //! Return true if specified char is a white space.
+ inline bool isSpaceChar (const char theChar)
+ {
+ return theChar == ' '
+ || theChar == '\t';
+ //return IsSpace (theChar);
+ }
+}
+
+#endif // _RWObj_Tools_HeaderFile
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <RWObj_TriangulationReader.hxx>
+
+#include <BRep_Builder.hxx>
+#include <TopoDS.hxx>
+#include <TopoDS_Iterator.hxx>
+
+IMPLEMENT_STANDARD_RTTIEXT(RWObj_TriangulationReader, RWObj_Reader)
+
+//================================================================
+// Function : addMesh
+// Purpose :
+//================================================================
+Standard_Boolean RWObj_TriangulationReader::addMesh (const RWObj_SubMesh& theMesh,
+ const RWObj_SubMeshReason theReason)
+{
+ if (!myToCreateShapes)
+ {
+ return Standard_False;
+ }
+
+ const RWObj_Material* aMaterial = myMaterials.Seek (theMesh.Material);
+ if (Handle(Poly_Triangulation) aTris = GetTriangulation())
+ {
+ myNodes.Clear();
+ myNodesUV.Clear();
+ myNormals.Clear();
+ myTriangles.Clear();
+ if (theMesh.Group != myLastGroupName)
+ {
+ // flush previous group and start a new one
+ if (addSubShape (myLastObjectShape, myLastGroupShape, Standard_False))
+ {
+ if (myShapeReceiver != NULL)
+ {
+ myShapeReceiver->BindNamedShape (myLastGroupShape, theMesh.Group, myLastGroupShape.ShapeType() == TopAbs_FACE ? aMaterial : NULL, Standard_False);
+ }
+ }
+ myLastGroupShape = TopoDS_Shape();
+ myLastGroupName = theMesh.Group;
+ }
+
+ TopoDS_Face aNewFace;
+ BRep_Builder aBuilder;
+ aBuilder.MakeFace (aNewFace, aTris);
+ addSubShape (myLastGroupShape, aNewFace, Standard_True);
+ if (myShapeReceiver != NULL)
+ {
+ myShapeReceiver->BindNamedShape (aNewFace, "", aMaterial, Standard_False);
+ }
+ }
+
+ if (theReason == RWObj_SubMeshReason_NewObject)
+ {
+ // forced flush at the end of the object
+ if (addSubShape (myLastObjectShape, myLastGroupShape, Standard_False))
+ {
+ if (myShapeReceiver != NULL)
+ {
+ myShapeReceiver->BindNamedShape (myLastGroupShape, theMesh.Group, myLastGroupShape.ShapeType() == TopAbs_FACE ? aMaterial : NULL, Standard_False);
+ }
+ }
+ myLastGroupShape = TopoDS_Shape();
+ myLastGroupName.Clear();
+
+ if (addSubShape (myResultShape, myLastObjectShape, Standard_False))
+ {
+ if (myShapeReceiver != NULL)
+ {
+ myShapeReceiver->BindNamedShape (myLastObjectShape, theMesh.Object, NULL, Standard_True);
+ }
+ }
+ myLastObjectShape = TopoDS_Compound();
+ }
+ return Standard_True;
+}
+
+// =======================================================================
+// function : addSubShape
+// purpose :
+// =======================================================================
+Standard_Boolean RWObj_TriangulationReader::addSubShape (TopoDS_Shape& theParent,
+ const TopoDS_Shape& theSubShape,
+ const Standard_Boolean theToExpandCompound)
+{
+ if (theSubShape.IsNull())
+ {
+ return Standard_False;
+ }
+
+ BRep_Builder aBuilder;
+ if (theParent.IsNull()
+ && theToExpandCompound)
+ {
+ theParent = theSubShape;
+ return Standard_True;
+ }
+
+ TopoDS_Compound aComp;
+ if (!theParent.IsNull()
+ && theParent.ShapeType() == TopAbs_COMPOUND)
+ {
+ aComp = TopoDS::Compound (theParent);
+ }
+ else
+ {
+ aBuilder.MakeCompound (aComp);
+ if (!theParent.IsNull())
+ {
+ aBuilder.Add (aComp, theParent);
+ }
+ }
+ aBuilder.Add (aComp, theSubShape);
+ theParent = aComp;
+ return Standard_True;
+}
+
+//=============================================================================
+//function : GetTriangulation
+//purpose :
+//=============================================================================
+Handle(Poly_Triangulation) RWObj_TriangulationReader::GetTriangulation()
+{
+ if (myTriangles.IsEmpty())
+ {
+ return Handle(Poly_Triangulation)();
+ }
+
+ const Standard_Boolean hasNormals = myNodes.Length() == myNormals.Length();
+ const Standard_Boolean hasUV = myNodes.Length() == myNodesUV.Length();
+
+ Handle(Poly_Triangulation) aPoly = new Poly_Triangulation (myNodes.Length(), myTriangles.Length(), hasUV);
+ for (Standard_Integer aNodeIter = 0; aNodeIter < myNodes.Size(); ++aNodeIter)
+ {
+ const gp_Pnt& aNode = myNodes.Value (aNodeIter);
+ aPoly->ChangeNode (aNodeIter + 1) = aNode;
+ }
+ if (hasUV)
+ {
+ for (Standard_Integer aNodeIter = 0; aNodeIter < myNodes.Size(); ++aNodeIter)
+ {
+ const Graphic3d_Vec2& aNode = myNodesUV.Value (aNodeIter);
+ aPoly->ChangeUVNode (aNodeIter + 1).SetCoord (aNode.x(), aNode.y());
+ }
+ }
+ if (hasNormals)
+ {
+ const Handle(TShort_HArray1OfShortReal) aNormals = new TShort_HArray1OfShortReal (1, myNodes.Length() * 3);
+ Standard_ShortReal* aNormArr = &aNormals->ChangeFirst();
+ Standard_Integer aNbInvalid = 0;
+ for (Standard_Integer aNodeIter = 0; aNodeIter < myNodes.Size(); ++aNodeIter)
+ {
+ const Graphic3d_Vec3& aNorm = myNormals.Value (aNodeIter);
+ const float aMod2 = aNorm.SquareModulus();
+ if (aMod2 > 0.001f)
+ {
+ aNormArr[aNodeIter * 3 + 0] = aNorm.x();
+ aNormArr[aNodeIter * 3 + 1] = aNorm.y();
+ aNormArr[aNodeIter * 3 + 2] = aNorm.z();
+ }
+ else
+ {
+ ++aNbInvalid;
+ aNormArr[aNodeIter * 3 + 0] = 0.0f;
+ aNormArr[aNodeIter * 3 + 1] = 0.0f;
+ aNormArr[aNodeIter * 3 + 2] = 1.0f;
+ }
+ }
+ if (aNbInvalid != myNodes.Length())
+ {
+ aPoly->SetNormals (aNormals);
+ }
+ }
+
+ for (Standard_Integer aTriIter = 0; aTriIter < myTriangles.Size(); ++aTriIter)
+ {
+ aPoly->ChangeTriangle (aTriIter + 1) = myTriangles (aTriIter);
+ }
+
+ return aPoly;
+}
+
+//================================================================
+// Function : ResultShape
+// Purpose :
+//================================================================
+TopoDS_Shape RWObj_TriangulationReader::ResultShape()
+{
+ if (!myToCreateShapes)
+ {
+ if (Handle(Poly_Triangulation) aTris = GetTriangulation())
+ {
+ TopoDS_Face aFace;
+ BRep_Builder aBuilder;
+ aBuilder.MakeFace (aFace, aTris);
+ return aFace;
+ }
+ return TopoDS_Shape();
+ }
+
+ if (!myResultShape.IsNull()
+ && myResultShape.ShapeType() == TopAbs_COMPOUND
+ && myResultShape.NbChildren() == 1
+ && myActiveSubMesh.Object.IsEmpty())
+ {
+ TopoDS_Iterator aChildIter (myResultShape);
+ return aChildIter.Value();
+ }
+ return myResultShape;
+}
--- /dev/null
+// Author: Kirill Gavrilov
+// Copyright (c) 2019 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#ifndef _RWObj_TriangulationReader_HeaderFile
+#define _RWObj_TriangulationReader_HeaderFile
+
+#include <RWObj_Reader.hxx>
+
+#include <Poly_Triangulation.hxx>
+#include <TopoDS_Compound.hxx>
+
+//! Interface to store shape attributes into document.
+class RWObj_IShapeReceiver
+{
+public:
+ //! @param theShape shape to register
+ //! @param theName shape name
+ //! @param theMaterial shape material
+ //! @param theIsRootShape indicates that this is a root object (free shape)
+ virtual void BindNamedShape (const TopoDS_Shape& theShape,
+ const TCollection_AsciiString& theName,
+ const RWObj_Material* theMaterial,
+ const Standard_Boolean theIsRootShape) = 0;
+};
+
+//! RWObj_Reader implementation dumping OBJ file into Poly_Triangulation.
+class RWObj_TriangulationReader : public RWObj_Reader
+{
+ DEFINE_STANDARD_RTTIEXT(RWObj_TriangulationReader, RWObj_Reader)
+public:
+
+ //! Constructor.
+ RWObj_TriangulationReader() : myShapeReceiver (NULL), myToCreateShapes (Standard_True) {}
+
+ //! Set flag to create shapes.
+ void SetCreateShapes (Standard_Boolean theToCreateShapes) { myToCreateShapes = theToCreateShapes; }
+
+ //! Set shape receiver callback.
+ void SetShapeReceiver (RWObj_IShapeReceiver* theReceiver) { myShapeReceiver = theReceiver; }
+
+ //! Create Poly_Triangulation from collected data
+ Standard_EXPORT virtual Handle(Poly_Triangulation) GetTriangulation();
+
+ //! Return result shape.
+ Standard_EXPORT TopoDS_Shape ResultShape();
+
+protected:
+
+ //! Flush active sub-mesh.
+ Standard_EXPORT virtual Standard_Boolean addMesh (const RWObj_SubMesh& theMesh,
+ const RWObj_SubMeshReason theReason) Standard_OVERRIDE;
+
+ //! Retrieve sub-mesh node position.
+ virtual gp_Pnt getNode (Standard_Integer theIndex) const Standard_OVERRIDE
+ {
+ return myNodes.Value (theIndex - 1);
+ }
+
+ //! Add new node.
+ virtual Standard_Integer addNode (const gp_Pnt& thePnt) Standard_OVERRIDE
+ {
+ myNodes.Append (thePnt);
+ return myNodes.Size();
+ }
+
+ //! Ignore normal.
+ virtual void setNodeNormal (const Standard_Integer theIndex,
+ const Graphic3d_Vec3& theNormal) Standard_OVERRIDE
+ {
+ myNormals.SetValue (theIndex - 1, theNormal);
+ }
+
+ //! Ignore texture coordinates.
+ virtual void setNodeUV (const Standard_Integer theIndex,
+ const Graphic3d_Vec2& theUV) Standard_OVERRIDE
+ {
+ myNodesUV.SetValue (theIndex - 1, theUV);
+ }
+
+ //! Add element.
+ virtual void addElement (Standard_Integer theN1, Standard_Integer theN2, Standard_Integer theN3, Standard_Integer theN4) Standard_OVERRIDE
+ {
+ myTriangles.Append (Poly_Triangle (theN1, theN2, theN3));
+ if (theN4 != -1)
+ {
+ myTriangles.Append (Poly_Triangle (theN1, theN3, theN4));
+ }
+ }
+
+protected:
+
+ //! Add sub-shape into specified shape
+ Standard_EXPORT Standard_Boolean addSubShape (TopoDS_Shape& theParent,
+ const TopoDS_Shape& theSubShape,
+ const Standard_Boolean theToExpandCompound);
+
+protected:
+
+ NCollection_Vector<gp_Pnt> myNodes; //!< nodes of currently filled triangulation
+ NCollection_Vector<Graphic3d_Vec3> myNormals; //!< normals of currently filled triangulation
+ NCollection_Vector<Graphic3d_Vec2> myNodesUV; //!< UVs of currently filled triangulation
+ NCollection_Vector<Poly_Triangle> myTriangles; //!< indexes of currently filled triangulation
+
+ RWObj_IShapeReceiver* myShapeReceiver; //!< optional shape receiver
+ TopoDS_Compound myResultShape; //!< result shape as Compound of objects
+ TopoDS_Compound myLastObjectShape; //!< Compound containing current object groups
+ TopoDS_Shape myLastGroupShape; //!< current group shape - either a single Face or Compound of Faces
+ TCollection_AsciiString myLastGroupName; //!< current group name
+ Standard_Boolean myToCreateShapes; //!< create a single triangulation
+
+};
+
+#endif // _RWObj_TriangulationReader_HeaderFile
#include <Quantity_NameOfColor.hxx>
#include <RWGltf_CafReader.hxx>
#include <RWStl.hxx>
+#include <RWObj.hxx>
+#include <RWObj_CafReader.hxx>
#include <SelectMgr_SelectionManager.hxx>
#include <Standard_ErrorHandler.hxx>
#include <StdSelect_ViewerSelector3d.hxx>
return 0;
}
+//! Parse RWMesh_CoordinateSystem enumeration.
+static Standard_Boolean parseCoordinateSystem (const char* theArg,
+ RWMesh_CoordinateSystem& theSystem)
+{
+ TCollection_AsciiString aCSStr (theArg);
+ aCSStr.LowerCase();
+ if (aCSStr == "zup")
+ {
+ theSystem = RWMesh_CoordinateSystem_Zup;
+ }
+ else if (aCSStr == "yup")
+ {
+ theSystem = RWMesh_CoordinateSystem_Yup;
+ }
+ else
+ {
+ return Standard_False;
+ }
+ return Standard_True;
+}
+
+//=============================================================================
+//function : ReadObj
+//purpose : Reads OBJ file
+//=============================================================================
+static Standard_Integer ReadObj (Draw_Interpretor& theDI,
+ Standard_Integer theNbArgs,
+ const char** theArgVec)
+{
+ TCollection_AsciiString aDestName, aFilePath;
+ Standard_Boolean toUseExistingDoc = Standard_False;
+ Standard_Real aFileUnitFactor = -1.0;
+ RWMesh_CoordinateSystem aResultCoordSys = RWMesh_CoordinateSystem_Zup, aFileCoordSys = RWMesh_CoordinateSystem_Yup;
+ Standard_Boolean toListExternalFiles = Standard_False, isSingleFace = Standard_False, isSinglePrecision = Standard_False;
+ Standard_Boolean isNoDoc = (TCollection_AsciiString(theArgVec[0]) == "readobj");
+ for (Standard_Integer anArgIter = 1; anArgIter < theNbArgs; ++anArgIter)
+ {
+ TCollection_AsciiString anArgCase (theArgVec[anArgIter]);
+ anArgCase.LowerCase();
+ if (anArgIter + 1 < theNbArgs
+ && (anArgCase == "-unit"
+ || anArgCase == "-units"
+ || anArgCase == "-fileunit"
+ || anArgCase == "-fileunits"))
+ {
+ const TCollection_AsciiString aUnitStr (theArgVec[++anArgIter]);
+ aFileUnitFactor = UnitsAPI::AnyToSI (1.0, aUnitStr.ToCString());
+ if (aFileUnitFactor <= 0.0)
+ {
+ std::cout << "Syntax error: wrong length unit '" << aUnitStr << "'\n";
+ return 1;
+ }
+ }
+ else if (anArgIter + 1 < theNbArgs
+ && (anArgCase == "-filecoordinatesystem"
+ || anArgCase == "-filecoordsystem"
+ || anArgCase == "-filecoordsys"))
+ {
+ if (!parseCoordinateSystem (theArgVec[++anArgIter], aFileCoordSys))
+ {
+ std::cout << "Syntax error: unknown coordinate system '" << theArgVec[anArgIter] << "'\n";
+ return 1;
+ }
+ }
+ else if (anArgIter + 1 < theNbArgs
+ && (anArgCase == "-resultcoordinatesystem"
+ || anArgCase == "-resultcoordsystem"
+ || anArgCase == "-resultcoordsys"
+ || anArgCase == "-rescoordsys"))
+ {
+ if (!parseCoordinateSystem (theArgVec[++anArgIter], aResultCoordSys))
+ {
+ std::cout << "Syntax error: unknown coordinate system '" << theArgVec[anArgIter] << "'\n";
+ return 1;
+ }
+ }
+ else if (anArgCase == "-singleprecision"
+ || anArgCase == "-singleprec")
+ {
+ isSinglePrecision = Standard_True;
+ if (anArgIter + 1 < theNbArgs
+ && ViewerTest::ParseOnOff (theArgVec[anArgIter + 1], isSinglePrecision))
+ {
+ ++anArgIter;
+ }
+ }
+ else if (isNoDoc
+ && (anArgCase == "-singleface"
+ || anArgCase == "-singletriangulation"))
+ {
+ isSingleFace = Standard_True;
+ }
+ else if (!isNoDoc
+ && (anArgCase == "-nocreate"
+ || anArgCase == "-nocreatedoc"))
+ {
+ toUseExistingDoc = Standard_True;
+ if (anArgIter + 1 < theNbArgs
+ && ViewerTest::ParseOnOff (theArgVec[anArgIter + 1], toUseExistingDoc))
+ {
+ ++anArgIter;
+ }
+ }
+ else if (anArgCase == "-listexternalfiles"
+ || anArgCase == "-listexternals"
+ || anArgCase == "-listexternal"
+ || anArgCase == "-external"
+ || anArgCase == "-externalfiles")
+ {
+ toListExternalFiles = Standard_True;
+ }
+ else if (aDestName.IsEmpty())
+ {
+ aDestName = theArgVec[anArgIter];
+ }
+ else if (aFilePath.IsEmpty())
+ {
+ aFilePath = theArgVec[anArgIter];
+ }
+ else
+ {
+ std::cout << "Syntax error at '" << theArgVec[anArgIter] << "'\n";
+ return 1;
+ }
+ }
+ if (aFilePath.IsEmpty())
+ {
+ std::cout << "Syntax error: wrong number of arguments\n";
+ return 1;
+ }
+
+ Handle(Draw_ProgressIndicator) aProgress = new Draw_ProgressIndicator (theDI, 1);
+ Handle(TDocStd_Document) aDoc;
+ if (!isNoDoc
+ && !toListExternalFiles)
+ {
+ Handle(TDocStd_Application) anApp = DDocStd::GetApplication();
+ Standard_CString aNameVar = aDestName.ToCString();
+ DDocStd::GetDocument (aNameVar, aDoc, Standard_False);
+ if (aDoc.IsNull())
+ {
+ if (toUseExistingDoc)
+ {
+ std::cout << "Error: document with name " << aDestName << " does not exist\n";
+ return 1;
+ }
+ anApp->NewDocument (TCollection_ExtendedString ("BinXCAF"), aDoc);
+ }
+ else if (!toUseExistingDoc)
+ {
+ std::cout << "Error: document with name " << aDestName << " already exists\n";
+ return 1;
+ }
+ }
+
+ RWObj_CafReader aReader;
+ aReader.SetSinglePrecision (isSinglePrecision);
+ aReader.SetSystemLengthUnit (UnitsMethods::GetCasCadeLengthUnit() * 0.001);
+ aReader.SetSystemCoordinateSystem (aResultCoordSys);
+ aReader.SetFileLengthUnit (aFileUnitFactor);
+ aReader.SetFileCoordinateSystem (aFileCoordSys);
+ aReader.SetDocument (aDoc);
+ if (isSingleFace)
+ {
+ RWObj_TriangulationReader aSimpleReader;
+ aSimpleReader.SetSinglePrecision (isSinglePrecision);
+ aSimpleReader.SetCreateShapes (Standard_False);
+ aSimpleReader.SetTransformation (aReader.CoordinateSystemConverter());
+ aSimpleReader.Read (aFilePath.ToCString(), aProgress);
+
+ Handle(Poly_Triangulation) aTriangulation = aSimpleReader.GetTriangulation();
+ TopoDS_Face aFace;
+ BRep_Builder aBuiler;
+ aBuiler.MakeFace (aFace);
+ aBuiler.UpdateFace (aFace, aTriangulation);
+ DBRep::Set (aDestName.ToCString(), aFace);
+ return 0;
+ }
+
+ if (toListExternalFiles)
+ {
+ aReader.ProbeHeader (aFilePath);
+ for (NCollection_IndexedMap<TCollection_AsciiString>::Iterator aFileIter (aReader.ExternalFiles()); aFileIter.More(); aFileIter.Next())
+ {
+ theDI << "\"" << aFileIter.Value() << "\" ";
+ }
+ }
+ else
+ {
+ aReader.Perform (aFilePath, aProgress);
+ if (isNoDoc)
+ {
+ DBRep::Set (aDestName.ToCString(), aReader.SingleShape());
+ }
+ else
+ {
+ Handle(DDocStd_DrawDocument) aDrawDoc = new DDocStd_DrawDocument (aDoc);
+ TDataStd_Name::Set (aDoc->GetData()->Root(), aDestName.ToCString());
+ Draw::Set (aDestName.ToCString(), aDrawDoc);
+ }
+ }
+ return 0;
+}
+
static Standard_Integer writevrml
(Draw_Interpretor& di, Standard_Integer argc, const char** argv)
{
"\n\t\t: Single triangulation-only Face is created otherwise (default).",
__FILE__, readstl, g);
theCommands.Add ("loadvrml" , "shape file",__FILE__,loadvrml,g);
+ theCommands.Add ("ReadObj",
+ "ReadObj Doc file [-fileCoordSys {Zup|Yup}] [-fileUnit Unit]"
+ "\n\t\t: [-resultCoordSys {Zup|Yup}] [-singlePrecision]"
+ "\n\t\t: [-listExternalFiles] [-noCreateDoc]"
+ "\n\t\t: Read OBJ file into XDE document."
+ "\n\t\t: -fileUnit length unit of OBJ file content;"
+ "\n\t\t: -fileCoordSys coordinate system defined by OBJ file; Yup when not specified."
+ "\n\t\t: -resultCoordSys result coordinate system; Zup when not specified."
+ "\n\t\t: -singlePrecision truncate vertex data to single precision during read; FALSE by default."
+ "\n\t\t: -listExternalFiles do not read mesh and only list external files."
+ "\n\t\t: -noCreateDoc read into existing XDE document.",
+ __FILE__, ReadObj, g);
+ theCommands.Add ("readobj",
+ "readobj shape file [-fileCoordSys {Zup|Yup}] [-fileUnit Unit]"
+ "\n\t\t: [-resultCoordSys {Zup|Yup}] [-singlePrecision]"
+ "\n\t\t: [-singleFace]"
+ "\n\t\t: Same as ReadObj but reads OBJ file into a shape instead of a document."
+ "\n\t\t: -singleFace merge OBJ content into a single triangulation Face.",
+ __FILE__, ReadObj, g);
theCommands.Add ("meshfromstl", "creates MeshVS_Mesh from STL file", __FILE__, createmesh, g );
theCommands.Add ("mesh3delem", "creates 3d element mesh to test", __FILE__, create3d, g );
001 stl_read
002 shape_write_stl
003 gltf_read
+004 obj_read
--- /dev/null
+pload XDE OCAF MODELING VISUALIZATION
+catch { Close D }
--- /dev/null
+vclear
+vinit View1
+XDisplay -dispMode 1 D -explore
+vaxo
+vfit
+vdump ${imagedir}/${casename}.png
--- /dev/null
+puts "========"
+puts "0029296: Data Exchange - implement import of mesh data from files in OBJ format"
+puts "Small textured airplane model"
+puts "========"
+
+ReadObj D [locate_data_file "P-51 Mustang.obj"]
+XGetOneShape s D
+checknbshapes s -face 14 -compound 1
+checktrinfo s -tri 4309 -nod 4727
--- /dev/null
+puts "========"
+puts "0029296: Data Exchange - implement import of mesh data from files in OBJ format"
+puts "Reading small generated file."
+puts "========"
+
+set material_mtl {newmtl ObjMat1
+Ns 96.07
+Ka 0.00 0.00 0.00
+Kd 0.64 0.64 0.64
+Ks 0.50 0.50 0.50
+Ni 1.00
+d 1.00}
+
+set minimal_ascii_obj {mtllib A1_material.mtl
+g Group1
+usemtl ObjMat1
+v 0 0 0
+v 2 0 0
+v 2 1 0
+v 1 2 0
+v 0 1 0
+v 0 0 2
+v 2 0 2
+v 2 1 2
+v 1 2 2
+v 0 1 2
+f 5 4 3 2 1
+f 7 8 9 10 6
+f 10 9 4 5
+f 9 8 3 4
+f 6 10 5 1
+f 2 3 8 7
+f 1 2 7 6}
+
+# Ascii MTL file, CRLF
+set fd [open ${imagedir}/${casename}_material.mtl w]
+fconfigure $fd -translation crlf
+puts $fd $material_mtl
+close $fd
+
+puts ""
+puts "#======================================================================"
+puts "# Ascii file, CRLF"
+puts "#======================================================================"
+set fd [open ${imagedir}/${casename}_one_ascii_dos.obj w]
+fconfigure $fd -translation crlf
+puts $fd $minimal_ascii_obj
+close $fd
+readobj mcrlf ${imagedir}/${casename}_one_ascii_dos.obj -singleFace
+checknbshapes mcrlf -face 1
+checktrinfo mcrlf -tri 16 -nod 10
+
+puts ""
+puts "#======================================================================"
+puts "# Ascii file with single facet, LF"
+puts "#======================================================================"
+set fd [open ${imagedir}/${casename}_one_ascii_unix.obj w]
+fconfigure $fd -translation lf
+puts $fd $minimal_ascii_obj
+close $fd
+readobj mlf ${imagedir}/${casename}_one_ascii_unix.obj -singleFace
+checknbshapes mlf -face 1
+checktrinfo mlf -tri 16 -nod 10
+
+vclear
+vinit View1
+vdisplay -dispMode 1 mlf
+vaxo
+vfit
+vdump ${imagedir}/${casename}_raw.png
+
+# read OBJ into document
+ReadObj D ${imagedir}/${casename}_one_ascii_unix.obj
--- /dev/null
+puts "========"
+puts "0029296: Data Exchange - implement import of mesh data from files in OBJ format"
+puts "Ship model with transparent windows"
+puts "========"
+
+ReadObj D [locate_data_file ship_boat.obj]
+XGetOneShape s D
+checknbshapes s -face 2359 -compound 2
+checktrinfo s -tri 27297 -nod 40496