0033661: Data Exchange, Step Import - Tessellated GDTs are not imported
[occt.git] / src / RWObj / RWObj_CafWriter.cxx
1 // Copyright (c) 2015-2021 OPEN CASCADE SAS
2 //
3 // This file is part of Open CASCADE Technology software library.
4 //
5 // This library is free software; you can redistribute it and/or modify it under
6 // the terms of the GNU Lesser General Public License version 2.1 as published
7 // by the Free Software Foundation, with special exception defined in the file
8 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
9 // distribution for complete text of the license and disclaimer of any warranty.
10 //
11 // Alternatively, this file may be used under the terms of Open CASCADE
12 // commercial license or contractual agreement.
13
14 #include <RWObj_CafWriter.hxx>
15
16 #include <Message.hxx>
17 #include <Message_LazyProgressScope.hxx>
18 #include <OSD_OpenFile.hxx>
19 #include <OSD_Path.hxx>
20 #include <RWMesh_FaceIterator.hxx>
21 #include <RWObj_ObjMaterialMap.hxx>
22 #include <RWObj_ObjWriterContext.hxx>
23 #include <Standard_CLocaleSentry.hxx>
24 #include <TDocStd_Document.hxx>
25 #include <TDataStd_Name.hxx>
26 #include <XCAFDoc_DocumentTool.hxx>
27 #include <XCAFDoc_ShapeTool.hxx>
28 #include <XCAFPrs_DocumentExplorer.hxx>
29
30 IMPLEMENT_STANDARD_RTTIEXT(RWObj_CafWriter, Standard_Transient)
31
32 namespace
33 {
34   //! Trivial cast.
35   inline Graphic3d_Vec3 objXyzToVec (const gp_XYZ& thePnt)
36   {
37     return Graphic3d_Vec3 ((float )thePnt.X(), (float )thePnt.Y(), (float )thePnt.Z());
38   }
39
40   //! Trivial cast.
41   inline Graphic3d_Vec2 objXyToVec (const gp_XY& thePnt)
42   {
43     return Graphic3d_Vec2 ((float )thePnt.X(), (float )thePnt.Y());
44   }
45
46   //! Read name attribute.
47   static TCollection_AsciiString readNameAttribute (const TDF_Label& theRefLabel)
48   {
49     Handle(TDataStd_Name) aNodeName;
50     if (!theRefLabel.FindAttribute (TDataStd_Name::GetID(), aNodeName))
51     {
52       return TCollection_AsciiString();
53     }
54     return TCollection_AsciiString (aNodeName->Get());
55   }
56 }
57
58 //================================================================
59 // Function : Constructor
60 // Purpose  :
61 //================================================================
62 RWObj_CafWriter::RWObj_CafWriter (const TCollection_AsciiString& theFile)
63 : myFile (theFile)
64 {
65   // OBJ file format doesn't define length units;
66   // Y-up coordinate system is most commonly used (but also undefined)
67   //myCSTrsf.SetOutputCoordinateSystem (RWMesh_CoordinateSystem_negZfwd_posYup);
68 }
69
70 //================================================================
71 // Function : Destructor
72 // Purpose  :
73 //================================================================
74 RWObj_CafWriter::~RWObj_CafWriter()
75 {
76   //
77 }
78
79 //================================================================
80 // Function : toSkipFaceMesh
81 // Purpose  :
82 //================================================================
83 Standard_Boolean RWObj_CafWriter::toSkipFaceMesh (const RWMesh_FaceIterator& theFaceIter)
84 {
85   return theFaceIter.IsEmptyMesh();
86 }
87
88 // =======================================================================
89 // function : Perform
90 // purpose  :
91 // =======================================================================
92 bool RWObj_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument,
93                                const TColStd_IndexedDataMapOfStringString& theFileInfo,
94                                const Message_ProgressRange& theProgress)
95 {
96   TDF_LabelSequence aRoots;
97   Handle(XCAFDoc_ShapeTool) aShapeTool = XCAFDoc_DocumentTool::ShapeTool (theDocument->Main());
98   aShapeTool->GetFreeShapes (aRoots);
99   return Perform (theDocument, aRoots, NULL, theFileInfo, theProgress);
100 }
101
102 // =======================================================================
103 // function : Perform
104 // purpose  :
105 // =======================================================================
106 bool RWObj_CafWriter::Perform (const Handle(TDocStd_Document)& theDocument,
107                                const TDF_LabelSequence& theRootLabels,
108                                const TColStd_MapOfAsciiString* theLabelFilter,
109                                const TColStd_IndexedDataMapOfStringString& theFileInfo,
110                                const Message_ProgressRange& theProgress)
111 {
112   TCollection_AsciiString aFolder, aFileName, aFullFileNameBase, aShortFileNameBase, aFileExt;
113   OSD_Path::FolderAndFileFromPath (myFile, aFolder, aFileName);
114   OSD_Path::FileNameAndExtension (aFileName, aShortFileNameBase, aFileExt);
115
116   Standard_Real aLengthUnit = 1.;
117   if (XCAFDoc_DocumentTool::GetLengthUnit(theDocument, aLengthUnit))
118   {
119     myCSTrsf.SetInputLengthUnit(aLengthUnit);
120   }
121
122   if (theRootLabels.IsEmpty()
123   || (theLabelFilter != NULL && theLabelFilter->IsEmpty()))
124   {
125     Message::SendFail ("Nothing to export into OBJ file");
126     return false;
127   }
128
129   Standard_Integer aNbNodesAll = 0, aNbElemsAll = 0;
130   Standard_Real aNbPEntities = 0; // steps for progress range
131   bool toCreateMatFile = false;
132   for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
133        aDocExplorer.More(); aDocExplorer.Next())
134   {
135     const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
136     if (theLabelFilter != NULL
137     && !theLabelFilter->Contains (aDocNode.Id))
138     {
139       continue;
140     }
141
142     for (RWMesh_FaceIterator aFaceIter (aDocNode.RefLabel, aDocNode.Location, true, aDocNode.Style); aFaceIter.More(); aFaceIter.Next())
143     {
144       if (toSkipFaceMesh (aFaceIter))
145       {
146         continue;
147       }
148
149       addFaceInfo (aFaceIter, aNbNodesAll, aNbElemsAll, aNbPEntities, toCreateMatFile);
150     }
151   }
152   if (aNbNodesAll == 0
153    || aNbElemsAll == 0)
154   {
155     Message::SendFail ("No mesh data to save");
156     return false;
157   }
158
159   TCollection_AsciiString aMatFileNameShort = aShortFileNameBase + ".mtl";
160   const TCollection_AsciiString aMatFileNameFull  = !aFolder.IsEmpty() ? aFolder + aMatFileNameShort : aMatFileNameShort;
161   if (!toCreateMatFile)
162   {
163     aMatFileNameShort.Clear();
164   }
165
166   Standard_CLocaleSentry  aLocaleSentry;
167   RWObj_ObjWriterContext  anObjFile(myFile);
168   RWObj_ObjMaterialMap    aMatMgr  (aMatFileNameFull);
169   aMatMgr.SetDefaultStyle (myDefaultStyle);
170   if (!anObjFile.IsOpened()
171    || !anObjFile.WriteHeader (aNbNodesAll, aNbElemsAll, aMatFileNameShort, theFileInfo))
172   {
173     return false;
174   }
175
176   int aRootDepth = 0;
177   if (theRootLabels.Size() == 1)
178   {
179     TDF_Label aRefLabel = theRootLabels.First();
180     XCAFDoc_ShapeTool::GetReferredShape (theRootLabels.First(), aRefLabel);
181     TCollection_AsciiString aRootName = readNameAttribute (aRefLabel);
182     if (aRootName.EndsWith (".obj"))
183     {
184       // workaround import/export of .obj file
185       aRootDepth = 1;
186     }
187   }
188
189   // simple global progress sentry - ignores size of node and index data
190   const Standard_Real aPatchStep = 2048.0; // about 100 KiB
191   Message_LazyProgressScope aPSentry (theProgress, "OBJ export", aNbPEntities, aPatchStep);
192
193   bool isDone = true;
194   for (XCAFPrs_DocumentExplorer aDocExplorer (theDocument, theRootLabels, XCAFPrs_DocumentExplorerFlags_OnlyLeafNodes);
195        aDocExplorer.More() && !aPSentry.IsAborted(); aDocExplorer.Next())
196   {
197     const XCAFPrs_DocumentNode& aDocNode = aDocExplorer.Current();
198     if (theLabelFilter != NULL
199     && !theLabelFilter->Contains (aDocNode.Id))
200     {
201       continue;
202     }
203
204     TCollection_AsciiString aName = readNameAttribute (aDocNode.RefLabel);
205     for (int aParentIter = aDocExplorer.CurrentDepth() - 1; aParentIter >= aRootDepth; --aParentIter)
206     {
207       const TCollection_AsciiString aParentName = readNameAttribute (aDocExplorer.Current (aParentIter).RefLabel);
208       if (!aParentName.IsEmpty())
209       {
210         aName = aParentName + "/" + aName;
211       }
212     }
213
214     if (!writeShape (anObjFile, aMatMgr, aPSentry, aDocNode.RefLabel, aDocNode.Location, aDocNode.Style, aName))
215     {
216       isDone = false;
217       break;
218     }
219   }
220
221   const bool isClosed = anObjFile.Close();
222   if (isDone && !isClosed)
223   {
224     Message::SendFail (TCollection_AsciiString ("Failed to write OBJ file\n") + myFile);
225     return false;
226   }
227   return isDone && !aPSentry.IsAborted();
228 }
229
230 // =======================================================================
231 // function : addFaceInfo
232 // purpose  :
233 // =======================================================================
234 void RWObj_CafWriter::addFaceInfo (const RWMesh_FaceIterator& theFace,
235                                    Standard_Integer& theNbNodes,
236                                    Standard_Integer& theNbElems,
237                                    Standard_Real& theNbProgressSteps,
238                                    Standard_Boolean& theToCreateMatFile)
239 {
240   theNbNodes += theFace.NbNodes();
241   theNbElems += theFace.NbTriangles();
242
243   theNbProgressSteps += theFace.NbNodes();
244   theNbProgressSteps += theFace.NbTriangles();
245   if (theFace.HasNormals())
246   {
247     theNbProgressSteps += theFace.NbNodes();
248   }
249   if (theFace.HasTexCoords()) //&& !theFace.FaceStyle().Texture().IsEmpty()
250   {
251     theNbProgressSteps += theFace.NbNodes();
252   }
253
254   theToCreateMatFile = theToCreateMatFile
255                    ||  theFace.HasFaceColor()
256                    || (!theFace.FaceStyle().BaseColorTexture().IsNull() && theFace.HasTexCoords());
257 }
258
259 // =======================================================================
260 // function : writeShape
261 // purpose  :
262 // =======================================================================
263 bool RWObj_CafWriter::writeShape (RWObj_ObjWriterContext&        theWriter,
264                                   RWObj_ObjMaterialMap&          theMatMgr,
265                                   Message_LazyProgressScope&     thePSentry,
266                                   const TDF_Label&               theLabel,
267                                   const TopLoc_Location&         theParentTrsf,
268                                   const XCAFPrs_Style&           theParentStyle,
269                                   const TCollection_AsciiString& theName)
270 {
271   bool toCreateGroup = true;
272   for (RWMesh_FaceIterator aFaceIter (theLabel, theParentTrsf, true, theParentStyle); aFaceIter.More() && !thePSentry.IsAborted(); aFaceIter.Next())
273   {
274     if (toSkipFaceMesh (aFaceIter))
275     {
276       continue;
277     }
278
279     ++theWriter.NbFaces;
280     {
281       const bool hasNormals   = aFaceIter.HasNormals();
282       const bool hasTexCoords = aFaceIter.HasTexCoords(); //&& !aFaceIter.FaceStyle().Texture().IsEmpty();
283       if (theWriter.NbFaces != 1)
284       {
285         toCreateGroup = toCreateGroup
286                      || hasNormals   != theWriter.HasNormals()
287                      || hasTexCoords != theWriter.HasTexCoords();
288       }
289       theWriter.SetNormals  (hasNormals);
290       theWriter.SetTexCoords(hasTexCoords);
291     }
292
293     if (toCreateGroup
294     && !theWriter.WriteGroup (theName))
295     {
296       return false;
297     }
298     toCreateGroup = false;
299
300     TCollection_AsciiString aMatName;
301     if (aFaceIter.HasFaceColor()
302     || !aFaceIter.FaceStyle().BaseColorTexture().IsNull())
303     {
304       aMatName = theMatMgr.AddMaterial (aFaceIter.FaceStyle());
305     }
306     if (aMatName != theWriter.ActiveMaterial())
307     {
308       theWriter.WriteActiveMaterial (aMatName);
309     }
310
311     // write nodes
312     if (!writePositions (theWriter, thePSentry, aFaceIter))
313     {
314       return false;
315     }
316
317     // write normals
318     if (theWriter.HasNormals()
319     && !writeNormals (theWriter, thePSentry, aFaceIter))
320     {
321       return false;
322     }
323
324     if (theWriter.HasTexCoords()
325     && !writeTextCoords (theWriter, thePSentry, aFaceIter))
326     {
327       return false;
328     }
329
330     if (!writeIndices (theWriter, thePSentry, aFaceIter))
331     {
332       return false;
333     }
334     theWriter.FlushFace (aFaceIter.NbNodes());
335   }
336   return true;
337 }
338
339 // =======================================================================
340 // function : writePositions
341 // purpose  :
342 // =======================================================================
343 bool RWObj_CafWriter::writePositions (RWObj_ObjWriterContext&    theWriter,
344                                       Message_LazyProgressScope& thePSentry,
345                                       const RWMesh_FaceIterator& theFace)
346 {
347   const Standard_Integer aNodeUpper = theFace.NodeUpper();
348   for (Standard_Integer aNodeIter = theFace.NodeLower(); aNodeIter <= aNodeUpper && thePSentry.More(); ++aNodeIter, thePSentry.Next())
349   {
350     gp_XYZ aNode = theFace.NodeTransformed (aNodeIter).XYZ();
351     myCSTrsf.TransformPosition (aNode);
352     if (!theWriter.WriteVertex (objXyzToVec (aNode)))
353     {
354       return false;
355     }
356   }
357   return true;
358 }
359
360 // =======================================================================
361 // function : writeNormals
362 // purpose  :
363 // =======================================================================
364 bool RWObj_CafWriter::writeNormals (RWObj_ObjWriterContext&    theWriter,
365                                     Message_LazyProgressScope& thePSentry,
366                                     const RWMesh_FaceIterator& theFace)
367 {
368   const Standard_Integer aNodeUpper = theFace.NodeUpper();
369   for (Standard_Integer aNodeIter = theFace.NodeLower(); aNodeIter <= aNodeUpper && thePSentry.More(); ++aNodeIter, thePSentry.Next())
370   {
371     const gp_Dir aNormal = theFace.NormalTransformed (aNodeIter);
372     Graphic3d_Vec3 aNormVec3 = objXyzToVec (aNormal.XYZ());
373     myCSTrsf.TransformNormal (aNormVec3);
374     if (!theWriter.WriteNormal (aNormVec3))
375     {
376       return false;
377     }
378   }
379   return true;
380 }
381
382 // =======================================================================
383 // function : writeTextCoords
384 // purpose  :
385 // =======================================================================
386 bool RWObj_CafWriter::writeTextCoords (RWObj_ObjWriterContext&    theWriter,
387                                        Message_LazyProgressScope& thePSentry,
388                                        const RWMesh_FaceIterator& theFace)
389 {
390   const Standard_Integer aNodeUpper = theFace.NodeUpper();
391   for (Standard_Integer aNodeIter = theFace.NodeLower(); aNodeIter <= aNodeUpper && thePSentry.More(); ++aNodeIter, thePSentry.Next())
392   {
393     gp_Pnt2d aTexCoord = theFace.NodeTexCoord (aNodeIter);
394     if (!theWriter.WriteTexCoord (objXyToVec (aTexCoord.XY())))
395     {
396       return false;
397     }
398   }
399   return true;
400 }
401
402 // =======================================================================
403 // function : writeIndices
404 // purpose  :
405 // =======================================================================
406 bool RWObj_CafWriter::writeIndices (RWObj_ObjWriterContext&    theWriter,
407                                     Message_LazyProgressScope& thePSentry,
408                                     const RWMesh_FaceIterator& theFace)
409 {
410   const Standard_Integer anElemLower = theFace.ElemLower();
411   const Standard_Integer anElemUpper = theFace.ElemUpper();
412   for (Standard_Integer anElemIter = anElemLower; anElemIter <= anElemUpper && thePSentry.More(); ++anElemIter, thePSentry.Next())
413   {
414     const Poly_Triangle aTri = theFace.TriangleOriented (anElemIter);
415     if (!theWriter.WriteTriangle (Graphic3d_Vec3i (aTri(1), aTri(2), aTri(3)) - Graphic3d_Vec3i (anElemLower)))
416     {
417       return false;
418     }
419   }
420   return true;
421 }