RWGltf_CafWriter has been fixed to write shared Faces having a different style.
RWGltf_GltfJsonParser::gltfParsePrimArray() now tries to create a shared TopoDS_Face
from the same primitive array definition.
RWGltf_CafReader - improved name generation.
RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, TopLoc_Location(), true, aDocNode.Style);
if (myToMergeFaces)
{
- if (myBinDataMap.Contains (aFaceIter.ExploredShape()))
+ RWGltf_StyledShape aStyledShape (aFaceIter.ExploredShape(), aDocNode.Style);
+ if (myBinDataMap.Contains (aStyledShape))
{
continue;
}
Handle(RWGltf_GltfFaceList) aGltfFaceList = new RWGltf_GltfFaceList();
- myBinDataMap.Add (aFaceIter.ExploredShape(), aGltfFaceList);
+ myBinDataMap.Add (aStyledShape, aGltfFaceList);
for (; aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
{
if (toSkipFaceMesh (aFaceIter))
{
for (; aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
{
+ RWGltf_StyledShape aStyledShape (aFaceIter.Face(), aFaceIter.FaceStyle());
if (toSkipFaceMesh (aFaceIter)
- || myBinDataMap.Contains (aFaceIter.Face()))
+ || myBinDataMap.Contains (aStyledShape))
{
continue;
}
aGltfFace->Shape = aFaceIter.Face();
aGltfFace->Style = aFaceIter.FaceStyle();
aGltfFaceList->Append (aGltfFace);
- myBinDataMap.Add (aFaceIter.Face(), aGltfFaceList);
+ myBinDataMap.Add (aStyledShape, aGltfFaceList);
}
}
}
Standard_Integer aNbAccessors = 0;
NCollection_Map<Handle(RWGltf_GltfFaceList)> aWrittenFaces;
+ NCollection_DataMap<TopoDS_Shape, Handle(RWGltf_GltfFace), TopTools_ShapeMapHasher> aWrittenPrimData;
for (Standard_Integer aTypeIter = 0; aTypeIter < 4; ++aTypeIter)
{
const RWGltf_GltfArrayType anArrType = (RWGltf_GltfArrayType )anArrTypes[aTypeIter];
}
aBuffView->ByteOffset = aBinFile->tellp();
aWrittenFaces.Clear (false);
+ aWrittenPrimData.Clear (false);
for (ShapeToGltfFaceMap::Iterator aBinDataIter (myBinDataMap); aBinDataIter.More() && aPSentryBin.More(); aBinDataIter.Next())
{
const Handle(RWGltf_GltfFaceList)& aGltfFaceList = aBinDataIter.Value();
for (RWGltf_GltfFaceList::Iterator aGltfFaceIter (*aGltfFaceList); aGltfFaceIter.More() && aPSentryBin.More(); aGltfFaceIter.Next())
{
const Handle(RWGltf_GltfFace)& aGltfFace = aGltfFaceIter.Value();
+
+ Handle(RWGltf_GltfFace) anOldGltfFace;
+ if (aWrittenPrimData.Find (aGltfFace->Shape, anOldGltfFace))
+ {
+ switch (anArrType)
+ {
+ case RWGltf_GltfArrayType_Position:
+ {
+ aGltfFace->NodePos = anOldGltfFace->NodePos;
+ break;
+ }
+ case RWGltf_GltfArrayType_Normal:
+ {
+ aGltfFace->NodeNorm = anOldGltfFace->NodeNorm;
+ break;
+ }
+ case RWGltf_GltfArrayType_TCoord0:
+ {
+ aGltfFace->NodeUV = anOldGltfFace->NodeUV;
+ break;
+ }
+ case RWGltf_GltfArrayType_Indices:
+ {
+ aGltfFace->Indices = anOldGltfFace->Indices;
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ continue;
+ }
+ aWrittenPrimData.Bind (aGltfFace->Shape, aGltfFace);
+
for (RWMesh_FaceIterator aFaceIter (aGltfFace->Shape, aGltfFace->Style); aFaceIter.More() && aPSentryBin.More(); aFaceIter.Next())
{
switch (anArrType)
RWGltf_GltfArrayType_Indices
};
NCollection_Map<Handle(RWGltf_GltfFaceList)> aWrittenFaces;
+ NCollection_Map<int> aWrittenIds;
+ int aNbAccessors = 0;
for (Standard_Integer aTypeIter = 0; aTypeIter < 4; ++aTypeIter)
{
const RWGltf_GltfArrayType anArrType = (RWGltf_GltfArrayType )anArrTypes[aTypeIter];
{
case RWGltf_GltfArrayType_Position:
{
+ const int anAccessorId = aGltfFace->NodePos.Id;
+ if (anAccessorId == RWGltf_GltfAccessor::INVALID_ID
+ || !aWrittenIds.Add (anAccessorId))
+ {
+ break;
+ }
+
+ if (anAccessorId != aNbAccessors)
+ {
+ throw Standard_ProgramError ("Internal error: RWGltf_CafWriter::writeAccessors()");
+ }
+ ++aNbAccessors;
writePositions (*aGltfFace);
break;
}
case RWGltf_GltfArrayType_Normal:
{
+ const int anAccessorId = aGltfFace->NodeNorm.Id;
+ if (anAccessorId == RWGltf_GltfAccessor::INVALID_ID
+ || !aWrittenIds.Add (anAccessorId))
+ {
+ break;
+ }
+
+ if (anAccessorId != aNbAccessors)
+ {
+ throw Standard_ProgramError ("Internal error: RWGltf_CafWriter::writeAccessors()");
+ }
+ ++aNbAccessors;
writeNormals (*aGltfFace);
break;
}
case RWGltf_GltfArrayType_TCoord0:
{
+ const int anAccessorId = aGltfFace->NodeUV.Id;
+ if (anAccessorId == RWGltf_GltfAccessor::INVALID_ID
+ || !aWrittenIds.Add (anAccessorId)
+ )
+ {
+ break;
+ }
+
+ if (anAccessorId != aNbAccessors)
+ {
+ throw Standard_ProgramError ("Internal error: RWGltf_CafWriter::writeAccessors()");
+ }
+ ++aNbAccessors;
writeTextCoords (*aGltfFace);
break;
}
case RWGltf_GltfArrayType_Indices:
{
+ const int anAccessorId = aGltfFace->Indices.Id;
+ if (anAccessorId == RWGltf_GltfAccessor::INVALID_ID
+ || !aWrittenIds.Add (anAccessorId)
+ )
+ {
+ break;
+ }
+
+ if (anAccessorId != aNbAccessors)
+ {
+ throw Standard_ProgramError ("Internal error: RWGltf_CafWriter::writeAccessors()");
+ }
+ ++aNbAccessors;
writeIndices (*aGltfFace);
break;
}
Handle(RWGltf_GltfFaceList) aGltfFaceList;
aShape.Location (TopLoc_Location());
- myBinDataMap.FindFromKey (aShape, aGltfFaceList);
+ RWGltf_StyledShape aStyledShape (aShape, aDocNode.Style);
+ myBinDataMap.FindFromKey (aStyledShape, aGltfFaceList);
if (!aWrittenFaces.Add (aGltfFaceList))
{
continue;
continue;
}
- const Handle(RWGltf_GltfFaceList)& aGltfFaceList = myBinDataMap.FindFromKey (aFaceIter.Face());
+ RWGltf_StyledShape aStyledShape (aFaceIter.Face(), aFaceIter.FaceStyle());
+ const Handle(RWGltf_GltfFaceList)& aGltfFaceList = myBinDataMap.FindFromKey (aStyledShape);
if (!aWrittenFaces.Add (aGltfFaceList))
{
continue;
protected:
- typedef NCollection_IndexedDataMap<TopoDS_Shape, Handle(RWGltf_GltfFaceList), TopTools_ShapeMapHasher> ShapeToGltfFaceMap;
+ //! Shape + Style pair.
+ struct RWGltf_StyledShape
+ {
+ TopoDS_Shape Shape;
+ XCAFPrs_Style Style;
+
+ RWGltf_StyledShape() {}
+ explicit RWGltf_StyledShape (const TopoDS_Shape& theShape) : Shape (theShape) {}
+ explicit RWGltf_StyledShape (const TopoDS_Shape& theShape,
+ const XCAFPrs_Style& theStyle) : Shape (theShape), Style (theStyle) {}
+ public:
+ //! Computes a hash code.
+ static Standard_Integer HashCode (const RWGltf_StyledShape& theShape, Standard_Integer theUpperBound)
+ {
+ return theShape.Shape.HashCode (theUpperBound);
+ }
+ //! Equality comparison.
+ static Standard_Boolean IsEqual (const RWGltf_StyledShape& theS1, const RWGltf_StyledShape& theS2)
+ {
+ return theS1.Shape.IsSame (theS2.Shape)
+ && theS1.Style.IsEqual(theS2.Style);
+ }
+ };
+
+ typedef NCollection_IndexedDataMap<RWGltf_StyledShape, Handle(RWGltf_GltfFaceList), RWGltf_StyledShape> ShapeToGltfFaceMap;
protected:
return true;
}
+ const TCollection_AsciiString aUserName ((aName != NULL && aName->IsString()) ? aName->GetString() : "");
+
BRep_Builder aBuilder;
TopoDS_Compound aMeshShape;
int aNbFaces = 0;
for (rapidjson::Value::ConstValueIterator aPrimArrIter = aPrims->Begin();
aPrimArrIter != aPrims->End(); ++aPrimArrIter)
{
- TCollection_AsciiString aUserName;
- if (aName != NULL
- && aName->IsString())
- {
- aUserName = aName->GetString();
- }
-
- Handle(RWGltf_GltfLatePrimitiveArray) aMeshData = new RWGltf_GltfLatePrimitiveArray (theMeshId, aUserName);
- if (!gltfParsePrimArray (aMeshData, theMeshId, *aPrimArrIter))
+ TopoDS_Shape aFace;
+ if (!gltfParsePrimArray (aFace, theMeshId, aUserName, *aPrimArrIter))
{
return false;
}
- if (!aMeshData->Data().IsEmpty())
+ if (!aFace.IsNull())
{
if (aMeshShape.IsNull())
{
aBuilder.MakeCompound (aMeshShape);
}
-
- TopoDS_Face aFace;
- aBuilder.MakeFace (aFace, aMeshData);
aBuilder.Add (aMeshShape, aFace);
- if (myAttribMap != NULL
- && aMeshData->HasStyle())
- {
- RWMesh_NodeAttributes aShapeAttribs;
- aShapeAttribs.RawName = aUserName;
-
- // assign material and not color
- //aShapeAttribs.Style.SetColorSurf (aMeshData->BaseColor());
-
- Handle(XCAFDoc_VisMaterial) aMat;
- myMaterials.Find (!aMeshData->MaterialPbr().IsNull() ? aMeshData->MaterialPbr()->Id : aMeshData->MaterialCommon()->Id, aMat);
- aShapeAttribs.Style.SetMaterial (aMat);
-
- myAttribMap->Bind (aFace, aShapeAttribs);
- }
- myFaceList.Append (aFace);
++aNbFaces;
}
}
// function : gltfParsePrimArray
// purpose :
// =======================================================================
-bool RWGltf_GltfJsonParser::gltfParsePrimArray (const Handle(RWGltf_GltfLatePrimitiveArray)& theMeshData,
+bool RWGltf_GltfJsonParser::gltfParsePrimArray (TopoDS_Shape& thePrimArrayShape,
const TCollection_AsciiString& theMeshId,
+ const TCollection_AsciiString& theMeshName,
const RWGltf_JsonValue& thePrimArray)
{
const RWGltf_JsonValue* anAttribs = findObjectMember (thePrimArray, "attributes");
Message::SendWarning (TCollection_AsciiString() + "Primitive array within Mesh '" + theMeshId + "' skipped due to unsupported mode");
return true;
}
- theMeshData->SetPrimitiveMode (aMode);
- // assign material
+ const TCollection_AsciiString aMatId = aMaterial != NULL ? getKeyString (*aMaterial) : TCollection_AsciiString();
+ const TCollection_AsciiString anIndicesId = anIndices != NULL ? getKeyString (*anIndices) : TCollection_AsciiString();
+ Handle(RWGltf_MaterialMetallicRoughness) aMatPbr;
+ Handle(RWGltf_MaterialCommon) aMatCommon;
+ Handle(XCAFDoc_VisMaterial) aMat;
if (aMaterial != NULL)
{
- Handle(RWGltf_MaterialMetallicRoughness) aMatPbr;
- if (myMaterialsPbr.Find (getKeyString (*aMaterial), aMatPbr))
+ if (myMaterialsPbr.Find (aMatId, aMatPbr))
{
- theMeshData->SetMaterialPbr (aMatPbr);
+ myMaterials.Find (aMatPbr->Id, aMat);
}
+ if (myMaterialsCommon.Find (aMatId, aMatCommon))
+ {
+ if (aMat.IsNull())
+ {
+ myMaterials.Find (aMatCommon->Id, aMat);
+ }
+ }
+ }
- Handle(RWGltf_MaterialCommon) aMatCommon;
- if (myMaterialsCommon.Find (getKeyString (*aMaterial), aMatCommon))
+ // try reusing already loaded primitive array - generate a unique id
+ TCollection_AsciiString aPrimArrayId, aPrimArrayIdWithMat;
+ aPrimArrayId += TCollection_AsciiString (aMode);
+ aPrimArrayId += TCollection_AsciiString (":") + anIndicesId;
+ for (rapidjson::Value::ConstMemberIterator anAttribIter = anAttribs->MemberBegin();
+ anAttribIter != anAttribs->MemberEnd(); ++anAttribIter)
+ {
+ const TCollection_AsciiString anAttribId = getKeyString (anAttribIter->value);
+ aPrimArrayId += TCollection_AsciiString (":") + anAttribId;
+ }
+ aPrimArrayIdWithMat = aPrimArrayId + TCollection_AsciiString ("::") + aMatId;
+ if (myShapeMap[ShapeMapGroup_PrimArray].Find (aPrimArrayIdWithMat, thePrimArrayShape))
+ {
+ return true;
+ }
+ else if (myShapeMap[ShapeMapGroup_PrimArray].Find (aPrimArrayId, thePrimArrayShape))
+ {
+ if (myAttribMap != NULL)
{
- theMeshData->SetMaterialCommon (aMatCommon);
+ // sharing just triangulation is not much useful
+ //Handle(RWGltf_GltfLatePrimitiveArray) aLateData = Handle(RWGltf_GltfLatePrimitiveArray)::DownCast (BRep_Tool::Triangulation (TopoDS::Face (thePrimArrayShape), aDummy));
+ //TopoDS_Face aFaceCopy; BRep_Builder().MakeFace (aFaceCopy, aLateData);
+
+ // make a located Face copy
+ TopoDS_Shape aFaceCopy = thePrimArrayShape;
+ aFaceCopy.Location (TopLoc_Location (gp_Trsf()));
+ RWMesh_NodeAttributes aShapeAttribs;
+ aShapeAttribs.RawName = theMeshName;
+ aShapeAttribs.Style.SetMaterial (aMat);
+ myAttribMap->Bind (aFaceCopy, aShapeAttribs);
+ myShapeMap[ShapeMapGroup_PrimArray].Bind (aPrimArrayIdWithMat, aFaceCopy);
+ thePrimArrayShape = aFaceCopy;
}
+ return true;
+ }
+
+ Handle(RWGltf_GltfLatePrimitiveArray) aMeshData = new RWGltf_GltfLatePrimitiveArray (theMeshId, theMeshName);
+ aMeshData->SetPrimitiveMode (aMode);
+ if (aMaterial != NULL)
+ {
+ aMeshData->SetMaterialPbr (aMatPbr);
+ aMeshData->SetMaterialCommon (aMatCommon);
}
bool hasPositions = false;
reportGltfError ("Primitive array attribute accessor key '" + anAttribId + "' points to non-existing object.");
return false;
}
- else if (!gltfParseAccessor (theMeshData, anAttribId, *anAccessor, aType, aDracoBuf))
+ else if (!gltfParseAccessor (aMeshData, anAttribId, *anAccessor, aType, aDracoBuf))
{
return false;
}
if (anIndices != NULL)
{
- const TCollection_AsciiString anIndicesId = getKeyString (*anIndices);
const RWGltf_JsonValue* anAccessor = myGltfRoots[RWGltf_GltfRootElement_Accessors].FindChild (*anIndices);
if (anAccessor == NULL
|| !anAccessor->IsObject())
reportGltfError ("Primitive array indices accessor key '" + anIndicesId + "' points to non-existing object.");
return false;
}
- else if (!gltfParseAccessor (theMeshData, anIndicesId, *anAccessor, RWGltf_GltfArrayType_Indices, aDracoBuf))
+ else if (!gltfParseAccessor (aMeshData, anIndicesId, *anAccessor, RWGltf_GltfArrayType_Indices, aDracoBuf))
{
return false;
}
}
else
{
- theMeshData->SetNbDeferredTriangles (theMeshData->NbDeferredNodes() / 3);
+ aMeshData->SetNbDeferredTriangles (aMeshData->NbDeferredNodes() / 3);
}
+ if (!aMeshData->Data().IsEmpty())
+ {
+ TopoDS_Face aFace;
+ BRep_Builder aBuilder;
+ aBuilder.MakeFace (aFace, aMeshData);
+ if (myAttribMap != NULL
+ && aMeshData->HasStyle())
+ {
+ RWMesh_NodeAttributes aShapeAttribs;
+ aShapeAttribs.RawName = theMeshName;
+
+ // assign material and not color
+ //aShapeAttribs.Style.SetColorSurf (aMeshData->BaseColor());
+ aShapeAttribs.Style.SetMaterial (aMat);
+
+ myAttribMap->Bind (aFace, aShapeAttribs);
+ }
+ myFaceList.Append (aFace);
+ myShapeMap[ShapeMapGroup_PrimArray].Bind (aPrimArrayId, aFace);
+ myShapeMap[ShapeMapGroup_PrimArray].Bind (aPrimArrayIdWithMat, aFace);
+ thePrimArrayShape = aFace;
+ }
return true;
}
return;
}
+ TopoDS_Shape aShape = theShape;
if (!theLoc.IsIdentity())
{
if (!theShape.Location().IsIdentity())
aUserName = theId;
}
- myShapeMap[theGroup].Bind (theId, theShape);
if (myAttribMap != NULL)
{
RWMesh_NodeAttributes aShapeAttribs;
- aShapeAttribs.Name = aUserName;
- aShapeAttribs.RawName = theId;
+ aShapeAttribs.Name = aUserName;
+ if (myIsGltf1)
+ {
+ aShapeAttribs.RawName = theId;
+ }
if (theShape.ShapeType() == TopAbs_FACE)
{
- TopLoc_Location aDummy;
- if (Handle(RWGltf_GltfLatePrimitiveArray) aLateData = Handle(RWGltf_GltfLatePrimitiveArray)::DownCast (BRep_Tool::Triangulation (TopoDS::Face (theShape), aDummy)))
+ RWMesh_NodeAttributes aFaceAttribs;
+ if (myAttribMap->Find (aShape, aFaceAttribs))
{
- if (aLateData->HasStyle())
- {
- // assign material and not color
- //aShapeAttribs.Style.SetColorSurf (aLateData->BaseColor());
-
- Handle(XCAFDoc_VisMaterial) aMat;
- myMaterials.Find (!aLateData->MaterialPbr().IsNull() ? aLateData->MaterialPbr()->Id : aLateData->MaterialCommon()->Id, aMat);
- aShapeAttribs.Style.SetMaterial (aMat);
- }
+ aShapeAttribs.Style.SetMaterial (aFaceAttribs.Style.Material());
if (aShapeAttribs.Name.IsEmpty()
&& myUseMeshNameAsFallback)
{
// fallback using Mesh name
- aShapeAttribs.Name = aLateData->Name();
+ aShapeAttribs.Name = aFaceAttribs.RawName;
+ }
+ else if (!aFaceAttribs.Name.IsEmpty()
+ && theLoc.IsIdentity()
+ && theGroup == ShapeMapGroup_Nodes)
+ {
+ // keep Product name (from Mesh) separated from Instance name (from Node)
+ theShape.Location (TopLoc_Location (gp_Trsf()) * theShape.Location(), Standard_False);
}
}
}
aShapeAttribs.Name = aMeshName;
}
}
+ else if (!aShapeAttribs.Name.IsEmpty()
+ && theGroup == ShapeMapGroup_Nodes)
+ {
+ RWMesh_NodeAttributes anOldAttribs;
+ if (myAttribMap->Find (aShape, anOldAttribs)
+ && !anOldAttribs.Name.IsEmpty())
+ {
+ // keep Product name (from Mesh) separated from Instance name (from Node)
+ theShape.Location (TopLoc_Location (gp_Trsf()) * theShape.Location(), Standard_False);
+ }
+ }
myAttribMap->Bind (theShape, aShapeAttribs);
}
+ myShapeMap[theGroup].Bind (theId, theShape);
}
#endif
const RWGltf_JsonValue& theMesh);
//! Parse primitive array.
- Standard_EXPORT bool gltfParsePrimArray (const Handle(RWGltf_GltfLatePrimitiveArray)& theMeshData,
+ Standard_EXPORT bool gltfParsePrimArray (TopoDS_Shape& thePrimArrayShape,
+ const TCollection_AsciiString& theMeshId,
const TCollection_AsciiString& theMeshName,
const RWGltf_JsonValue& thePrimArray);
//! Groups for re-using shapes.
enum ShapeMapGroup
{
- ShapeMapGroup_Nodes, //!< nodes
- ShapeMapGroup_Meshes, //!< meshes
+ ShapeMapGroup_Nodes, //!< nodes
+ ShapeMapGroup_Meshes, //!< meshes
+ ShapeMapGroup_PrimArray, //!< primitive array
};
//! Bind name attribute.
NCollection_DataMap<TCollection_AsciiString, Handle(RWGltf_MaterialMetallicRoughness)> myMaterialsPbr;
NCollection_DataMap<TCollection_AsciiString, Handle(RWGltf_MaterialCommon)> myMaterialsCommon;
NCollection_DataMap<TCollection_AsciiString, Handle(XCAFDoc_VisMaterial)> myMaterials;
- NCollection_DataMap<TCollection_AsciiString, TopoDS_Shape> myShapeMap[2];
+ NCollection_DataMap<TCollection_AsciiString, TopoDS_Shape> myShapeMap[3];
NCollection_DataMap<TCollection_AsciiString, bool> myProbedFiles;
NCollection_DataMap<TCollection_AsciiString, Handle(NCollection_Buffer)> myDecodedBuffers;
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
{
return;
}
- aShape.Location (theLocation);
+ aShape.Location (theLocation, false);
myFaceIter.Init (aShape, TopAbs_FACE);
if (theToMapColors)
{
dispatchStyles (theLabel, theLocation, theStyle);
+ myStyles.Bind (aShape, theStyle);
}
Next();
ReadGltf D1 "$aTmpGltf1"
XGetOneShape s1 D1
-checknbshapes s1 -face 160 -compound 28
+checknbshapes s1 -face 53 -compound 28
ReadGltf D "$aTmpGltf2"
XGetOneShape s2 D
-checknbshapes s2 -face 18 -compound 10
+checknbshapes s2 -face 5 -compound 10
--- /dev/null
+puts "========"
+puts "0032107: Data Exchange, RWGltf_CafReader - reading glTF document back loses sharing"
+puts "========"
+
+vclear
+vclose ALL
+Close *
+source $env(CSF_OCCTSamplesPath)/tcl/vis_pbr_spheres.tcl
+vdump "${imagedir}/${casename}_0.png"
+
+set aTmpGltf1 "${imagedir}/${casename}_tmp1.glb"
+set aTmpGltf1m "${imagedir}/${casename}_tmp1m.glb"
+set aTmpGltf2 "${imagedir}/${casename}_tmp2.glb"
+set aTmpGltf2m "${imagedir}/${casename}_tmp2m.glb"
+lappend occ_tmp_files $aTmpGltf1
+lappend occ_tmp_files $aTmpGltf1m
+lappend occ_tmp_files $aTmpGltf2
+lappend occ_tmp_files $aTmpGltf2m
+
+WriteGltf D "$aTmpGltf1"
+puts [file size "$aTmpGltf1"]
+WriteGltf D "$aTmpGltf1m" -mergeFaces
+puts [file size "$aTmpGltf1m"]
+
+ReadGltf D1 "$aTmpGltf1"
+XGetOneShape s1 D1
+checknbshapes s1 -face 26 -compound 22
+vclear
+XDisplay D1 -dispMode 1
+vdump "${imagedir}/${casename}_1.png"
+
+ReadGltf D1m "$aTmpGltf1m"
+XGetOneShape s1m D1m
+checknbshapes s1m -face 5 -compound 18
+vclear
+XDisplay D1m -dispMode 1
+vdump "${imagedir}/${casename}_1m.png"
+
+WriteGltf D1 "$aTmpGltf2"
+puts [file size "$aTmpGltf2"]
+WriteGltf D1m "$aTmpGltf2m" -mergeFaces
+puts [file size "$aTmpGltf2m"]
+
+ReadGltf D2 "$aTmpGltf2"
+XGetOneShape s2 D2
+checknbshapes s2 -face 26 -compound 22
+vclear
+XDisplay D2 -dispMode 1
+vdump "${imagedir}/${casename}_2.png"
+
+ReadGltf D2m "$aTmpGltf2m"
+XGetOneShape s2m D2m
+checknbshapes s2m -face 5 -compound 18
+vclear
+XDisplay D2m -dispMode 1
+vdump "${imagedir}/${casename}_2m.png"