diff --git a/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.cxx b/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.cxx index cee90ef709..4e7538ffd6 100644 --- a/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.cxx +++ b/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.cxx @@ -100,6 +100,8 @@ bool DEGLTF_ConfigurationNode::Load(const Handle(DE_ConfigurationContext)& theRe theResource->BooleanVal("read.print.debug.message", InternalParameters.ReadPrintDebugMessages, aScope); + InternalParameters.ReadApplyScale = + theResource->BooleanVal("read.apply.scale", InternalParameters.ReadApplyScale, aScope); InternalParameters.WriteComment = theResource->StringVal("write.comment", InternalParameters.WriteComment, aScope); @@ -260,6 +262,13 @@ TCollection_AsciiString DEGLTF_ConfigurationNode::Save() const aScope + "read.print.debug.message :\t " + InternalParameters.ReadPrintDebugMessages + "\n"; aResult += "!\n"; + aResult += "!\n"; + aResult += + "!Flag to apply non-uniform transformation directly to the triangulation (modify nodes)\n"; + aResult += "!Default value: 1(true). Available values: 0(false), 1(true)\n"; + aResult += aScope + "read.apply.scale :\t " + InternalParameters.ReadApplyScale + "\n"; + aResult += "!\n"; + aResult += "!\n"; aResult += "!Write parameters:\n"; aResult += "!\n"; diff --git a/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.hxx b/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.hxx index 72fafaeab2..5ad854f42a 100644 --- a/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.hxx +++ b/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.hxx @@ -101,6 +101,7 @@ public: bool ReadSkipLateDataLoading = false; //!< Flag to skip triangulation loading bool ReadKeepLateData = true;//!< Flag to keep information about deferred storage to load/unload triangulation later bool ReadPrintDebugMessages = false; //!< Flag to print additional debug information + bool ReadApplyScale = true; //!< Flag to apply non-uniform scale factor to the triangulations (modify nodes coordinates) // Writing TCollection_AsciiString WriteComment; //!< Export special comment TCollection_AsciiString WriteAuthor; //!< Author of exported file name diff --git a/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx b/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx index 5ca76abfd2..84c782b181 100644 --- a/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx +++ b/src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx @@ -41,6 +41,7 @@ static void SetReaderParameters(RWGltf_CafReader& theReade theReader.SetToSkipLateDataLoading(theNode->InternalParameters.ReadSkipLateDataLoading); theReader.SetToKeepLateData(theNode->InternalParameters.ReadKeepLateData); theReader.SetToPrintDebugMessages(theNode->InternalParameters.ReadPrintDebugMessages); + theReader.SetToApplyScale(theNode->InternalParameters.ReadApplyScale); } } // namespace diff --git a/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.cxx b/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.cxx index b2123be39d..b3b4cbe880 100644 --- a/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.cxx +++ b/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.cxx @@ -26,6 +26,11 @@ #include #include #include +#include +#include +#include +#include +#include IMPLEMENT_STANDARD_RTTIEXT(RWGltf_CafReader, RWMesh_CafReader) @@ -170,7 +175,8 @@ RWGltf_CafReader::RWGltf_CafReader() myIsDoublePrecision(false), myToSkipLateDataLoading(false), myToKeepLateData(true), - myToPrintDebugMessages(false) + myToPrintDebugMessages(false), + myToApplyScale(true) { myCoordSysConverter.SetInputLengthUnit(1.0); // glTF defines model in meters myCoordSysConverter.SetInputCoordinateSystem(RWMesh_CoordinateSystem_glTF); @@ -299,6 +305,9 @@ Standard_Boolean RWGltf_CafReader::performMesh(std::istream& th { aDoc.SetBinaryFormat(aBinBodyOffset, aBinBodyLen); } + myShapeScaleMap = new NCollection_DataMap(); + aDoc.SetToApplyScale(myToApplyScale); + aDoc.SetScaleMap(*myShapeScaleMap); #ifdef HAVE_RAPIDJSON rapidjson::ParseResult aRes; @@ -418,3 +427,263 @@ void RWGltf_CafReader::updateLateDataReader( } } } + +//================================================================================================= + +void RWGltf_CafReader::fillDocument() +{ + if (!myToFillDoc || myXdeDoc.IsNull() || myRootShapes.IsEmpty()) + { + return; + } + // set units + Standard_Real aLengthUnit = 1.; + if (!XCAFDoc_DocumentTool::GetLengthUnit(myXdeDoc, aLengthUnit)) + { + XCAFDoc_DocumentTool::SetLengthUnit(myXdeDoc, SystemLengthUnit()); + } + else if (aLengthUnit != SystemLengthUnit()) + { + Message::SendWarning("Warning: Length unit of document not equal to the system length unit"); + } + + const Standard_Boolean wasAutoNaming = XCAFDoc_ShapeTool::AutoNaming(); + XCAFDoc_ShapeTool::SetAutoNaming(Standard_False); + const TCollection_AsciiString aRootName; // = generateRootName (theFile); + CafDocumentTools aTools; + aTools.ShapeTool = XCAFDoc_DocumentTool::ShapeTool(myXdeDoc->Main()); + aTools.ColorTool = XCAFDoc_DocumentTool::ColorTool(myXdeDoc->Main()); + aTools.VisMaterialTool = XCAFDoc_DocumentTool::VisMaterialTool(myXdeDoc->Main()); + for (TopTools_SequenceOfShape::Iterator aRootIter(myRootShapes); aRootIter.More(); + aRootIter.Next()) + { + addShapeIntoDoc(aTools, aRootIter.Value(), TDF_Label(), aRootName); + } + XCAFDoc_DocumentTool::ShapeTool(myXdeDoc->Main())->UpdateAssemblies(); + XCAFDoc_ShapeTool::SetAutoNaming(wasAutoNaming); +} + +//================================================================================================= + +Standard_Boolean RWGltf_CafReader::addShapeIntoDoc(CafDocumentTools& theTools, + const TopoDS_Shape& theShape, + const TDF_Label& theLabel, + const TCollection_AsciiString& theParentName, + const Standard_Boolean theHasScale, + const gp_XYZ& theScale) +{ + if (theShape.IsNull() || myXdeDoc.IsNull()) + { + return Standard_False; + } + + const TopAbs_ShapeEnum aShapeType = theShape.ShapeType(); + TopoDS_Shape aShapeToAdd = theShape; + const TopoDS_Shape aShapeNoLoc = theShape.Located(TopLoc_Location()); + Standard_Boolean toMakeAssembly = Standard_False; + bool isShapeScaled = myShapeScaleMap->IsBound(theShape); + gp_XYZ aCurScale; + + if (theHasScale) + { + // update translation part + gp_Trsf aTrsf = theShape.Location().Transformation(); + gp_XYZ aTranslation = aTrsf.TranslationPart(); + aTranslation.SetX(aTranslation.X() * theScale.X()); + aTranslation.SetY(aTranslation.Y() * theScale.Y()); + aTranslation.SetZ(aTranslation.Z() * theScale.Z()); + aTrsf.SetTranslationPart(aTranslation); + aShapeToAdd.Location(TopLoc_Location(aTrsf)); + } + + if (isShapeScaled && aShapeType == TopAbs_FACE) + { + // Scale triangulation + aCurScale = myShapeScaleMap->Find(theShape); + TopLoc_Location aLoc; + TopoDS_Face aFace = TopoDS::Face(aShapeToAdd); + myShapeScaleMap->UnBind(theShape); + const Handle(Poly_Triangulation)& aPolyTri = BRep_Tool::Triangulation(aFace, aLoc); + if (!aPolyTri.IsNull()) + { + for (int aNodeIdx = 1; aNodeIdx <= aPolyTri->NbNodes(); ++aNodeIdx) + { + gp_Pnt aNode = aPolyTri->Node(aNodeIdx); + aNode.SetX(aNode.X() * aCurScale.X()); + aNode.SetY(aNode.Y() * aCurScale.Y()); + aNode.SetZ(aNode.Z() * aCurScale.Z()); + aPolyTri->SetNode(aNodeIdx, aNode); + } + } + } + + if (theShape.ShapeType() == TopAbs_COMPOUND) + { + RWMesh_NodeAttributes aSubFaceAttribs; + for (TopoDS_Iterator aSubShapeIter(theShape, Standard_True, Standard_False); + !toMakeAssembly && aSubShapeIter.More(); + aSubShapeIter.Next()) + { + if (aSubShapeIter.Value().ShapeType() != TopAbs_FACE) + { + toMakeAssembly = Standard_True; + break; + } + + const TopoDS_Face& aFace = TopoDS::Face(aSubShapeIter.Value()); + toMakeAssembly = + toMakeAssembly + || (myAttribMap.Find(aFace, aSubFaceAttribs) && !aSubFaceAttribs.Name.IsEmpty()); + } + + if (toMakeAssembly) + { + // create an empty Compound to add as assembly, so that we can add children one-by-one via + // AddComponent() + TopoDS_Compound aCompound; + BRep_Builder aBuilder; + aBuilder.MakeCompound(aCompound); + aCompound.Location(theShape.Location(), Standard_False); + aShapeToAdd = aCompound; + } + } + + TDF_Label aNewLabel, anOldLabel; + if (theLabel.IsNull()) + { + // add new shape + aNewLabel = theTools.ShapeTool->AddShape(aShapeToAdd, toMakeAssembly); + } + else if (theTools.ShapeTool->IsAssembly(theLabel)) + { + // add shape as component + if (theTools.ComponentMap.Find(aShapeNoLoc, anOldLabel)) + { + aNewLabel = theTools.ShapeTool->AddComponent(theLabel, anOldLabel, theShape.Location()); + } + else + { + aNewLabel = theTools.ShapeTool->AddComponent(theLabel, aShapeToAdd, toMakeAssembly); + + TDF_Label aRefLabel = aNewLabel; + theTools.ShapeTool->GetReferredShape(aNewLabel, aRefLabel); + if (!aRefLabel.IsNull()) + { + theTools.ComponentMap.Bind(aShapeNoLoc, aRefLabel); + } + } + } + else + { + // add shape as sub-shape + aNewLabel = theTools.ShapeTool->AddSubShape(theLabel, theShape); + if (!aNewLabel.IsNull()) + { + Handle(XCAFDoc_ShapeMapTool) aShapeMapTool = XCAFDoc_ShapeMapTool::Set(aNewLabel); + aShapeMapTool->SetShape(theShape); + } + } + if (aNewLabel.IsNull()) + { + return Standard_False; + } + + if (toMakeAssembly) + { + TDF_Label aRefLabel; + theTools.ShapeTool->GetReferredShape(aNewLabel, aRefLabel); + if (!aRefLabel.IsNull()) + { + theTools.OriginalShapeMap.Bind(theShape, aRefLabel); + } + } + + // if new label is a reference get referred shape + TDF_Label aNewRefLabel = aNewLabel; + theTools.ShapeTool->GetReferredShape(aNewLabel, aNewRefLabel); + + RWMesh_NodeAttributes aRefShapeAttribs; + myAttribMap.Find(aShapeNoLoc, aRefShapeAttribs); + + bool hasProductName = false; + if (aNewLabel != aNewRefLabel) + { + // put attributes to the Instance (overrides Product attributes) + RWMesh_NodeAttributes aShapeAttribs; + if (!theShape.Location().IsIdentity() && myAttribMap.Find(theShape, aShapeAttribs)) + { + if (!aShapeAttribs.Style.IsEqual(aRefShapeAttribs.Style)) + { + setShapeStyle(theTools, aNewLabel, aShapeAttribs.Style); + } + if (aShapeAttribs.NamedData != aRefShapeAttribs.NamedData) + { + setShapeNamedData(theTools, aNewLabel, aShapeAttribs.NamedData); + } + setShapeName(aNewLabel, aShapeType, aShapeAttribs.Name, theLabel, theParentName); + if (aRefShapeAttribs.Name.IsEmpty() && !aShapeAttribs.Name.IsEmpty()) + { + // it is not nice having unnamed Product, so copy name from first Instance (probably the + // only one) + hasProductName = true; + setShapeName(aNewRefLabel, aShapeType, aShapeAttribs.Name, theLabel, theParentName); + } + else if (aShapeAttribs.Name.IsEmpty() && !aRefShapeAttribs.Name.IsEmpty()) + { + // copy name from Product + setShapeName(aNewLabel, aShapeType, aRefShapeAttribs.Name, theLabel, theParentName); + } + } + else + { + // copy name from Product + setShapeName(aNewLabel, aShapeType, aRefShapeAttribs.Name, theLabel, theParentName); + } + } + + if (!anOldLabel.IsNull()) + { + // already defined in the document + return Standard_True; + } + + // put attributes to the Product (shared across Instances) + if (!hasProductName) + { + setShapeName(aNewRefLabel, aShapeType, aRefShapeAttribs.Name, theLabel, theParentName); + } + setShapeStyle(theTools, aNewRefLabel, aRefShapeAttribs.Style); + setShapeNamedData(theTools, aNewRefLabel, aRefShapeAttribs.NamedData); + + if (theTools.ShapeTool->IsAssembly(aNewRefLabel)) + { + bool aHasScale = theHasScale | isShapeScaled; + gp_XYZ aScale = theScale; + if (isShapeScaled) + { + aScale.SetX(aCurScale.X() * theScale.X()); + aScale.SetY(aCurScale.Y() * theScale.Y()); + aScale.SetZ(aCurScale.Z() * theScale.Z()); + } + // store sub-shapes (iterator is set to not inherit Location of parent object) + TCollection_AsciiString aDummyName; + for (TopoDS_Iterator aSubShapeIter(theShape, Standard_True, Standard_False); + aSubShapeIter.More(); + aSubShapeIter.Next()) + { + addShapeIntoDoc(theTools, aSubShapeIter.Value(), aNewRefLabel, aDummyName, aHasScale, aScale); + } + } + else + { + // store a plain list of sub-shapes in case if they have custom attributes (usually per-face + // color) + for (TopoDS_Iterator aSubShapeIter(theShape, Standard_True, Standard_False); + aSubShapeIter.More(); + aSubShapeIter.Next()) + { + addSubShapeIntoDoc(theTools, aSubShapeIter.Value(), aNewRefLabel); + } + } + return Standard_True; +} diff --git a/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.hxx b/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.hxx index abd5153ff1..0d22191fc3 100644 --- a/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.hxx +++ b/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.hxx @@ -45,6 +45,10 @@ public: //! main (default) scene will be loaded. bool ToLoadAllScenes() const { return myToLoadAllScenes; } + //! Return TRUE if non-uniform scaling should be applied directly to the triangulation. + //! FALSE if the average scale should be applied to the transformation matrix. + bool ToApplyScale() const { return myToApplyScale; } + //! Set flag to flag to load all scenes in the document, FALSE by default which means only main //! (default) scene will be loaded. void SetLoadAllScenes(bool theToLoadAll) { myToLoadAllScenes = theToLoadAll; } @@ -67,6 +71,10 @@ public: //! Sets flag to skip data loading. void SetToSkipLateDataLoading(bool theToSkip) { myToSkipLateDataLoading = theToSkip; } + //! Set flag to apply non-uniform scaling directly to the triangulation (modify nodes). + //! TRUE by default. In case of FALSE the average scale is applied to the transformation matrix. + void SetToApplyScale(bool theToApplyScale) { myToApplyScale = theToApplyScale; } + //! Returns TRUE if data should be loaded into itself without its transferring to new structure. //! It allows to keep information about deferred storage to load/unload this data later. //! TRUE by default. @@ -92,6 +100,17 @@ protected: const Standard_Boolean theToProbe) Standard_OVERRIDE; + //! Fill document with new root shapes. + Standard_EXPORT virtual void fillDocument() Standard_OVERRIDE; + + //! Append new shape into the document (recursively). + Standard_EXPORT Standard_Boolean addShapeIntoDoc(CafDocumentTools& theTools, + const TopoDS_Shape& theShape, + const TDF_Label& theLabel, + const TCollection_AsciiString& theParentName, + const Standard_Boolean theHasScale = false, + const gp_XYZ& theScale = gp_XYZ(0., 0., 0.)); + //! Create primitive array reader context. //! Can be overridden by sub-class to read triangulation into application-specific data structures //! instead of Poly_Triangulation. Default implementation creates RWGltf_TriangulationReader. @@ -123,6 +142,9 @@ protected: Standard_Boolean myToKeepLateData; //!< flag to keep information about deferred storage to load/unload triangulation later // clang-format on Standard_Boolean myToPrintDebugMessages; //!< flag to print additional debug information + Standard_Boolean myToApplyScale; //!< flag to apply non-uniform scaling + NCollection_DataMap* + myShapeScaleMap; //!< map of shapes with non-uniform scalings }; #endif // _RWGltf_CafReader_HeaderFile diff --git a/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.cxx b/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.cxx index 989c418751..e517be7bf5 100644 --- a/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.cxx +++ b/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.cxx @@ -15,6 +15,7 @@ #include "RWGltf_GltfJsonParser.hxx" #include +#include #include #include #include @@ -504,8 +505,11 @@ bool RWGltf_GltfJsonParser::parseTransformationComponents( const RWGltf_JsonValue* theRotationVal, const RWGltf_JsonValue* theScaleVal, const RWGltf_JsonValue* theTranslationVal, - TopLoc_Location& theResult) const + TopLoc_Location& theResult, + bool& theHasScale, + gp_XYZ& theScale) const { + theHasScale = false; gp_Trsf aTrsf; if (theRotationVal != NULL) { @@ -584,13 +588,16 @@ bool RWGltf_GltfJsonParser::parseTransformationComponents( || Abs(aScaleVec.y() - aScaleVec.z()) > Precision::Confusion() || Abs(aScaleVec.x() - aScaleVec.z()) > Precision::Confusion()) { - Graphic3d_Mat4d aScaleMat; - aScaleMat.SetDiagonal(aScaleVec); - Graphic3d_Mat4d aMat4; aTrsf.GetMat4(aMat4); - aMat4 = aMat4 * aScaleMat; + if (!myToApplyScale) + { + Graphic3d_Mat4d aScaleMat; + aScaleMat.SetDiagonal(aScaleVec); + aMat4 = aMat4 * aScaleMat; + } + aTrsf = gp_Trsf(); aTrsf.SetValues(aMat4.GetValue(0, 0), aMat4.GetValue(0, 1), @@ -605,9 +612,18 @@ bool RWGltf_GltfJsonParser::parseTransformationComponents( aMat4.GetValue(2, 2), aMat4.GetValue(2, 3)); - Message::SendWarning(TCollection_AsciiString("glTF reader, scene node '") + theSceneNodeId - + "' defines unsupported scaling " + aScaleVec.x() + " " + aScaleVec.y() - + " " + aScaleVec.z()); + TCollection_AsciiString aWarnMessage = TCollection_AsciiString("glTF reader, scene node '") + + theSceneNodeId + "' defines unsupported scaling " + + aScaleVec.x() + " " + aScaleVec.y() + " " + + aScaleVec.z(); + if (myToApplyScale) + { + aWarnMessage += TCollection_AsciiString(". It was applied to the triangulation directly"); + theHasScale = true; + theScale = gp_XYZ(aScaleVec.x(), aScaleVec.y(), aScaleVec.z()); + } + + Message::SendWarning(aWarnMessage); } else if (Abs(aScaleVec.x() - 1.0) > Precision::Confusion()) { @@ -638,6 +654,7 @@ RWGltf_GltfJsonParser::RWGltf_GltfJsonParser(TopTools_SequenceOfShape& theRootSh : myRootShapes(&theRootShapes), myAttribMap(NULL), myExternalFiles(NULL), + myShapeScaleMap(NULL), myMetadata(NULL), myBinBodyOffset(0), myBinBodyLen(0), @@ -647,7 +664,8 @@ RWGltf_GltfJsonParser::RWGltf_GltfJsonParser(TopTools_SequenceOfShape& theRootSh myToLoadAllScenes(false), myUseMeshNameAsFallback(true), myToProbeHeader(false), - myToReadAssetExtras(true) + myToReadAssetExtras(true), + myToApplyScale(true) { myCSTrsf.SetInputLengthUnit(1.0); // meters myCSTrsf.SetInputCoordinateSystem(RWMesh_CoordinateSystem_glTF); @@ -1567,6 +1585,8 @@ bool RWGltf_GltfJsonParser::gltfParseSceneNode(TopoDS_Shape& th const RWGltf_JsonValue* anExtrasVal = findObjectMember(theSceneNode, "extras"); TopLoc_Location aNodeLoc; + bool aHasScale = false; + gp_XYZ aScale; const bool aHasTransformComponents = aTrsfRotVal != NULL || aTrsfScaleVal != NULL || aTrsfTransVal != NULL; const bool aHasTransformMatrix = aTrsfMatVal != NULL; @@ -1588,7 +1608,9 @@ bool RWGltf_GltfJsonParser::gltfParseSceneNode(TopoDS_Shape& th aTrsfRotVal, aTrsfScaleVal, aTrsfTransVal, - aNodeLoc)) + aNodeLoc, + aHasScale, + aScale)) { return false; } @@ -1605,7 +1627,7 @@ bool RWGltf_GltfJsonParser::gltfParseSceneNode(TopoDS_Shape& th if (aChildren != NULL && !gltfParseSceneNodes(aChildShapes, *aChildren, theProgress)) { theNodeShape = aNodeShape; - bindNodeShape(theNodeShape, aNodeLoc, theSceneNodeId, aName, anExtras); + bindNodeShape(theNodeShape, aNodeLoc, aHasScale, aScale, theSceneNodeId, aName, anExtras); return false; } for (TopTools_SequenceOfShape::Iterator aChildShapeIter(aChildShapes); aChildShapeIter.More(); @@ -1627,7 +1649,7 @@ bool RWGltf_GltfJsonParser::gltfParseSceneNode(TopoDS_Shape& th if (aMesh == NULL) { theNodeShape = aNodeShape; - bindNodeShape(theNodeShape, aNodeLoc, theSceneNodeId, aName, anExtras); + bindNodeShape(theNodeShape, aNodeLoc, aHasScale, aScale, theSceneNodeId, aName, anExtras); reportGltfError("Scene node '" + theSceneNodeId + "' refers to non-existing mesh."); return false; } @@ -1636,7 +1658,7 @@ bool RWGltf_GltfJsonParser::gltfParseSceneNode(TopoDS_Shape& th if (!gltfParseMesh(aMeshShape, getKeyString(*aMeshIter), *aMesh)) { theNodeShape = aNodeShape; - bindNodeShape(theNodeShape, aNodeLoc, theSceneNodeId, aName, anExtras); + bindNodeShape(theNodeShape, aNodeLoc, aHasScale, aScale, theSceneNodeId, aName, anExtras); return false; } if (!aMeshShape.IsNull()) @@ -1654,7 +1676,7 @@ bool RWGltf_GltfJsonParser::gltfParseSceneNode(TopoDS_Shape& th if (aMesh == NULL) { theNodeShape = aNodeShape; - bindNodeShape(theNodeShape, aNodeLoc, theSceneNodeId, aName, anExtras); + bindNodeShape(theNodeShape, aNodeLoc, aHasScale, aScale, theSceneNodeId, aName, anExtras); reportGltfError("Scene node '" + theSceneNodeId + "' refers to non-existing mesh."); return false; } @@ -1663,13 +1685,15 @@ bool RWGltf_GltfJsonParser::gltfParseSceneNode(TopoDS_Shape& th if (!gltfParseMesh(aMeshShape, getKeyString(*aMesh_2), *aMesh)) { theNodeShape = aNodeShape; - bindNodeShape(theNodeShape, aNodeLoc, theSceneNodeId, aName, anExtras); + bindNodeShape(theNodeShape, aNodeLoc, aHasScale, aScale, theSceneNodeId, aName, anExtras); return false; } if (!aMeshShape.IsNull()) { aBuilder.Add(aNodeShape, aMeshShape); ++aNbSubShapes; + if (aHasScale) + myShapeScaleMap->Bind(aMeshShape, aScale); } } @@ -1681,7 +1705,7 @@ bool RWGltf_GltfJsonParser::gltfParseSceneNode(TopoDS_Shape& th { theNodeShape = aNodeShape; } - bindNodeShape(theNodeShape, aNodeLoc, theSceneNodeId, aName, anExtras); + bindNodeShape(theNodeShape, aNodeLoc, aHasScale, aScale, theSceneNodeId, aName, anExtras); return true; } @@ -2337,10 +2361,11 @@ bool RWGltf_GltfJsonParser::gltfParseBuffer( } //================================================================================================= - void RWGltf_GltfJsonParser::bindNamedShape(TopoDS_Shape& theShape, ShapeMapGroup theGroup, const TopLoc_Location& theLoc, + const bool theHasScale, + const gp_XYZ& theScale, const TCollection_AsciiString& theId, const RWGltf_JsonValue* theUserName, const Handle(TDataStd_NamedData)& theExtras) @@ -2350,6 +2375,19 @@ void RWGltf_GltfJsonParser::bindNamedShape(TopoDS_Shape& the return; } + if (theHasScale && myShapeScaleMap->IsBound(theShape)) + { + // Check scaling values + gp_XYZ aScale = myShapeScaleMap->Find(theShape); + if ((aScale - theScale).Modulus() > Precision::Confusion()) + { + // Create a shape copy to avoid problems with different scaling + BRepBuilderAPI_Copy aCopy; + aCopy.Perform(theShape, Standard_True, Standard_True); + theShape = aCopy.Shape(); + } + } + TopoDS_Shape aShape = theShape; if (!theLoc.IsIdentity()) { @@ -2453,6 +2491,11 @@ void RWGltf_GltfJsonParser::bindNamedShape(TopoDS_Shape& the } myAttribMap->Bind(theShape, aShapeAttribs); } + + if (theHasScale) + { + myShapeScaleMap->Bind(theShape, theScale); + } myShapeMap[theGroup].Bind(theId, theShape); } diff --git a/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.hxx b/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.hxx index 7982d2e41d..d6f2ac039e 100644 --- a/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.hxx +++ b/src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.hxx @@ -81,6 +81,12 @@ public: //! Set map for storing node attributes. void SetAttributeMap(RWMesh_NodeAttributeMap& theAttribMap) { myAttribMap = &theAttribMap; } + //! Set map for storing non-uniform scalings. + void SetScaleMap(NCollection_DataMap& theScaleMap) + { + myShapeScaleMap = &theScaleMap; + } + //! Set list for storing external files. void SetExternalFiles(NCollection_IndexedMap& theExternalFiles) { @@ -120,6 +126,10 @@ public: //! Set flag to use Mesh name in case if Node name is empty, TRUE by default. void SetMeshNameAsFallback(bool theToFallback) { myUseMeshNameAsFallback = theToFallback; } + //! Set flag to apply non-uniform scaling directly to the triangulation (modify nodes). + //! TRUE by default. In case of FALSE the average scale is applied to the transformation matrix. + void SetToApplyScale(bool theToApplyScale) { myToApplyScale = theToApplyScale; } + //! Parse glTF document. Standard_EXPORT bool Parse(const Message_ProgressRange& theProgress); @@ -289,11 +299,20 @@ protected: //! Bind name attribute. void bindNodeShape(TopoDS_Shape& theShape, const TopLoc_Location& theLoc, + const bool theHasScale, + const gp_XYZ& theScale, const TCollection_AsciiString& theNodeId, const RWGltf_JsonValue* theUserName, const Handle(TDataStd_NamedData)& theExtras) { - bindNamedShape(theShape, ShapeMapGroup_Nodes, theLoc, theNodeId, theUserName, theExtras); + bindNamedShape(theShape, + ShapeMapGroup_Nodes, + theLoc, + theHasScale, + theScale, + theNodeId, + theUserName, + theExtras); } //! Bind name attribute. @@ -305,6 +324,8 @@ protected: bindNamedShape(theShape, ShapeMapGroup_Meshes, TopLoc_Location(), + false, + gp_XYZ(), theMeshId, theUserName, theExtras); @@ -326,6 +347,8 @@ protected: Standard_EXPORT void bindNamedShape(TopoDS_Shape& theShape, ShapeMapGroup theGroup, const TopLoc_Location& theLoc, + const bool theHasScale, + const gp_XYZ& theScale, const TCollection_AsciiString& theId, const RWGltf_JsonValue* theUserName, const Handle(TDataStd_NamedData)& theExtras); @@ -417,13 +440,18 @@ private: //! @param theTranslationVal Json value containing translation component of transformation. //! May be null in which case it is ignored. //! @param theResult TopLoc_Location object where result of parsing will be written. + //! @param theHasScale The flag indicates if scale component was found in the transformation. + //! @param theScale Found scale vector. Only valid if @p theHasScale is true. + //! Otherwise, it is undefined. //! @param If true - parsing was successful, transformation is written into @p theResult. //! If true - failed to parse, @p theResult is unchanged. bool parseTransformationComponents(const TCollection_AsciiString& theSceneNodeId, const RWGltf_JsonValue* theRotationVal, const RWGltf_JsonValue* theScaleVal, const RWGltf_JsonValue* theTranslationVal, - TopLoc_Location& theResult) const; + TopLoc_Location& theResult, + bool& theHasScale, + gp_XYZ& theScale) const; //! Fill lines and points data not deferred. //! @param theMeshData source glTF triangulation @@ -439,7 +467,9 @@ protected: TopTools_SequenceOfShape* myRootShapes; //!< sequence of result root shapes RWMesh_NodeAttributeMap* myAttribMap; //!< shape attributes NCollection_IndexedMap* - myExternalFiles; //!< list of external file references + myExternalFiles; //!< list of external file references + NCollection_DataMap* + myShapeScaleMap; //!< map of shapes with non-uniform scalings // clang-format off RWMesh_CoordinateSystemConverter myCSTrsf; //!< transformation from glTF to OCCT coordinate system // clang-format on @@ -470,6 +500,7 @@ protected: bool myUseMeshNameAsFallback; //!< flag to use Mesh name in case if Node name is empty, TRUE by default bool myToProbeHeader; //!< flag to probe header without full reading, FALSE by default bool myToReadAssetExtras; //!< flag to translate asset.extras into metadata, TRUE by default + bool myToApplyScale; //!< flag to apply non-uniform scaling // clang-format on #ifdef HAVE_RAPIDJSON diff --git a/src/Draw/TKXSDRAWGLTF/XSDRAWGLTF/XSDRAWGLTF.cxx b/src/Draw/TKXSDRAWGLTF/XSDRAWGLTF/XSDRAWGLTF.cxx index 991441d7ff..9a9e06a895 100644 --- a/src/Draw/TKXSDRAWGLTF/XSDRAWGLTF/XSDRAWGLTF.cxx +++ b/src/Draw/TKXSDRAWGLTF/XSDRAWGLTF/XSDRAWGLTF.cxx @@ -119,6 +119,7 @@ static Standard_Integer ReadGltf(Draw_Interpretor& theDI, Standard_Boolean toPrintDebugInfo = Standard_False; Standard_Boolean toLoadAllScenes = Standard_False; Standard_Boolean toPrintAssetInfo = Standard_False; + Standard_Boolean toApplyScale = Standard_True; Standard_Boolean isNoDoc = (TCollection_AsciiString(theArgVec[0]) == "readgltf"); for (Standard_Integer anArgIter = 1; anArgIter < theNbArgs; ++anArgIter) { @@ -167,6 +168,10 @@ static Standard_Integer ReadGltf(Draw_Interpretor& theDI, { toPrintAssetInfo = Draw::ParseOnOffIterator(theNbArgs, theArgVec, anArgIter); } + else if (anArgCase == "-applyscale") + { + toApplyScale = Draw::ParseOnOffIterator(theNbArgs, theArgVec, anArgIter); + } else if (aDestName.IsEmpty()) { aDestName = theArgVec[anArgIter]; @@ -229,6 +234,7 @@ static Standard_Integer ReadGltf(Draw_Interpretor& theDI, aReader.SetToKeepLateData(toKeepLateData); aReader.SetToPrintDebugMessages(toPrintDebugInfo); aReader.SetLoadAllScenes(toLoadAllScenes); + aReader.SetToApplyScale(toApplyScale); if (aDestName.IsEmpty()) { aReader.ProbeHeader(aFilePath); @@ -523,7 +529,10 @@ void XSDRAWGLTF::Factory(Draw_Interpretor& theDI) "\n\t\t: -allScenes load all scenes defined in the document instead of default one " "(false by default)" "\n\t\t: -toPrintDebugInfo print additional debug information during data reading" - "\n\t\t: -assetInfo print asset information", + "\n\t\t: -assetInfo print asset information" + "\n\t\t: -applyScale apply non-uniform scaling directly to the triangulation (modify " + "\n\t\t: nodes) (true by default). In case of false the average scale is " + "\n\t\t: applied to the transformation matrix.", __FILE__, ReadGltf, aGroup); diff --git a/tests/de_mesh/gltf_read/cartoning b/tests/de_mesh/gltf_read/cartoning new file mode 100644 index 0000000000..27b9641399 --- /dev/null +++ b/tests/de_mesh/gltf_read/cartoning @@ -0,0 +1,21 @@ +puts "========" +puts "OCP-1948: Implement non-uniform scaling in Gltf Import" +puts "========" +Close D -silent +ReadGltf D [locate_data_file bug_ocp1948_PSU_Cartoning_subunit__right-01.01.01.03-CART-03_green_bottom.glb] + +XGetOneShape s D +checknbshapes s -face 87 -compound 21 +checktrinfo s -tri 16473 -nod 15835 + +# check center of gravity +set REF_X 18300.5 +set REF_Y -9484 +set REF_Z 129.844 +set tol 1e-4 +set pos [vprops s] +if {([expr abs($REF_X - [lindex $pos 9])] > $tol) || + ([expr abs($REF_Y - [lindex $pos 12])] > $tol) || + ([expr abs($REF_Z - [lindex $pos 15])] > $tol)} { + puts "Error: wrong position of the imported model." + } diff --git a/tests/de_wrapper/configuration/A3 b/tests/de_wrapper/configuration/A3 index 52c224f82a..9401a71317 100644 --- a/tests/de_wrapper/configuration/A3 +++ b/tests/de_wrapper/configuration/A3 @@ -128,6 +128,7 @@ provider.GLTF.OCC.read.use.mesh.name.as.fallback : 1 provider.GLTF.OCC.read.skip.late.data.loading : 0 provider.GLTF.OCC.read.keep.late.data : 1 provider.GLTF.OCC.read.print.debug.message : 0 +provider.GLTF.OCC.read.apply.scale : 1 provider.GLTF.OCC.write.comment : provider.GLTF.OCC.write.author : provider.GLTF.OCC.write.trsf.format : 0