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