1
0
mirror of https://git.dev.opencascade.org/repos/occt.git synced 2025-04-10 18:51:21 +03:00

0029296: Data Exchange - implement import of mesh data from files in OBJ format

RWObj_Reader and RWObj_CafReader - added new classes reading triangulation from OBJ file.
This commit is contained in:
kgv 2019-05-05 20:31:35 +03:00 committed by bugmaster
parent 0a419c51ed
commit 4151c94d20
24 changed files with 2705 additions and 0 deletions

View File

@ -442,3 +442,4 @@ n XCAFNoteObjects
t TKRWMesh
n RWGltf
n RWMesh
n RWObj

14
src/RWObj/FILES Normal file
View File

@ -0,0 +1,14 @@
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

32
src/RWObj/RWObj.cxx Normal file
View File

@ -0,0 +1,32 @@
// 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();
}

35
src/RWObj/RWObj.hxx Normal file
View File

@ -0,0 +1,35 @@
// 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

View File

@ -0,0 +1,104 @@
// 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;
}

View File

@ -0,0 +1,63 @@
// 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

View File

@ -0,0 +1,43 @@
// 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

View File

@ -0,0 +1,351 @@
// 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;
}

View File

@ -0,0 +1,58 @@
// 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

829
src/RWObj/RWObj_Reader.cxx Normal file
View File

@ -0,0 +1,829 @@
// 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;
}

364
src/RWObj/RWObj_Reader.hxx Normal file
View File

@ -0,0 +1,364 @@
// 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

View File

@ -0,0 +1,30 @@
// 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

View File

@ -0,0 +1,27 @@
// 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

81
src/RWObj/RWObj_Tools.hxx Normal file
View File

@ -0,0 +1,81 @@
// 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

View File

@ -0,0 +1,223 @@
// 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;
}

View File

@ -0,0 +1,124 @@
// 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

View File

@ -1,2 +1,3 @@
RWGltf
RWMesh
RWObj

View File

@ -44,6 +44,8 @@
#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>
@ -290,6 +292,210 @@ static Standard_Integer readstl(Draw_Interpretor& theDI,
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)
{
@ -1404,6 +1610,25 @@ void XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands)
"\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 );

View File

@ -1,3 +1,4 @@
001 stl_read
002 shape_write_stl
003 gltf_read
004 obj_read

View File

@ -0,0 +1,2 @@
pload XDE OCAF MODELING VISUALIZATION
catch { Close D }

View File

@ -0,0 +1,6 @@
vclear
vinit View1
XDisplay -dispMode 1 D -explore
vaxo
vfit
vdump ${imagedir}/${casename}.png

View File

@ -0,0 +1,9 @@
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

View File

@ -0,0 +1,73 @@
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

View File

@ -0,0 +1,9 @@
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