diff --git a/src/Message/FILES b/src/Message/FILES index 1f553d28bf..f611229c87 100755 --- a/src/Message/FILES +++ b/src/Message/FILES @@ -21,6 +21,7 @@ Message_CompositeAlerts.hxx Message_ExecStatus.hxx Message_Gravity.hxx Message_HArrayOfMsg.hxx +Message_LazyProgressScope.hxx Message_Level.cxx Message_Level.hxx Message_ListIteratorOfListOfMsg.hxx diff --git a/src/Message/Message_LazyProgressScope.hxx b/src/Message/Message_LazyProgressScope.hxx new file mode 100644 index 0000000000..a4ddef8ecb --- /dev/null +++ b/src/Message/Message_LazyProgressScope.hxx @@ -0,0 +1,80 @@ +// Copyright (c) 2017-2021 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 _Message_LazyProgressScope_HeaderFiler +#define _Message_LazyProgressScope_HeaderFiler + +#include + +//! Progress scope with lazy updates and abort fetches. +//! +//! Although Message_ProgressIndicator implementation is encouraged to spare GUI updates, +//! even optimized implementation might show a noticeable overhead on a very small update step (e.g. per triangle). +//! +//! The class splits initial (displayed) number of overall steps into larger chunks specified in constructor, +//! so that displayed progress is updated at larger steps. +class Message_LazyProgressScope : protected Message_ProgressScope +{ +public: + + //! Main constructor. + //! @param theRange [in] progress range to scope + //! @param theName [in] name of this scope + //! @param theMax [in] number of steps within this scope + //! @param thePatchStep [in] number of steps to update progress + //! @param theIsInf [in] infinite flag + Message_LazyProgressScope (const Message_ProgressRange& theRange, + const char* theName, + const Standard_Real theMax, + const Standard_Real thePatchStep, + const Standard_Boolean theIsInf = Standard_False) + : Message_ProgressScope (theRange, theName, theMax, theIsInf), + myPatchStep (thePatchStep), + myPatchProgress (0.0), + myIsLazyAborted (Standard_False) {} + + //! Increment progress with 1. + void Next() + { + if (++myPatchProgress < myPatchStep) + { + return; + } + + myPatchProgress = 0.0; + Message_ProgressScope::Next (myPatchStep); + IsAborted(); + } + + //! Return TRUE if progress has been aborted - return the cached state lazily updated. + Standard_Boolean More() const + { + return !myIsLazyAborted; + } + + //! Return TRUE if progress has been aborted - fetches actual value from the Progress. + Standard_Boolean IsAborted() + { + myIsLazyAborted = myIsLazyAborted || !Message_ProgressScope::More(); + return myIsLazyAborted; + } + +protected: + + Standard_Real myPatchStep; + Standard_Real myPatchProgress; + Standard_Boolean myIsLazyAborted; + +}; + +#endif // _Message_LazyProgressScope_HeaderFiler diff --git a/src/RWGltf/RWGltf_CafWriter.cxx b/src/RWGltf/RWGltf_CafWriter.cxx index aebe94348d..3dedb7f8f7 100644 --- a/src/RWGltf/RWGltf_CafWriter.cxx +++ b/src/RWGltf/RWGltf_CafWriter.cxx @@ -1068,7 +1068,7 @@ void RWGltf_CafWriter::writeAsset (const TColStd_IndexedDataMapOfStringString& t myWriter->Key (RWGltf_GltfRootElementName (RWGltf_GltfRootElement_Asset)); myWriter->StartObject(); myWriter->Key ("generator"); - myWriter->String ("Open CASCADE Technology [www.opencascade.com]"); + myWriter->String ("Open CASCADE Technology [dev.opencascade.org]"); myWriter->Key ("version"); myWriter->String ("2.0"); // glTF format version diff --git a/src/RWMesh/RWMesh_FaceIterator.cxx b/src/RWMesh/RWMesh_FaceIterator.cxx index ec3dd5e54d..cdcd5241d7 100644 --- a/src/RWMesh/RWMesh_FaceIterator.cxx +++ b/src/RWMesh/RWMesh_FaceIterator.cxx @@ -127,7 +127,7 @@ void RWMesh_FaceIterator::dispatchStyles (const TDF_Label& theLabel, // function : normal // purpose : // ======================================================================= -gp_Dir RWMesh_FaceIterator::normal (Standard_Integer theNode) +gp_Dir RWMesh_FaceIterator::normal (Standard_Integer theNode) const { gp_Dir aNormal (gp::DZ()); if (myPolyTriang->HasNormals()) diff --git a/src/RWMesh/RWMesh_FaceIterator.hxx b/src/RWMesh/RWMesh_FaceIterator.hxx index 6547c11f54..f70b378610 100644 --- a/src/RWMesh/RWMesh_FaceIterator.hxx +++ b/src/RWMesh/RWMesh_FaceIterator.hxx @@ -100,7 +100,7 @@ public: bool HasTexCoords() const { return !myPolyTriang.IsNull() && myPolyTriang->HasUVNodes(); } //! Return normal at specified node index with face transformation applied and face orientation applied. - gp_Dir NormalTransformed (Standard_Integer theNode) + gp_Dir NormalTransformed (Standard_Integer theNode) const { gp_Dir aNorm = normal (theNode); if (myTrsf.Form() != gp_Identity) @@ -148,7 +148,7 @@ public: gp_Pnt node (const Standard_Integer theNode) const { return myPolyTriang->Node (theNode); } //! Return normal at specified node index without face transformation applied. - Standard_EXPORT gp_Dir normal (Standard_Integer theNode); + Standard_EXPORT gp_Dir normal (Standard_Integer theNode) const; //! Return triangle with specified index. Poly_Triangle triangle (Standard_Integer theElemIndex) const { return myPolyTriang->Triangle (theElemIndex); } @@ -185,7 +185,7 @@ private: TopoDS_Face myFace; //!< current face Handle(Poly_Triangulation) myPolyTriang; //!< triangulation of current face TopLoc_Location myFaceLocation; //!< current face location - BRepLProp_SLProps mySLTool; //!< auxiliary tool for fetching normals from surface + mutable BRepLProp_SLProps mySLTool; //!< auxiliary tool for fetching normals from surface BRepAdaptor_Surface myFaceAdaptor; //!< surface adaptor for fetching normals from surface Standard_Boolean myHasNormals; //!< flag indicating that current face has normals gp_Trsf myTrsf; //!< current face transformation diff --git a/src/RWObj/FILES b/src/RWObj/FILES index f6a988e6fb..2c3177566a 100644 --- a/src/RWObj/FILES +++ b/src/RWObj/FILES @@ -2,9 +2,15 @@ RWObj.cxx RWObj.hxx RWObj_CafReader.cxx RWObj_CafReader.hxx +RWObj_CafWriter.cxx +RWObj_CafWriter.hxx RWObj_Material.hxx RWObj_MtlReader.cxx RWObj_MtlReader.hxx +RWObj_ObjMaterialMap.cxx +RWObj_ObjMaterialMap.hxx +RWObj_ObjWriterContext.cxx +RWObj_ObjWriterContext.hxx RWObj_Reader.cxx RWObj_Reader.hxx RWObj_SubMesh.hxx diff --git a/src/RWObj/RWObj_CafWriter.cxx b/src/RWObj/RWObj_CafWriter.cxx new file mode 100644 index 0000000000..979af884d5 --- /dev/null +++ b/src/RWObj/RWObj_CafWriter.cxx @@ -0,0 +1,416 @@ +// Copyright (c) 2015-2021 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +IMPLEMENT_STANDARD_RTTIEXT(RWObj_CafWriter, Standard_Transient) + +namespace +{ + //! Trivial cast. + inline Graphic3d_Vec3 objXyzToVec (const gp_XYZ& thePnt) + { + return Graphic3d_Vec3 ((float )thePnt.X(), (float )thePnt.Y(), (float )thePnt.Z()); + } + + //! Trivial cast. + inline Graphic3d_Vec2 objXyToVec (const gp_XY& thePnt) + { + return Graphic3d_Vec2 ((float )thePnt.X(), (float )thePnt.Y()); + } + + //! Read name attribute. + static TCollection_AsciiString readNameAttribute (const TDF_Label& theRefLabel) + { + Handle(TDataStd_Name) aNodeName; + if (!theRefLabel.FindAttribute (TDataStd_Name::GetID(), aNodeName)) + { + return TCollection_AsciiString(); + } + return TCollection_AsciiString (aNodeName->Get()); + } +} + +//================================================================ +// Function : Constructor +// Purpose : +//================================================================ +RWObj_CafWriter::RWObj_CafWriter (const TCollection_AsciiString& theFile) +: myFile (theFile) +{ + // OBJ file format doesn't define length units; + // Y-up coordinate system is most commonly used (but also undefined) + //myCSTrsf.SetOutputCoordinateSystem (RWMesh_CoordinateSystem_negZfwd_posYup); +} + +//================================================================ +// Function : Destructor +// Purpose : +//================================================================ +RWObj_CafWriter::~RWObj_CafWriter() +{ + // +} + +//================================================================ +// Function : toSkipFaceMesh +// Purpose : +//================================================================ +Standard_Boolean RWObj_CafWriter::toSkipFaceMesh (const RWMesh_FaceIterator& theFaceIter) +{ + return theFaceIter.IsEmptyMesh(); +} + +// ======================================================================= +// function : Perform +// purpose : +// ======================================================================= +bool RWObj_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument, + const TColStd_IndexedDataMapOfStringString& theFileInfo, + const Message_ProgressRange& theProgress) +{ + TDF_LabelSequence aRoots; + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool (theDocument->Main()); + aShapeTool->GetFreeShapes (aRoots); + return Perform (theDocument, aRoots, NULL, theFileInfo, theProgress); +} + +// ======================================================================= +// function : Perform +// purpose : +// ======================================================================= +bool RWObj_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument, + const TDF_LabelSequence& theRootLabels, + const TColStd_MapOfAsciiString* theLabelFilter, + const TColStd_IndexedDataMapOfStringString& theFileInfo, + const Message_ProgressRange& theProgress) +{ + TCollection_AsciiString aFolder, aFileName, aFullFileNameBase, aShortFileNameBase, aFileExt; + OSD_Path::FolderAndFileFromPath (myFile, aFolder, aFileName); + OSD_Path::FileNameAndExtension (aFileName, aShortFileNameBase, aFileExt); + + if (theRootLabels.IsEmpty() + || (theLabelFilter != NULL && theLabelFilter->IsEmpty())) + { + Message::SendFail ("Nothing to export into OBJ file"); + return false; + } + + Standard_Integer aNbNodesAll = 0, aNbElemsAll = 0; + Standard_Real aNbPEntities = 0; // steps for progress range + bool toCreateMatFile = false; + for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes); + aDocExplorer.More(); aDocExplorer.Next()) + { + const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current(); + if (theLabelFilter != NULL + && !theLabelFilter->Contains (aDocNode.Id)) + { + continue; + } + + for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, aDocNode.Location, true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next()) + { + if (toSkipFaceMesh (aFaceIter)) + { + continue; + } + + addFaceInfo (aFaceIter, aNbNodesAll, aNbElemsAll, aNbPEntities, toCreateMatFile); + } + } + if (aNbNodesAll == 0 + || aNbElemsAll == 0) + { + Message::SendFail ("No mesh data to save"); + return false; + } + + TCollection_AsciiString aMatFileNameShort = aShortFileNameBase + ".mtl"; + const TCollection_AsciiString aMatFileNameFull = !aFolder.IsEmpty() ? aFolder + aMatFileNameShort : aMatFileNameShort; + if (!toCreateMatFile) + { + aMatFileNameShort.Clear(); + } + + Standard_CLocaleSentry aLocaleSentry; + RWObj_ObjWriterContext anObjFile(myFile); + RWObj_ObjMaterialMap aMatMgr (aMatFileNameFull); + aMatMgr.SetDefaultStyle (myDefaultStyle); + if (!anObjFile.IsOpened() + || !anObjFile.WriteHeader (aNbNodesAll, aNbElemsAll, aMatFileNameShort, theFileInfo)) + { + return false; + } + + int aRootDepth = 0; + if (theRootLabels.Size() == 1) + { + TDF_Label aRefLabel = theRootLabels.First(); + XCAFDoc_ShapeTool::GetReferredShape (theRootLabels.First(), aRefLabel); + TCollection_AsciiString aRootName = readNameAttribute (aRefLabel); + if (aRootName.EndsWith (".obj")) + { + // workaround import/export of .obj file + aRootDepth = 1; + } + } + + // simple global progress sentry - ignores size of node and index data + const Standard_Real aPatchStep = 2048.0; // about 100 KiB + Message_LazyProgressScope aPSentry (theProgress, "OBJ export", aNbPEntities, aPatchStep); + + bool isDone = true; + for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes); + aDocExplorer.More() && !aPSentry.IsAborted(); aDocExplorer.Next()) + { + const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current(); + if (theLabelFilter != NULL + && !theLabelFilter->Contains (aDocNode.Id)) + { + continue; + } + + TCollection_AsciiString aName = readNameAttribute (aDocNode.RefLabel); + for (int aParentIter = aDocExplorer.CurrentDepth() - 1; aParentIter >= aRootDepth; --aParentIter) + { + const TCollection_AsciiString aParentName = readNameAttribute (aDocExplorer.Current (aParentIter).RefLabel); + if (!aParentName.IsEmpty()) + { + aName = aParentName + "/" + aName; + } + } + + if (!writeShape (anObjFile, aMatMgr, aPSentry, aDocNode.RefLabel, aDocNode.Location, aDocNode.Style, aName)) + { + isDone = false; + break; + } + } + + const bool isClosed = anObjFile.Close(); + if (isDone && !isClosed) + { + Message::SendFail (TCollection_AsciiString ("Failed to write OBJ file\n") + myFile); + return false; + } + return isDone && !aPSentry.IsAborted(); +} + +// ======================================================================= +// function : addFaceInfo +// purpose : +// ======================================================================= +void RWObj_CafWriter::addFaceInfo (const RWMesh_FaceIterator& theFace, + Standard_Integer& theNbNodes, + Standard_Integer& theNbElems, + Standard_Real& theNbProgressSteps, + Standard_Boolean& theToCreateMatFile) +{ + theNbNodes += theFace.NbNodes(); + theNbElems += theFace.NbTriangles(); + + theNbProgressSteps += theFace.NbNodes(); + theNbProgressSteps += theFace.NbTriangles(); + if (theFace.HasNormals()) + { + theNbProgressSteps += theFace.NbNodes(); + } + if (theFace.HasTexCoords()) //&& !theFace.FaceStyle().Texture().IsEmpty() + { + theNbProgressSteps += theFace.NbNodes(); + } + + theToCreateMatFile = theToCreateMatFile + || theFace.HasFaceColor() + || (!theFace.FaceStyle().BaseColorTexture().IsNull() && theFace.HasTexCoords()); +} + +// ======================================================================= +// function : writeShape +// purpose : +// ======================================================================= +bool RWObj_CafWriter::writeShape (RWObj_ObjWriterContext& theWriter, + RWObj_ObjMaterialMap& theMatMgr, + Message_LazyProgressScope& thePSentry, + const TDF_Label& theLabel, + const TopLoc_Location& theParentTrsf, + const XCAFPrs_Style& theParentStyle, + const TCollection_AsciiString& theName) +{ + bool toCreateGroup = true; + for (RWMesh_FaceIterator aFaceIter (theLabel, theParentTrsf, true, theParentStyle); aFaceIter.More() && !thePSentry.IsAborted(); aFaceIter.Next()) + { + if (toSkipFaceMesh (aFaceIter)) + { + continue; + } + + ++theWriter.NbFaces; + { + const bool hasNormals = aFaceIter.HasNormals(); + const bool hasTexCoords = aFaceIter.HasTexCoords(); //&& !aFaceIter.FaceStyle().Texture().IsEmpty(); + if (theWriter.NbFaces != 1) + { + toCreateGroup = toCreateGroup + || hasNormals != theWriter.HasNormals() + || hasTexCoords != theWriter.HasTexCoords(); + } + theWriter.SetNormals (hasNormals); + theWriter.SetTexCoords(hasTexCoords); + } + + if (toCreateGroup + && !theWriter.WriteGroup (theName)) + { + return false; + } + toCreateGroup = false; + + TCollection_AsciiString aMatName; + if (aFaceIter.HasFaceColor() + || !aFaceIter.FaceStyle().BaseColorTexture().IsNull()) + { + aMatName = theMatMgr.AddMaterial (aFaceIter.FaceStyle()); + } + if (aMatName != theWriter.ActiveMaterial()) + { + theWriter.WriteActiveMaterial (aMatName); + } + + // write nodes + if (!writePositions (theWriter, thePSentry, aFaceIter)) + { + return false; + } + + // write normals + if (theWriter.HasNormals() + && !writeNormals (theWriter, thePSentry, aFaceIter)) + { + return false; + } + + if (theWriter.HasTexCoords() + && !writeTextCoords (theWriter, thePSentry, aFaceIter)) + { + return false; + } + + if (!writeIndices (theWriter, thePSentry, aFaceIter)) + { + return false; + } + theWriter.FlushFace (aFaceIter.NbNodes()); + } + return true; +} + +// ======================================================================= +// function : writePositions +// purpose : +// ======================================================================= +bool RWObj_CafWriter::writePositions (RWObj_ObjWriterContext& theWriter, + Message_LazyProgressScope& thePSentry, + const RWMesh_FaceIterator& theFace) +{ + const Standard_Integer aNodeUpper = theFace.NodeUpper(); + for (Standard_Integer aNodeIter = theFace.NodeLower(); aNodeIter <= aNodeUpper && thePSentry.More(); ++aNodeIter, thePSentry.Next()) + { + gp_XYZ aNode = theFace.NodeTransformed (aNodeIter).XYZ(); + myCSTrsf.TransformPosition (aNode); + if (!theWriter.WriteVertex (objXyzToVec (aNode))) + { + return false; + } + } + return true; +} + +// ======================================================================= +// function : writeNormals +// purpose : +// ======================================================================= +bool RWObj_CafWriter::writeNormals (RWObj_ObjWriterContext& theWriter, + Message_LazyProgressScope& thePSentry, + const RWMesh_FaceIterator& theFace) +{ + const Standard_Integer aNodeUpper = theFace.NodeUpper(); + for (Standard_Integer aNodeIter = theFace.NodeLower(); aNodeIter <= aNodeUpper && thePSentry.More(); ++aNodeIter, thePSentry.Next()) + { + const gp_Dir aNormal = theFace.NormalTransformed (aNodeIter); + Graphic3d_Vec3 aNormVec3 = objXyzToVec (aNormal.XYZ()); + myCSTrsf.TransformNormal (aNormVec3); + if (!theWriter.WriteNormal (aNormVec3)) + { + return false; + } + } + return true; +} + +// ======================================================================= +// function : writeTextCoords +// purpose : +// ======================================================================= +bool RWObj_CafWriter::writeTextCoords (RWObj_ObjWriterContext& theWriter, + Message_LazyProgressScope& thePSentry, + const RWMesh_FaceIterator& theFace) +{ + const Standard_Integer aNodeUpper = theFace.NodeUpper(); + for (Standard_Integer aNodeIter = theFace.NodeLower(); aNodeIter <= aNodeUpper && thePSentry.More(); ++aNodeIter, thePSentry.Next()) + { + gp_Pnt2d aTexCoord = theFace.NodeTexCoord (aNodeIter); + if (!theWriter.WriteTexCoord (objXyToVec (aTexCoord.XY()))) + { + return false; + } + } + return true; +} + +// ======================================================================= +// function : writeIndices +// purpose : +// ======================================================================= +bool RWObj_CafWriter::writeIndices (RWObj_ObjWriterContext& theWriter, + Message_LazyProgressScope& thePSentry, + const RWMesh_FaceIterator& theFace) +{ + const Standard_Integer anElemLower = theFace.ElemLower(); + const Standard_Integer anElemUpper = theFace.ElemUpper(); + for (Standard_Integer anElemIter = anElemLower; anElemIter <= anElemUpper && thePSentry.More(); ++anElemIter, thePSentry.Next()) + { + const Poly_Triangle aTri = theFace.TriangleOriented (anElemIter); + if (!theWriter.WriteTriangle (Graphic3d_Vec3i (aTri(1), aTri(2), aTri(3)) - Graphic3d_Vec3i (anElemLower))) + { + return false; + } + } + return true; +} diff --git a/src/RWObj/RWObj_CafWriter.hxx b/src/RWObj/RWObj_CafWriter.hxx new file mode 100644 index 0000000000..001f38b531 --- /dev/null +++ b/src/RWObj/RWObj_CafWriter.hxx @@ -0,0 +1,167 @@ +// Copyright (c) 2015-2021 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_CafWriter_HeaderFiler +#define _RWObj_CafWriter_HeaderFiler + +#include +#include +#include +#include +#include +#include + +#include + +class Message_ProgressRange; +class RWMesh_FaceIterator; +class TDocStd_Document; + +class Message_LazyProgressScope; +class RWObj_ObjWriterContext; +class RWObj_ObjMaterialMap; + +//! OBJ writer context from XCAF document. +class RWObj_CafWriter : public Standard_Transient +{ + DEFINE_STANDARD_RTTIEXT(RWObj_CafWriter, Standard_Transient) +public: + + //! Main constructor. + //! @param theFile [in] path to output OBJ file + Standard_EXPORT RWObj_CafWriter (const TCollection_AsciiString& theFile); + + //! Destructor. + Standard_EXPORT virtual ~RWObj_CafWriter(); + + //! Return transformation from OCCT to OBJ coordinate system. + const RWMesh_CoordinateSystemConverter& CoordinateSystemConverter() const { return myCSTrsf; } + + //! Return transformation from OCCT to OBJ coordinate system. + RWMesh_CoordinateSystemConverter& ChangeCoordinateSystemConverter() { return myCSTrsf; } + + //! Set transformation from OCCT to OBJ coordinate system. + void SetCoordinateSystemConverter (const RWMesh_CoordinateSystemConverter& theConverter) { myCSTrsf = theConverter; } + + //! Return default material definition to be used for nodes with only color defined. + const XCAFPrs_Style& DefaultStyle() const { return myDefaultStyle; } + + //! Set default material definition to be used for nodes with only color defined. + void SetDefaultStyle (const XCAFPrs_Style& theStyle) { myDefaultStyle = theStyle; } + + //! Write OBJ file and associated MTL material file. + //! Triangulation data should be precomputed within shapes! + //! @param theDocument [in] input document + //! @param theRootLabels [in] list of root shapes to export + //! @param theLabelFilter [in] optional filter with document nodes to export, + //! with keys defined by XCAFPrs_DocumentExplorer::DefineChildId() and filled recursively + //! (leaves and parent assembly nodes at all levels); + //! when not NULL, all nodes not included into the map will be ignored + //! @param theFileInfo [in] map with file metadata to put into OBJ header section + //! @param theProgress [in] optional progress indicator + //! @return FALSE on file writing failure + Standard_EXPORT virtual bool Perform (const Handle(TDocStd_Document)& theDocument, + const TDF_LabelSequence& theRootLabels, + const TColStd_MapOfAsciiString* theLabelFilter, + const TColStd_IndexedDataMapOfStringString& theFileInfo, + const Message_ProgressRange& theProgress); + + //! Write OBJ file and associated MTL material file. + //! Triangulation data should be precomputed within shapes! + //! @param theDocument [in] input document + //! @param theFileInfo [in] map with file metadata to put into glTF header section + //! @param theProgress [in] optional progress indicator + //! @return FALSE on file writing failure + Standard_EXPORT virtual bool Perform (const Handle(TDocStd_Document)& theDocument, + const TColStd_IndexedDataMapOfStringString& theFileInfo, + const Message_ProgressRange& theProgress); + +protected: + + //! Return TRUE if face mesh should be skipped (e.g. because it is invalid or empty). + Standard_EXPORT virtual Standard_Boolean toSkipFaceMesh (const RWMesh_FaceIterator& theFaceIter); + + //! Collect face triangulation info. + //! @param theFace [in] face to process + //! @param theNbNodes [in] [out] overall number of triangulation nodes (should be appended) + //! @param theNbElems [in] [out] overall number of triangulation elements (should be appended) + //! @param theNbProgressSteps [in] [out] overall number of progress steps (should be appended) + //! @param theToCreateMatFile [in] [out] flag to create material file or not (should be appended) + Standard_EXPORT virtual void addFaceInfo (const RWMesh_FaceIterator& theFace, + Standard_Integer& theNbNodes, + Standard_Integer& theNbElems, + Standard_Real& theNbProgressSteps, + Standard_Boolean& theToCreateMatFile); + + //! Write the shape. + //! @param theWriter [in] OBJ writer context + //! @param theMatMgr [in] OBJ material map + //! @param thePSentry [in] progress sentry + //! @param theLabel [in] document label to process + //! @param theParentTrsf [in] parent node transformation + //! @param theParentStyle [in] parent node style + //! @param theName [in] node name + Standard_EXPORT virtual bool writeShape (RWObj_ObjWriterContext& theWriter, + RWObj_ObjMaterialMap& theMatMgr, + Message_LazyProgressScope& thePSentry, + const TDF_Label& theLabel, + const TopLoc_Location& theParentTrsf, + const XCAFPrs_Style& theParentStyle, + const TCollection_AsciiString& theName); + + //! Write face triangle vertex positions. + //! @param theWriter [in] OBJ writer context + //! @param thePSentry [in] progress sentry + //! @param theFace [in] current face + //! @return FALSE on writing file error + Standard_EXPORT virtual bool writePositions (RWObj_ObjWriterContext& theWriter, + Message_LazyProgressScope& thePSentry, + const RWMesh_FaceIterator& theFace); + + //! Write face triangle vertex normals. + //! @param theWriter [in] OBJ writer context + //! @param thePSentry [in] progress sentry + //! @param theFace [in] current face + //! @return FALSE on writing file error + Standard_EXPORT virtual bool writeNormals (RWObj_ObjWriterContext& theWriter, + Message_LazyProgressScope& thePSentry, + const RWMesh_FaceIterator& theFace); + + //! Write face triangle vertex texture coordinates. + //! @param theWriter [in] OBJ writer context + //! @param thePSentry [in] progress sentry + //! @param theFace [in] current face + //! @return FALSE on writing file error + Standard_EXPORT virtual bool writeTextCoords (RWObj_ObjWriterContext& theWriter, + Message_LazyProgressScope& thePSentry, + const RWMesh_FaceIterator& theFace); + + //! Write face triangles indices. + //! @param theWriter [in] OBJ writer context + //! @param thePSentry [in] progress sentry + //! @param theFace [in] current face + //! @return FALSE on writing file error + Standard_EXPORT virtual bool writeIndices (RWObj_ObjWriterContext& theWriter, + Message_LazyProgressScope& thePSentry, + const RWMesh_FaceIterator& theFace); + + +protected: + + TCollection_AsciiString myFile; //!< output OBJ file + RWMesh_CoordinateSystemConverter myCSTrsf; //!< transformation from OCCT to OBJ coordinate system + XCAFPrs_Style myDefaultStyle; //!< default material definition to be used for nodes with only color defined + +}; + +#endif // _RWObj_CafWriter_HeaderFiler diff --git a/src/RWObj/RWObj_ObjMaterialMap.cxx b/src/RWObj/RWObj_ObjMaterialMap.cxx new file mode 100644 index 0000000000..7007f2943b --- /dev/null +++ b/src/RWObj/RWObj_ObjMaterialMap.cxx @@ -0,0 +1,153 @@ +// Copyright (c) 2015-2021 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 + +#include +#include + +IMPLEMENT_STANDARD_RTTIEXT(RWObj_ObjMaterialMap, RWMesh_MaterialMap) + +// ================================================================ +// Function : RWObj_ObjMaterialMap +// Purpose : +// ================================================================ +RWObj_ObjMaterialMap::RWObj_ObjMaterialMap (const TCollection_AsciiString& theFile) +: RWMesh_MaterialMap (theFile), + myFile (NULL) +{ + // +} + +// ================================================================ +// Function : ~RWObj_ObjMaterialMap +// Purpose : +// ================================================================ +RWObj_ObjMaterialMap::~RWObj_ObjMaterialMap() +{ + if (myFile != NULL) + { + if (::fclose (myFile) != 0) + { + myIsFailed = true; + } + } + + if (myIsFailed) + { + Message::SendFail (TCollection_AsciiString ("File cannot be written\n") + myFileName); + } +} + +// ================================================================ +// Function : AddMaterial +// Purpose : +// ================================================================ +TCollection_AsciiString RWObj_ObjMaterialMap::AddMaterial (const XCAFPrs_Style& theStyle) +{ + if (myFile == NULL + && !myIsFailed) + { + myFile = OSD_OpenFile (myFileName.ToCString(), "wb"); + myIsFailed = myFile == NULL; + if (myFile != NULL) + { + Fprintf (myFile, "# Exported by Open CASCADE Technology [dev.opencascade.org]\n"); + } + } + if (myFile == NULL) + { + return TCollection_AsciiString(); + } + + return RWMesh_MaterialMap::AddMaterial (theStyle); +} + +// ================================================================ +// Function : DefineMaterial +// Purpose : +// ================================================================ +void RWObj_ObjMaterialMap::DefineMaterial (const XCAFPrs_Style& theStyle, + const TCollection_AsciiString& theKey, + const TCollection_AsciiString& theName) +{ + (void )theName; + Fprintf (myFile, "newmtl %s\n", theKey.ToCString()); + + bool hasMaterial = false; + const XCAFDoc_VisMaterialCommon aDefMat = !myDefaultStyle.Material().IsNull() + ? myDefaultStyle.Material()->ConvertToCommonMaterial() + : XCAFDoc_VisMaterialCommon(); + Quantity_Color anAmbQ (aDefMat.AmbientColor), aDiffQ (aDefMat.DiffuseColor), aSpecQ (aDefMat.SpecularColor); + Standard_ShortReal aTransp = 0.0f; + Standard_ShortReal aSpecular = aDefMat.Shininess * 1000.0f; + if (!theStyle.Material().IsNull() + && !theStyle.Material()->IsEmpty()) + { + hasMaterial = true; + const XCAFDoc_VisMaterialCommon aComMat = theStyle.Material()->ConvertToCommonMaterial(); + anAmbQ = aComMat.AmbientColor; + aDiffQ = aComMat.DiffuseColor; + aSpecQ = aComMat.SpecularColor; + aTransp = aComMat.Transparency; + aSpecular = aComMat.Shininess * 1000.0f; + } + if (theStyle.IsSetColorSurf()) + { + hasMaterial = true; + aDiffQ = theStyle.GetColorSurf(); + anAmbQ = Quantity_Color ((Graphic3d_Vec3 )theStyle.GetColorSurf() * 0.25f); + if (theStyle.GetColorSurfRGBA().Alpha() < 1.0f) + { + aTransp = 1.0f - theStyle.GetColorSurfRGBA().Alpha(); + } + } + + if (hasMaterial) + { + Graphic3d_Vec3d anAmb, aDiff, aSpec; + anAmbQ.Values (anAmb.r(), anAmb.g(), anAmb.b(), Quantity_TOC_sRGB); + aDiffQ.Values (aDiff.r(), aDiff.g(), aDiff.b(), Quantity_TOC_sRGB); + aSpecQ.Values (aSpec.r(), aSpec.g(), aSpec.b(), Quantity_TOC_sRGB); + + Fprintf (myFile, "Ka %f %f %f\n", anAmb.r(), anAmb.g(), anAmb.b()); + Fprintf (myFile, "Kd %f %f %f\n", aDiff.r(), aDiff.g(), aDiff.b()); + Fprintf (myFile, "Ks %f %f %f\n", aSpec.r(), aSpec.g(), aSpec.b()); + Fprintf (myFile, "Ns %f\n", aSpecular); + if (aTransp >= 0.0001f) + { + Fprintf (myFile, "Tr %f\n", aTransp); + } + } + + if (const Handle(Image_Texture)& aBaseTexture = theStyle.BaseColorTexture()) + { + TCollection_AsciiString aTexture; + if (!myImageMap.Find (aBaseTexture, aTexture) + && !myImageFailMap.Contains (aBaseTexture)) + { + if (CopyTexture (aTexture, aBaseTexture, TCollection_AsciiString (myImageMap.Extent() + 1))) + { + myImageMap.Bind (aBaseTexture, aTexture); + } + else + { + myImageFailMap.Add (aBaseTexture); + } + } + if (!aTexture.IsEmpty()) + { + Fprintf (myFile, "map_Kd %s\n", aTexture.ToCString()); + } + } +} diff --git a/src/RWObj/RWObj_ObjMaterialMap.hxx b/src/RWObj/RWObj_ObjMaterialMap.hxx new file mode 100644 index 0000000000..fd9df2e218 --- /dev/null +++ b/src/RWObj/RWObj_ObjMaterialMap.hxx @@ -0,0 +1,46 @@ +// Copyright (c) 2015-2021 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_ObjMaterialMap_HeaderFiler +#define _RWObj_ObjMaterialMap_HeaderFiler + +#include + +//! Material MTL file writer for OBJ export. +class RWObj_ObjMaterialMap : public RWMesh_MaterialMap +{ + DEFINE_STANDARD_RTTIEXT(RWObj_ObjMaterialMap, RWMesh_MaterialMap) +public: + + //! Main constructor. + Standard_EXPORT RWObj_ObjMaterialMap (const TCollection_AsciiString& theFile); + + //! Destructor, will emit error message if file was not closed. + Standard_EXPORT virtual ~RWObj_ObjMaterialMap(); + + //! Add material + Standard_EXPORT virtual TCollection_AsciiString AddMaterial (const XCAFPrs_Style& theStyle) Standard_OVERRIDE; + + //! Virtual method actually defining the material (e.g. export to the file). + Standard_EXPORT virtual void DefineMaterial (const XCAFPrs_Style& theStyle, + const TCollection_AsciiString& theKey, + const TCollection_AsciiString& theName) Standard_OVERRIDE; + +private: + + FILE* myFile; + NCollection_DataMap myImageMap; + +}; + +#endif // _RWObj_ObjMaterialMap_HeaderFiler diff --git a/src/RWObj/RWObj_ObjWriterContext.cxx b/src/RWObj/RWObj_ObjWriterContext.cxx new file mode 100644 index 0000000000..e7ce1e6abf --- /dev/null +++ b/src/RWObj/RWObj_ObjWriterContext.cxx @@ -0,0 +1,305 @@ +// Copyright (c) 2015-2021 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 + +#include +#include +#include + +// ======================================================================= +// function : splitLines +// purpose : +// ======================================================================= +static void splitLines (const TCollection_AsciiString& theString, + NCollection_IndexedMap& theLines) +{ + if (theString.IsEmpty()) + { + return; + } + + Standard_Integer aLineFrom = 1; + for (Standard_Integer aCharIter = 1;; ++aCharIter) + { + const char aChar = theString.Value (aCharIter); + if (aChar != '\r' + && aChar != '\n' + && aCharIter != theString.Length()) + { + continue; + } + + if (aLineFrom != aCharIter) + { + TCollection_AsciiString aLine = theString.SubString (aLineFrom, aCharIter); + aLine.RightAdjust(); + theLines.Add (aLine); + } + + if (aCharIter == theString.Length()) + { + break; + } + else if (aChar == '\r' + && theString.Value (aCharIter + 1) == '\n') + { + // CRLF + ++aCharIter; + } + aLineFrom = aCharIter + 1; + } +} + +// ================================================================ +// Function : RWObj_ObjWriterContext +// Purpose : +// ================================================================ +RWObj_ObjWriterContext::RWObj_ObjWriterContext (const TCollection_AsciiString& theName) +: NbFaces (0), + myFile (OSD_OpenFile (theName.ToCString(), "wb")), + myName (theName), + myElemPosFirst (1, 1, 1, 1), + myElemNormFirst(1, 1, 1, 1), + myElemUVFirst (1, 1, 1, 1), + myHasNormals (false), + myHasTexCoords (false) +{ + if (myFile == NULL) + { + Message::SendFail (TCollection_AsciiString ("File cannot be created\n") + theName); + return; + } +} + +// ================================================================ +// Function : ~RWObj_ObjWriterContext +// Purpose : +// ================================================================ +RWObj_ObjWriterContext::~RWObj_ObjWriterContext() +{ + if (myFile != NULL) + { + ::fclose (myFile); + Message::SendFail (TCollection_AsciiString ("File cannot be written\n") + myName); + } +} + +// ================================================================ +// Function : Close +// Purpose : +// ================================================================ +bool RWObj_ObjWriterContext::Close() +{ + bool isOk = ::fclose (myFile) == 0; + myFile = NULL; + return isOk; +} + +// ================================================================ +// Function : WriteHeader +// Purpose : +// ================================================================ +bool RWObj_ObjWriterContext::WriteHeader (const Standard_Integer theNbNodes, + const Standard_Integer theNbElems, + const TCollection_AsciiString& theMatLib, + const TColStd_IndexedDataMapOfStringString& theFileInfo) +{ + bool isOk = ::Fprintf (myFile, "# Exported by Open CASCADE Technology [dev.opencascade.org]\n" + "# Vertices: %d\n" + "# Faces: %d\n", theNbNodes, theNbElems) != 0; + for (TColStd_IndexedDataMapOfStringString::Iterator aKeyValueIter (theFileInfo); aKeyValueIter.More(); aKeyValueIter.Next()) + { + NCollection_IndexedMap aKeyLines, aValLines; + splitLines (aKeyValueIter.Key(), aKeyLines); + splitLines (aKeyValueIter.Value(), aValLines); + for (Standard_Integer aLineIter = 1; aLineIter <= aKeyLines.Extent(); ++aLineIter) + { + const TCollection_AsciiString& aLine = aKeyLines.FindKey (aLineIter); + isOk = isOk + && ::Fprintf (myFile, + aLineIter > 1 ? "\n# %s" : "# %s", + aLine.ToCString()) != 0; + } + isOk = isOk + && ::Fprintf (myFile, !aKeyLines.IsEmpty() ? ":" : "# ") != 0; + for (Standard_Integer aLineIter = 1; aLineIter <= aValLines.Extent(); ++aLineIter) + { + const TCollection_AsciiString& aLine = aValLines.FindKey (aLineIter); + isOk = isOk + && ::Fprintf (myFile, + aLineIter > 1 ? "\n# %s" : " %s", + aLine.ToCString()) != 0; + } + isOk = isOk + && ::Fprintf (myFile, "\n") != 0; + } + + if (!theMatLib.IsEmpty()) + { + isOk = isOk + && ::Fprintf (myFile, "mtllib %s\n", theMatLib.ToCString()) != 0; + } + return isOk; +} + +// ================================================================ +// Function : WriteActiveMaterial +// Purpose : +// ================================================================ +bool RWObj_ObjWriterContext::WriteActiveMaterial (const TCollection_AsciiString& theMaterial) +{ + myActiveMaterial = theMaterial; + return !theMaterial.IsEmpty() + ? Fprintf (myFile, "usemtl %s\n", theMaterial.ToCString()) != 0 + : Fprintf (myFile, "usemtl\n") != 0; +} + +// ================================================================ +// Function : WriteTriangle +// Purpose : +// ================================================================ +bool RWObj_ObjWriterContext::WriteTriangle (const Graphic3d_Vec3i& theTri) +{ + const Graphic3d_Vec3i aTriPos = theTri + myElemPosFirst.xyz(); + if (myHasNormals) + { + const Graphic3d_Vec3i aTriNorm = theTri + myElemNormFirst.xyz(); + if (myHasTexCoords) + { + const Graphic3d_Vec3i aTriUv = theTri + myElemUVFirst.xyz(); + return Fprintf (myFile, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", + aTriPos[0], aTriUv[0], aTriNorm[0], + aTriPos[1], aTriUv[1], aTriNorm[1], + aTriPos[2], aTriUv[2], aTriNorm[2]) != 0; + } + else + { + return Fprintf (myFile, "f %d//%d %d//%d %d//%d\n", + aTriPos[0], aTriNorm[0], + aTriPos[1], aTriNorm[1], + aTriPos[2], aTriNorm[2]) != 0; + } + } + if (myHasTexCoords) + { + const Graphic3d_Vec3i aTriUv = theTri + myElemUVFirst.xyz(); + return Fprintf (myFile, "f %d/%d %d/%d %d/%d\n", + aTriPos[0], aTriUv[0], + aTriPos[1], aTriUv[1], + aTriPos[2], aTriUv[2]) != 0; + } + else + { + return Fprintf (myFile, "f %d %d %d\n", aTriPos[0], aTriPos[1], aTriPos[2]) != 0; + } +} + +// ================================================================ +// Function : WriteQuad +// Purpose : +// ================================================================ +bool RWObj_ObjWriterContext::WriteQuad (const Graphic3d_Vec4i& theQuad) +{ + const Graphic3d_Vec4i aQPos = theQuad + myElemPosFirst; + if (myHasNormals) + { + const Graphic3d_Vec4i aQNorm = theQuad + myElemNormFirst; + if (myHasTexCoords) + { + const Graphic3d_Vec4i aQTex = theQuad + myElemUVFirst; + return Fprintf (myFile, "f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d\n", + aQPos[0], aQTex[0], aQNorm[0], + aQPos[1], aQTex[1], aQNorm[1], + aQPos[2], aQTex[2], aQNorm[2], + aQPos[3], aQTex[3], aQNorm[3]) != 0; + } + else + { + return Fprintf (myFile, "f %d//%d %d//%d %d//%d %d//%d\n", + aQPos[0], aQNorm[0], + aQPos[1], aQNorm[1], + aQPos[2], aQNorm[2], + aQPos[3], aQNorm[3]) != 0; + } + } + if (myHasTexCoords) + { + const Graphic3d_Vec4i aQTex = theQuad + myElemUVFirst; + return Fprintf (myFile, "f %d/%d %d/%d %d/%d %d/%d\n", + aQPos[0], aQTex[0], + aQPos[1], aQTex[1], + aQPos[2], aQTex[2], + aQPos[3], aQTex[3]) != 0; + } + else + { + return Fprintf (myFile, "f %d %d %d %d\n", aQPos[0], aQPos[1], aQPos[2], aQPos[3]) != 0; + } +} + +// ================================================================ +// Function : WriteVertex +// Purpose : +// ================================================================ +bool RWObj_ObjWriterContext::WriteVertex (const Graphic3d_Vec3& theValue) +{ + return Fprintf (myFile, "v %f %f %f\n", theValue.x(), theValue.y(), theValue.z()) != 0; +} + +// ================================================================ +// Function : WriteNormal +// Purpose : +// ================================================================ +bool RWObj_ObjWriterContext::WriteNormal (const Graphic3d_Vec3& theValue) +{ + return Fprintf (myFile, "vn %f %f %f\n", theValue.x(), theValue.y(), theValue.z()) != 0; +} + +// ================================================================ +// Function : WriteTexCoord +// Purpose : +// ================================================================ +bool RWObj_ObjWriterContext::WriteTexCoord (const Graphic3d_Vec2& theValue) +{ + return Fprintf (myFile, "vt %f %f\n", theValue.x(), theValue.y()) != 0; +} + +// ================================================================ +// Function : WriteGroup +// Purpose : +// ================================================================ +bool RWObj_ObjWriterContext::WriteGroup (const TCollection_AsciiString& theValue) +{ + return !theValue.IsEmpty() + ? Fprintf (myFile, "g %s\n", theValue.ToCString()) != 0 + : Fprintf (myFile, "g\n") != 0; +} + +// ================================================================ +// Function : FlushFace +// Purpose : +// ================================================================ +void RWObj_ObjWriterContext::FlushFace (Standard_Integer theNbNodes) +{ + Graphic3d_Vec4i aShift (theNbNodes, theNbNodes, theNbNodes, theNbNodes); + myElemPosFirst += aShift; + if (myHasNormals) + { + myElemNormFirst += aShift; + } + if (myHasTexCoords) + { + myElemUVFirst += aShift; + } +} diff --git a/src/RWObj/RWObj_ObjWriterContext.hxx b/src/RWObj/RWObj_ObjWriterContext.hxx new file mode 100644 index 0000000000..d5bff55ae4 --- /dev/null +++ b/src/RWObj/RWObj_ObjWriterContext.hxx @@ -0,0 +1,100 @@ +// Copyright (c) 2015-2021 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_ObjWriterContext_HeaderFiler +#define _RWObj_ObjWriterContext_HeaderFiler + +#include +#include +#include + +//! Auxiliary low-level tool writing OBJ file. +class RWObj_ObjWriterContext +{ +public: + + //! Main constructor. + Standard_EXPORT RWObj_ObjWriterContext (const TCollection_AsciiString& theName); + + //! Destructor, will emit error message if file was not closed. + Standard_EXPORT ~RWObj_ObjWriterContext(); + + //! Return true if file has been opened. + bool IsOpened() const { return myFile != NULL; } + + //! Correctly close the file. + Standard_EXPORT bool Close(); + + //! Return true if normals are defined. + bool HasNormals() const { return myHasNormals; } + + //! Set if normals are defined. + void SetNormals (const bool theHasNormals) { myHasNormals = theHasNormals; } + + //! Return true if normals are defined. + bool HasTexCoords() const { return myHasTexCoords; } + + //! Set if normals are defined. + void SetTexCoords (const bool theHasTexCoords) { myHasTexCoords = theHasTexCoords; } + + //! Write the header. + Standard_EXPORT bool WriteHeader (const Standard_Integer theNbNodes, + const Standard_Integer theNbElems, + const TCollection_AsciiString& theMatLib, + const TColStd_IndexedDataMapOfStringString& theFileInfo); + + //! Return active material or empty string if not set. + const TCollection_AsciiString& ActiveMaterial() const { return myActiveMaterial; } + + //! Set active material. + Standard_EXPORT bool WriteActiveMaterial (const TCollection_AsciiString& theMaterial); + + //! Writing a triangle + Standard_EXPORT bool WriteTriangle (const Graphic3d_Vec3i& theTri); + + //! Writing a quad + Standard_EXPORT bool WriteQuad (const Graphic3d_Vec4i& theQuad); + + //! Writing a vector + Standard_EXPORT bool WriteVertex (const Graphic3d_Vec3& theValue); + + //! Writing a vector + Standard_EXPORT bool WriteNormal (const Graphic3d_Vec3& theValue); + + //! Writing a vector + Standard_EXPORT bool WriteTexCoord (const Graphic3d_Vec2& theValue); + + //! Writing a group name + Standard_EXPORT bool WriteGroup (const TCollection_AsciiString& theValue); + + //! Increment indices shift. + Standard_EXPORT void FlushFace (Standard_Integer theNbNodes); + +public: + + Standard_Integer NbFaces; + +private: + + FILE* myFile; + TCollection_AsciiString myName; + TCollection_AsciiString myActiveMaterial; + Graphic3d_Vec4i myElemPosFirst; + Graphic3d_Vec4i myElemNormFirst; + Graphic3d_Vec4i myElemUVFirst; + bool myHasNormals; + bool myHasTexCoords; + +}; + +#endif // _RWObj_ObjWriterContext_HeaderFiler diff --git a/src/XCAFPrs/XCAFPrs_Style.hxx b/src/XCAFPrs/XCAFPrs_Style.hxx index cef8024b70..22c9664548 100644 --- a/src/XCAFPrs/XCAFPrs_Style.hxx +++ b/src/XCAFPrs/XCAFPrs_Style.hxx @@ -83,6 +83,27 @@ public: //! Manage visibility. Standard_Boolean IsVisible() const { return myIsVisible; } + //! Return base color texture. + const Handle(Image_Texture)& BaseColorTexture() const + { + static const Handle(Image_Texture) THE_NULL_TEXTURE; + if (myMaterial.IsNull()) + { + return THE_NULL_TEXTURE; + } + else if (myMaterial->HasPbrMaterial() + && !myMaterial->PbrMaterial().BaseColorTexture.IsNull()) + { + return myMaterial->PbrMaterial().BaseColorTexture; + } + else if (myMaterial->HasCommonMaterial() + && !myMaterial->CommonMaterial().DiffuseTexture.IsNull()) + { + return myMaterial->CommonMaterial().DiffuseTexture; + } + return THE_NULL_TEXTURE; + } + //! Returns True if styles are the same //! Methods for using Style as key in maps Standard_Boolean IsEqual (const XCAFPrs_Style& theOther) const diff --git a/src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx b/src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx index 9a33cb7b8b..4569f8785f 100644 --- a/src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx +++ b/src/XSDRAWSTLVRML/XSDRAWSTLVRML.cxx @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -669,6 +670,117 @@ static Standard_Integer ReadObj (Draw_Interpretor& theDI, return 0; } +//============================================================================= +//function : WriteObj +//purpose : Writes OBJ file +//============================================================================= +static Standard_Integer WriteObj (Draw_Interpretor& theDI, + Standard_Integer theNbArgs, + const char** theArgVec) +{ + TCollection_AsciiString anObjFilePath; + Handle(TDocStd_Document) aDoc; + Handle(TDocStd_Application) anApp = DDocStd::GetApplication(); + TColStd_IndexedDataMapOfStringString aFileInfo; + Standard_Real aFileUnitFactor = -1.0; + RWMesh_CoordinateSystem aSystemCoordSys = RWMesh_CoordinateSystem_Zup, aFileCoordSys = RWMesh_CoordinateSystem_Yup; + 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) + { + Message::SendFail() << "Syntax error: wrong length unit '" << aUnitStr << "'"; + return 1; + } + } + else if (anArgIter + 1 < theNbArgs + && (anArgCase == "-filecoordinatesystem" + || anArgCase == "-filecoordsystem" + || anArgCase == "-filecoordsys")) + { + if (!parseCoordinateSystem (theArgVec[++anArgIter], aFileCoordSys)) + { + Message::SendFail() << "Syntax error: unknown coordinate system '" << theArgVec[anArgIter] << "'"; + return 1; + } + } + else if (anArgIter + 1 < theNbArgs + && (anArgCase == "-systemcoordinatesystem" + || anArgCase == "-systemcoordsystem" + || anArgCase == "-systemcoordsys" + || anArgCase == "-syscoordsys")) + { + if (!parseCoordinateSystem (theArgVec[++anArgIter], aSystemCoordSys)) + { + Message::SendFail() << "Syntax error: unknown coordinate system '" << theArgVec[anArgIter] << "'"; + return 1; + } + } + else if (anArgCase == "-comments" + && anArgIter + 1 < theNbArgs) + { + aFileInfo.Add ("Comments", theArgVec[++anArgIter]); + } + else if (anArgCase == "-author" + && anArgIter + 1 < theNbArgs) + { + aFileInfo.Add ("Author", theArgVec[++anArgIter]); + } + else if (aDoc.IsNull()) + { + Standard_CString aNameVar = theArgVec[anArgIter]; + DDocStd::GetDocument (aNameVar, aDoc, false); + if (aDoc.IsNull()) + { + TopoDS_Shape aShape = DBRep::Get (aNameVar); + if (aShape.IsNull()) + { + Message::SendFail() << "Syntax error: '" << aNameVar << "' is not a shape nor document"; + return 1; + } + + anApp->NewDocument (TCollection_ExtendedString ("BinXCAF"), aDoc); + Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool (aDoc->Main()); + aShapeTool->AddShape (aShape); + } + } + else if (anObjFilePath.IsEmpty()) + { + anObjFilePath = theArgVec[anArgIter]; + } + else + { + Message::SendFail() << "Syntax error at '" << theArgVec[anArgIter] << "'"; + return 1; + } + } + if (anObjFilePath.IsEmpty()) + { + Message::SendFail() << "Syntax error: wrong number of arguments"; + return 1; + } + + Handle(Draw_ProgressIndicator) aProgress = new Draw_ProgressIndicator (theDI, 1); + + const Standard_Real aSystemUnitFactor = UnitsMethods::GetCasCadeLengthUnit() * 0.001; + RWObj_CafWriter aWriter (anObjFilePath); + aWriter.ChangeCoordinateSystemConverter().SetInputLengthUnit (aSystemUnitFactor); + aWriter.ChangeCoordinateSystemConverter().SetInputCoordinateSystem (aSystemCoordSys); + aWriter.ChangeCoordinateSystemConverter().SetOutputLengthUnit (aFileUnitFactor); + aWriter.ChangeCoordinateSystemConverter().SetOutputCoordinateSystem (aFileCoordSys); + aWriter.Perform (aDoc, aFileInfo, aProgress->Start()); + return 0; +} + static Standard_Integer writevrml (Draw_Interpretor& di, Standard_Integer argc, const char** argv) { @@ -1782,18 +1894,20 @@ void XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands) "\n\t\t: (false by default)" "\n\t\t: -keepLate data is loaded into itself with preservation of information" "\n\t\t: about deferred storage to load/unload this data later.", - "\n\t\t: -toPrintDebugInfo print additional debug inforamtion during data reading" + "\n\t\t: -toPrintDebugInfo print additional debug information during data reading" __FILE__, ReadGltf, g); theCommands.Add ("readgltf", "readgltf shape file" "\n\t\t: Same as ReadGltf but reads glTF file into a shape instead of a document.", __FILE__, ReadGltf, g); theCommands.Add ("WriteGltf", - "WriteGltf Doc file [-trsfFormat {compact|TRS|mat4}=compact] [-comments Text] [-author Name] [-forceUVExport] [-texturesSeparate]" - "\n\t\t: Write XDE document into glTF file." - "\n\t\t: -trsfFormat preferred transformation format" - "\n\t\t: -forceUVExport always export UV coordinates" - "\n\t\t: -texturesSeparate write textures to separate files", + "WriteGltf Doc file [-trsfFormat {compact|TRS|mat4}=compact]" + "\n\t\t: [-comments Text] [-author Name]" + "\n\t\t: [-forceUVExport] [-texturesSeparate]" + "\n\t\t: Write XDE document into glTF file." + "\n\t\t: -trsfFormat preferred transformation format" + "\n\t\t: -forceUVExport always export UV coordinates" + "\n\t\t: -texturesSeparate write textures to separate files", __FILE__, WriteGltf, g); theCommands.Add ("writegltf", "writegltf shape file", @@ -1826,6 +1940,18 @@ void XSDRAWSTLVRML::InitCommands (Draw_Interpretor& theCommands) "\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 ("WriteObj", + "WriteObj Doc file [-fileCoordSys {Zup|Yup}] [-fileUnit Unit]" + "\n\t\t: [-systemCoordSys {Zup|Yup}]" + "\n\t\t: [-comments Text] [-author Name]" + "\n\t\t: Write XDE document into OBJ file." + "\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: -systemCoordSys system coordinate system; Zup when not specified.", + __FILE__, WriteObj, g); + theCommands.Add ("writeobj", + "writeobj shape file", + __FILE__, WriteObj, 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 ); diff --git a/tests/de_mesh/grids.list b/tests/de_mesh/grids.list index 55f4acaec0..803b947e11 100644 --- a/tests/de_mesh/grids.list +++ b/tests/de_mesh/grids.list @@ -1,6 +1,7 @@ 001 stl_read 002 shape_write_stl 003 gltf_read -004 obj_read -005 gltf_write -006 gltf_lateload +004 gltf_write +005 gltf_lateload +006 obj_read +007 obj_write diff --git a/tests/de_mesh/obj_write/as1 b/tests/de_mesh/obj_write/as1 new file mode 100644 index 0000000000..4490648010 --- /dev/null +++ b/tests/de_mesh/obj_write/as1 @@ -0,0 +1,30 @@ +puts "========" +puts "0029303: Data Exchange - add RWObj_CafWriter tool for wavefront OBJ file" +puts "Write as1 STEP model into OBJ file" +puts "========" + +pload XDE OCAF MODELING VISUALIZATION +Close D -silent +Close D1 -silent +ReadStep D1 [locate_data_file as1-oc-214-mat.stp] +XGetOneShape ss D1 +incmesh ss 1.0 + +set aTmpObjBase "${imagedir}/${casename}_tmp" +set aTmpObj "${aTmpObjBase}.obj" +lappend occ_tmp_files $aTmpObj +lappend occ_tmp_files "${aTmpObjBase}.mtl" +lappend occ_tmp_files "${aTmpObjBase}_textures" + +WriteObj D1 "$aTmpObj" + +ReadObj D "$aTmpObj" +XGetOneShape s D +checknbshapes s -face 18 -compound 2 + +vclear +vinit View1 +XDisplay -dispMode 1 D +vaxo +vfit +vdump ${imagedir}/${casename}.png diff --git a/tests/de_mesh/obj_write/ball b/tests/de_mesh/obj_write/ball new file mode 100644 index 0000000000..81d586a202 --- /dev/null +++ b/tests/de_mesh/obj_write/ball @@ -0,0 +1,28 @@ +puts "========" +puts "0029303: Data Exchange - add RWObj_CafWriter tool for wavefront OBJ file" +puts "Write B-Rep model into OBJ file" +puts "========" + +pload XDE OCAF MODELING VISUALIZATION +Close D -silent + +restore [locate_data_file Ball.brep] b +incmesh b 0.1 + +set aTmpObjBase "${imagedir}/${casename}_tmp" +set aTmpObj "${aTmpObjBase}.obj" +lappend occ_tmp_files $aTmpObj +lappend occ_tmp_files "${aTmpObjBase}.mtl" + +writeobj b "$aTmpObj" + +ReadObj D "$aTmpObj" +XGetOneShape s D +checknbshapes s -face 2 -compound 2 + +vclear +vinit View1 +XDisplay -dispMode 1 D +vaxo +vfit +vdump ${imagedir}/${casename}.png diff --git a/tests/de_mesh/obj_write/lantern b/tests/de_mesh/obj_write/lantern new file mode 100644 index 0000000000..d8b737cadb --- /dev/null +++ b/tests/de_mesh/obj_write/lantern @@ -0,0 +1,29 @@ +puts "========" +puts "0029303: Data Exchange - add RWObj_CafWriter tool for wavefront OBJ file" +puts "Write textured lantern glTF model into OBJ file" +puts "========" + +pload XDE OCAF MODELING VISUALIZATION +Close D -silent +Close D1 -silent +ReadGltf D1 [locate_data_file bug30691_Lantern.glb] + +set aTmpObjBase "${imagedir}/${casename}_tmp" +set aTmpObj "${aTmpObjBase}.obj" +lappend occ_tmp_files $aTmpObj +lappend occ_tmp_files "${aTmpObjBase}.mtl" +lappend occ_tmp_files "${aTmpObjBase}_textures" + +WriteObj D1 "$aTmpObj" + +ReadObj D "$aTmpObj" +XGetOneShape s D +checknbshapes s -face 3 -compound 1 +checktrinfo s -tri 5394 -nod 4145 + +vclear +vinit View1 +XDisplay -dispMode 1 D +vaxo +vfit +vdump ${imagedir}/${casename}.png diff --git a/tests/de_mesh/obj_write/mustang b/tests/de_mesh/obj_write/mustang new file mode 100644 index 0000000000..e4c494056f --- /dev/null +++ b/tests/de_mesh/obj_write/mustang @@ -0,0 +1,29 @@ +puts "========" +puts "0029303: Data Exchange - add RWObj_CafWriter tool for wavefront OBJ file" +puts "Write textured plane OBJ model into OBJ file" +puts "========" + +pload XDE OCAF MODELING VISUALIZATION +Close D -silent +Close D1 -silent +ReadObj D1 [locate_data_file "P-51 Mustang.obj"] + +set aTmpObjBase "${imagedir}/${casename}_tmp" +set aTmpObj "${aTmpObjBase}.obj" +lappend occ_tmp_files $aTmpObj +lappend occ_tmp_files "${aTmpObjBase}.mtl" +lappend occ_tmp_files "${aTmpObjBase}_textures" + +WriteObj D1 "$aTmpObj" + +ReadObj D "$aTmpObj" +XGetOneShape s D +checknbshapes s -face 14 -compound 1 +checktrinfo s -tri 4309 -nod 4727 + +vclear +vinit View1 +XDisplay -dispMode 1 D +vaxo +vfit +vdump ${imagedir}/${casename}.png diff --git a/tests/de_mesh/obj_write/ship_boat b/tests/de_mesh/obj_write/ship_boat new file mode 100644 index 0000000000..efce97c5da --- /dev/null +++ b/tests/de_mesh/obj_write/ship_boat @@ -0,0 +1,29 @@ +puts "========" +puts "0029303: Data Exchange - add RWObj_CafWriter tool for wavefront OBJ file" +puts "Write textured boat OBJ model into OBJ file" +puts "========" + +pload XDE OCAF MODELING VISUALIZATION +Close D -silent +Close D1 -silent +ReadObj D1 [locate_data_file ship_boat.obj] + +set aTmpObjBase "${imagedir}/${casename}_tmp" +set aTmpObj "${aTmpObjBase}.obj" +lappend occ_tmp_files $aTmpObj +lappend occ_tmp_files "${aTmpObjBase}.mtl" +lappend occ_tmp_files "${aTmpObjBase}_textures" + +WriteObj D1 "$aTmpObj" + +ReadObj D "$aTmpObj" +XGetOneShape s D +checknbshapes s -face 158 -compound 2 +checktrinfo s -tri 27297 -nod 40496 + +vclear +vinit View1 +XDisplay -dispMode 1 D +vaxo +vfit +vdump ${imagedir}/${casename}.png diff --git a/tests/v3d/memory/bug26538 b/tests/v3d/memory/bug26538 index 93d5109e65..ea409df48f 100644 --- a/tests/v3d/memory/bug26538 +++ b/tests/v3d/memory/bug26538 @@ -7,19 +7,20 @@ pload MODELING VISUALIZATION box b1 1 1 1 box b2 1 1 1 +vclear vinit View1 -vdisplay b1 -vdisplay b2 +vdisplay b1 b2 vsetlocation b2 10 10 10 vfit set listmem {} - -set i_max 3 -for {set i 1} {${i} <= ${i_max}} {incr i} { - vfps 1000 +set aNbChecks 50 +for {set anIter 1} {$anIter <= $aNbChecks} {incr anIter} { + vfps 100 lappend listmem [meminfo h] - checktrend $listmem 0 1 "Memory leak detected" + #checktrend $listmem 0 1 "Memory leak detected" } +puts $listmem +checktrend $listmem 0 1 "Memory leak detected" vdump ${imagedir}/${casename}.png