]> OCCT Git - occt.git/commitdiff
Data Exchange, Gltf Reader - Implement non-uniform scaling in Gltf Import #503
authorikochetkova <irina.kochetkova@opencascade.com>
Thu, 24 Apr 2025 13:20:36 +0000 (14:20 +0100)
committerGitHub <noreply@github.com>
Thu, 24 Apr 2025 13:20:36 +0000 (14:20 +0100)
Apply non-uniform scale factors directly to the triangulation during Gltf import.
Add flag for turning on/off (on by default) this behavior.
OCP-1948

src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.cxx
src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_ConfigurationNode.hxx
src/DataExchange/TKDEGLTF/DEGLTF/DEGLTF_Provider.cxx
src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.cxx
src/DataExchange/TKDEGLTF/RWGltf/RWGltf_CafReader.hxx
src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.cxx
src/DataExchange/TKDEGLTF/RWGltf/RWGltf_GltfJsonParser.hxx
src/Draw/TKXSDRAWGLTF/XSDRAWGLTF/XSDRAWGLTF.cxx
tests/de_mesh/gltf_read/cartoning [new file with mode: 0644]
tests/de_wrapper/configuration/A3

index cee90ef70962f9f7cb06be7e6bedafa396bfdb77..4e7538ffd6278648d28da7fb79389840d0148537 100644 (file)
@@ -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";
index 72fafaeab2de59aad193d184669684a7e3d3446c..5ad854f42ad20d713d648380dcaeade451e14fd4 100644 (file)
@@ -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
index 5ca76abfd28ae10e47e1045bac041e7eb1537b7f..84c782b181ad203ead81ed117e6cf75bf1d169c4 100644 (file)
@@ -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
 
index b2123be39db45a4eadddbf1678dd8dd0b65aaf51..b3b4cbe8809c82100bcef3c5c3c02675c765b12a 100644 (file)
 #include <OSD_FileSystem.hxx>
 #include <OSD_ThreadPool.hxx>
 #include <RWGltf_GltfLatePrimitiveArray.hxx>
+#include <TDocStd_Document.hxx>
+#include <TopoDS.hxx>
+#include <XCAFDoc_DocumentTool.hxx>
+#include <XCAFDoc_ShapeMapTool.hxx>
+#include <XCAFDoc_ShapeTool.hxx>
 
 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<TopoDS_Shape, gp_XYZ, TopTools_ShapeMapHasher>();
+  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;
+}
index abd5153ff17babc14245819bd18cda1d3ba24b1c..0d22191fc38533449cba8acdf624e810122a5a5d 100644 (file)
@@ -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<TopoDS_Shape, gp_XYZ, TopTools_ShapeMapHasher>*
+    myShapeScaleMap; //!< map of shapes with non-uniform scalings
 };
 
 #endif // _RWGltf_CafReader_HeaderFile
index 989c4187512080153b6c4aee13d1aad98cf9dbb1..e517be7bf546cbb726a2585183c8c4a37a559e45 100644 (file)
@@ -15,6 +15,7 @@
 #include "RWGltf_GltfJsonParser.hxx"
 
 #include <BRep_Builder.hxx>
+#include <BRepBuilderAPI_Copy.hxx>
 #include <FSD_Base64.hxx>
 #include <Message.hxx>
 #include <Message_Messenger.hxx>
@@ -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);
 }
 
index 7982d2e41d84cef5e3a77e424eb0dc42812a1257..d6f2ac039e54f76b416104ada1c6be3014460ec6 100644 (file)
@@ -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<TopoDS_Shape, gp_XYZ, TopTools_ShapeMapHasher>& theScaleMap)
+  {
+    myShapeScaleMap = &theScaleMap;
+  }
+
   //! Set list for storing external files.
   void SetExternalFiles(NCollection_IndexedMap<TCollection_AsciiString>& 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<TCollection_AsciiString>*
-    myExternalFiles;                                //!< list of external file references
+    myExternalFiles; //!< list of external file references
+  NCollection_DataMap<TopoDS_Shape, gp_XYZ, TopTools_ShapeMapHasher>*
+    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
index 991441d7fff45cfb680c6810333e0921be2eb82b..9a9e06a8952591010bca0b03cf2ca1390f59e512 100644 (file)
@@ -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 (file)
index 0000000..27b9641
--- /dev/null
@@ -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."
+  }
index 52c224f82a8d37b0e65e93cd87d434cfe9be7c74..9401a713174356016abc1a3ed32bdc28add81fb7 100644 (file)
@@ -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