1 // Author: Kirill Gavrilov
2 // Copyright (c) 2017-2019 OPEN CASCADE SAS
4 // This file is part of Open CASCADE Technology software library.
6 // This library is free software; you can redistribute it and/or modify it under
7 // the terms of the GNU Lesser General Public License version 2.1 as published
8 // by the Free Software Foundation, with special exception defined in the file
9 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
10 // distribution for complete text of the license and disclaimer of any warranty.
12 // Alternatively, this file may be used under the terms of Open CASCADE
13 // commercial license or contractual agreement.
15 #include <RWObj_Reader.hxx>
17 #include <RWObj_MtlReader.hxx>
19 #include <BRepMesh_DataStructureOfDelaun.hxx>
20 #include <BRepMesh_Delaun.hxx>
22 #include <Message.hxx>
23 #include <Message_Messenger.hxx>
24 #include <Message_ProgressSentry.hxx>
25 #include <NCollection_IncAllocator.hxx>
26 #include <OSD_OpenFile.hxx>
27 #include <OSD_Path.hxx>
28 #include <OSD_Timer.hxx>
29 #include <Precision.hxx>
30 #include <Standard_CLocaleSentry.hxx>
36 #define ftell64(a) _ftelli64(a)
37 #define fseek64(a,b,c) _fseeki64(a,b,c)
39 #define ftell64(a) ftello(a)
40 #define fseek64(a,b,c) fseeko(a,b,c)
43 IMPLEMENT_STANDARD_RTTIEXT(RWObj_Reader, Standard_Transient)
49 struct RWObj_ReaderFile
52 NCollection_Array1<char> Line;
53 Standard_Integer LineBuffLen;
54 Standard_Integer MaxLineLen;
58 //! Constructor opening the file.
59 RWObj_ReaderFile (const TCollection_AsciiString& theFile,
60 const Standard_Integer theMaxLineLen = 256)
61 : File (OSD_OpenFile (theFile.ToCString(), "rb")),
62 Line (0, theMaxLineLen - 1),
63 LineBuffLen (theMaxLineLen),
64 MaxLineLen (theMaxLineLen),
68 if (this->File != NULL)
70 // determine length of file
71 ::fseek64 (this->File, 0, SEEK_END);
72 FileLen = ::ftell64 (this->File);
73 ::fseek64 (this->File, 0, SEEK_SET);
77 //! Destructor closing the file.
86 //! Read line, also considers multi-line syntax (when last line symbol is slash).
89 int64_t aPosPrev = this->Position;
90 char* aLine = &Line.ChangeFirst();
91 for (; ::feof (this->File) == 0 && ::fgets (aLine, MaxLineLen - 1, this->File) != NULL; )
93 const int64_t aPosNew = ::ftell64 (this->File);
100 const Standard_Integer aNbRead = Standard_Integer(aPosNew - aPosPrev);
101 bool toReadMore = false;
102 for (int aTailIter = aNbRead - 1; aTailIter >= 0; --aTailIter)
104 if (aLine[aTailIter] != '\n'
105 && aLine[aTailIter] != '\r'
106 && aLine[aTailIter] != '\0')
108 if (aLine[aTailIter] == '\\')
111 aLine[aTailIter] = ' ';
112 const ptrdiff_t aFullLen = aLine + aTailIter + 1 - &this->Line.First();
113 if (LineBuffLen < aFullLen + MaxLineLen)
115 LineBuffLen += MaxLineLen;
116 this->Line.Resize (0, LineBuffLen - 1, true);
118 aLine = &this->Line.ChangeFirst() + aFullLen;
140 //! Return TRUE if given polygon has clockwise node order.
141 static bool isClockwisePolygon (const Handle(BRepMesh_DataStructureOfDelaun)& theMesh,
142 const IMeshData::VectorOfInteger& theIndexes)
145 const int aNbElemNodes = theIndexes.Size();
146 for (int aNodeIter = theIndexes.Lower(); aNodeIter <= theIndexes.Upper(); ++aNodeIter)
148 int aNodeNext = theIndexes.Lower() + ((aNodeIter + 1) % aNbElemNodes);
149 const BRepMesh_Vertex& aVert1 = theMesh->GetNode (theIndexes.Value (aNodeIter));
150 const BRepMesh_Vertex& aVert2 = theMesh->GetNode (theIndexes.Value (aNodeNext));
151 aPtSum += (aVert2.Coord().X() - aVert1.Coord().X())
152 * (aVert2.Coord().Y() + aVert1.Coord().Y());
158 // ================================================================
161 // ================================================================
162 RWObj_Reader::RWObj_Reader()
163 : myMemLimitBytes (Standard_Size(-1)),
174 // ================================================================
177 // ================================================================
178 Standard_Boolean RWObj_Reader::read (const TCollection_AsciiString& theFile,
179 const Handle(Message_ProgressIndicator)& theProgress,
180 const Standard_Boolean theToProbe)
189 myObjVertsUV.Clear();
191 myPackedIndices.Clear();
193 myFileComments.Clear();
194 myExternalFiles.Clear();
195 myActiveSubMesh = RWObj_SubMesh();
197 // determine file location to load associated files
198 TCollection_AsciiString aFileName;
199 OSD_Path::FolderAndFileFromPath (theFile, myFolder, aFileName);
200 myCurrElem.resize (1024, -1);
202 Standard_CLocaleSentry aLocaleSentry;
203 RWObj_ReaderFile aFile (theFile);
204 if (aFile.File == NULL)
206 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: file '") + theFile + "' is not found!", Message_Fail);
207 return Standard_False;
210 // determine length of file
211 const int64_t aFileLen = aFile.FileLen;
214 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: file '") + theFile + "' is empty!", Message_Fail);
215 return Standard_False;
218 const Standard_Integer aNbMiBTotal = Standard_Integer(aFileLen / (1024 * 1024));
219 Standard_Integer aNbMiBPassed = 0;
220 Message_ProgressSentry aPSentry (theProgress, "Reading text OBJ file", 0, aNbMiBTotal, 1);
225 for (; aFile.ReadLine(); )
228 const char* aLine = &aFile.Line.First();
229 if (aTimer.ElapsedTime() > 1.0)
231 if (!aPSentry.More())
236 const Standard_Integer aNbMiBRead = Standard_Integer(aFile.Position / (1024 * 1024));
237 for (; aNbMiBPassed < aNbMiBRead; ++aNbMiBPassed) { aPSentry.Next(); }
246 TCollection_AsciiString aComment (aLine + 1);
247 aComment.LeftAdjust();
248 aComment.RightAdjust();
249 if (!aComment.IsEmpty())
251 if (!myFileComments.IsEmpty())
253 myFileComments += "\n";
255 myFileComments += aComment;
260 else if (*aLine == '\n'
270 if (::memcmp (aLine, "mtllib", 6) == 0)
272 readMaterialLib (aLine + 7);
274 else if (aLine[0] == 'v' && RWObj_Tools::isSpaceChar (aLine[1]))
278 else if (aLine[0] == 'f' && RWObj_Tools::isSpaceChar (aLine[1]))
285 if (aLine[0] == 'v' && RWObj_Tools::isSpaceChar (aLine[1]))
288 pushVertex (aLine + 2);
290 else if (aLine[0] == 'v'
292 && RWObj_Tools::isSpaceChar (aLine[2]))
294 pushNormal (aLine + 3);
296 else if (aLine[0] == 'v'
298 && RWObj_Tools::isSpaceChar (aLine[2]))
300 pushTexel (aLine + 3);
302 else if (aLine[0] == 'f' && RWObj_Tools::isSpaceChar (aLine[1]))
305 pushIndices (aLine + 2);
307 else if (aLine[0] == 'g' && IsSpace (aLine[1]))
309 pushGroup (aLine + 2);
311 else if (aLine[0] == 's' && IsSpace (aLine[1]))
313 pushSmoothGroup (aLine + 2);
315 else if (aLine[0] == 'o' && IsSpace (aLine[1]))
317 pushObject (aLine + 2);
319 else if (::memcmp (aLine, "mtllib", 6) == 0)
321 readMaterialLib (aLine + 7);
323 else if (::memcmp (aLine, "usemtl", 6) == 0)
325 pushMaterial (aLine + 7);
330 addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject);
335 // collect external references
336 for (NCollection_DataMap<TCollection_AsciiString, RWObj_Material>::Iterator aMatIter (myMaterials); aMatIter.More(); aMatIter.Next())
338 const RWObj_Material& aMat = aMatIter.Value();
339 if (!aMat.DiffuseTexture.IsEmpty())
341 myExternalFiles.Add (aMat.DiffuseTexture);
343 if (!aMat.SpecularTexture.IsEmpty())
345 myExternalFiles.Add (aMat.SpecularTexture);
347 if (!aMat.BumpTexture.IsEmpty())
349 myExternalFiles.Add (aMat.BumpTexture);
353 // flush the last group
356 addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject);
358 if (myNbElemsBig != 0)
360 Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: OBJ reader, ") + myNbElemsBig
361 + " polygon(s) have been split into triangles.", Message_Warning);
364 for (; aNbMiBPassed < aNbMiBTotal; ++aNbMiBPassed) { aPSentry.Next(); }
368 // =======================================================================
369 // function : pushIndices
371 // =======================================================================
372 void RWObj_Reader::pushIndices (const char* thePos)
376 Standard_Integer aNbElemNodes = 0;
377 for (Standard_Integer aNode = 0;; ++aNode)
379 Graphic3d_Vec3i a3Indices (-1, -1, -1);
380 a3Indices[0] = strtol (thePos, &aNext, 10) - 1;
391 a3Indices[1] = strtol (thePos, &aNext, 10) - 1;
394 // parse Normal index
398 a3Indices[2] = strtol (thePos, &aNext, 10) - 1;
403 // handle negative indices
404 if (a3Indices[0] < -1)
406 a3Indices[0] += myObjVerts.Upper() + 2;
408 if (a3Indices[1] < -1)
410 a3Indices[1] += myObjVertsUV.Upper() + 2;
412 if (a3Indices[2] < -1)
414 a3Indices[2] += myObjNorms.Upper() + 2;
417 Standard_Integer anIndex = -1;
418 if (!myPackedIndices.Find (a3Indices, anIndex))
420 if (a3Indices[0] >= 0)
422 myMemEstim += sizeof(Graphic3d_Vec3);
424 if (a3Indices[1] >= 0)
426 myMemEstim += sizeof(Graphic3d_Vec2);
428 if (a3Indices[2] >= 0)
430 myMemEstim += sizeof(Graphic3d_Vec3);
432 myMemEstim += sizeof(Graphic3d_Vec4i) + sizeof(Standard_Integer); // naive map
433 if (a3Indices[0] < myObjVerts.Lower() || a3Indices[0] > myObjVerts.Upper())
436 Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: invalid OBJ syntax at line ") + myNbLines
437 + ": vertex index is out of range.", Message_Fail);
441 anIndex = addNode (myObjVerts.Value (a3Indices[0]));
442 myPackedIndices.Bind (a3Indices, anIndex);
443 if (a3Indices[1] >= 0)
445 if (myObjVertsUV.IsEmpty())
447 Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
448 + ": UV index is specified but no UV nodes are defined.", Message_Warning);
450 else if (a3Indices[1] < myObjVertsUV.Lower() || a3Indices[1] > myObjVertsUV.Upper())
452 Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
453 + ": UV index is out of range.", Message_Warning);
454 setNodeUV (anIndex,Graphic3d_Vec2 (0.0f, 0.0f));
458 setNodeUV (anIndex, myObjVertsUV.Value (a3Indices[1]));
461 if (a3Indices[2] >= 0)
463 if (myObjNorms.IsEmpty())
465 Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
466 + ": Normal index is specified but no Normals nodes are defined.", Message_Warning);
468 else if (a3Indices[2] < myObjNorms.Lower() || a3Indices[2] > myObjNorms.Upper())
470 Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ") + myNbLines
471 + ": Normal index is out of range.", Message_Warning);
472 setNodeNormal (anIndex, Graphic3d_Vec3 (0.0f, 0.0f, 1.0f));
476 setNodeNormal (anIndex, myObjNorms.Value (a3Indices[2]));
481 if (myCurrElem.size() < size_t(aNode))
483 myCurrElem.resize (aNode * 2, -1);
485 myCurrElem[aNode] = anIndex;
486 aNbElemNodes = aNode + 1;
500 if (myCurrElem[0] < 0
508 if (aNbElemNodes == 3)
510 myMemEstim += sizeof(Graphic3d_Vec4i);
511 addElement (myCurrElem[0], myCurrElem[1], myCurrElem[2], -1);
513 else if (aNbElemNodes == 4)
515 myMemEstim += sizeof(Graphic3d_Vec4i);
516 addElement (myCurrElem[0], myCurrElem[1], myCurrElem[2], myCurrElem[3]);
520 const NCollection_Array1<Standard_Integer> aCurrElemArray1 (myCurrElem[0], 1, aNbElemNodes);
521 const Standard_Integer aNbAdded = triangulatePolygon (aCurrElemArray1);
527 myMemEstim += sizeof(Graphic3d_Vec4i) * aNbAdded;
531 //================================================================
532 // Function : triangulatePolygonFan
534 //================================================================
535 Standard_Integer RWObj_Reader::triangulatePolygonFan (const NCollection_Array1<Standard_Integer>& theIndices)
537 const Standard_Integer aNbElemNodes = theIndices.Size();
538 for (Standard_Integer aNodeIter = 0; aNodeIter < aNbElemNodes - 2; ++aNodeIter)
540 Graphic3d_Vec4i aTriNodes (-1, -1, -1, -1);
541 for (Standard_Integer aNodeInSubTriIter = 0; aNodeInSubTriIter < 3; ++aNodeInSubTriIter)
543 const Standard_Integer aCurrNodeIndex = (aNodeInSubTriIter == 0) ? 0 : (aNodeIter + aNodeInSubTriIter);
544 aTriNodes[aNodeInSubTriIter] = theIndices.Value (theIndices.Lower() + aCurrNodeIndex);
546 addElement (aTriNodes[0], aTriNodes[1], aTriNodes[2], -1);
548 return aNbElemNodes - 2;
551 //================================================================
552 // Function : polygonCenter
554 //================================================================
555 gp_XYZ RWObj_Reader::polygonCenter (const NCollection_Array1<Standard_Integer>& theIndices)
557 if (theIndices.Size() < 3)
559 return gp_XYZ (0.0, 0.0, 0.0);
561 else if (theIndices.Size() == 4)
563 gp_XYZ aCenter = getNode (theIndices.Value (theIndices.Lower() + 0)).XYZ()
564 + getNode (theIndices.Value (theIndices.Lower() + 2)).XYZ();
569 gp_XYZ aCenter (0, 0, 0);
570 for (NCollection_Array1<Standard_Integer>::Iterator aPntIter (theIndices); aPntIter.More(); aPntIter.Next())
572 aCenter += getNode (aPntIter.Value()).XYZ();
575 aCenter /= (Standard_Real )theIndices.Size();
579 //================================================================
580 // Function : polygonNormal
582 //================================================================
583 gp_XYZ RWObj_Reader::polygonNormal (const NCollection_Array1<Standard_Integer>& theIndices)
585 const gp_XYZ aCenter = polygonCenter (theIndices);
586 gp_XYZ aMaxDir = getNode (theIndices.First()).XYZ() - aCenter;
587 gp_XYZ aNormal = (getNode (theIndices.Last()).XYZ() - aCenter).Crossed (aMaxDir);
588 for (int aPntIter = theIndices.Lower(); aPntIter < theIndices.Upper(); ++aPntIter)
590 const gp_XYZ aTmpDir2 = getNode (theIndices.Value (aPntIter + 1)).XYZ() - aCenter;
591 if (aTmpDir2.SquareModulus() > aMaxDir.SquareModulus())
596 const gp_XYZ aTmpDir1 = getNode (theIndices.Value (aPntIter)).XYZ() - aCenter;
597 gp_XYZ aDelta = aTmpDir1.Crossed (aTmpDir2);
598 if (aNormal.Dot (aDelta) < 0.0)
605 const Standard_Real aMod = aNormal.Modulus();
606 if (aMod > gp::Resolution())
613 //================================================================
614 // Function : triangulatePolygon
616 //================================================================
617 Standard_Integer RWObj_Reader::triangulatePolygon (const NCollection_Array1<Standard_Integer>& theIndices)
619 const Standard_Integer aNbElemNodes = theIndices.Size();
620 if (aNbElemNodes < 3)
625 const gp_XYZ aPolygonNorm = polygonNormal (theIndices);
627 // map polygon onto plane
630 const double aAbsXYZ[] = { Abs(aPolygonNorm.X()), Abs(aPolygonNorm.Y()), Abs(aPolygonNorm.Z()) };
631 Standard_Integer aMinI = (aAbsXYZ[0] < aAbsXYZ[1]) ? 0 : 1;
632 aMinI = (aAbsXYZ[aMinI] < aAbsXYZ[2]) ? aMinI : 2;
633 const Standard_Integer aI1 = (aMinI + 1) % 3 + 1;
634 const Standard_Integer aI2 = (aMinI + 2) % 3 + 1;
635 aXDir.ChangeCoord (aMinI + 1) = 0;
636 aXDir.ChangeCoord (aI1) = aPolygonNorm.Coord (aI2);
637 aXDir.ChangeCoord (aI2) = -aPolygonNorm.Coord (aI1);
639 const gp_XYZ aYDir = aPolygonNorm ^ aXDir;
641 Handle(NCollection_IncAllocator) anAllocator = new NCollection_IncAllocator();
642 Handle(BRepMesh_DataStructureOfDelaun) aMeshStructure = new BRepMesh_DataStructureOfDelaun (anAllocator);
643 IMeshData::VectorOfInteger anIndexes (aNbElemNodes, anAllocator);
644 for (Standard_Integer aNodeIter = 0; aNodeIter < aNbElemNodes; ++aNodeIter)
646 const Standard_Integer aNodeIndex = theIndices.Value (theIndices.Lower() + aNodeIter);
647 const gp_XYZ aPnt3d = getNode (aNodeIndex).XYZ();
648 gp_XY aPnt2d (aXDir * aPnt3d, aYDir * aPnt3d);
649 BRepMesh_Vertex aVertex (aPnt2d, aNodeIndex, BRepMesh_Frontier);
650 anIndexes.Append (aMeshStructure->AddNode (aVertex));
653 const bool isClockwiseOrdered = isClockwisePolygon (aMeshStructure, anIndexes);
654 for (Standard_Integer aIdx = anIndexes.Lower(); aIdx <= anIndexes.Upper(); ++aIdx)
656 const Standard_Integer aPtIdx = isClockwiseOrdered ? aIdx : (aIdx + 1) % anIndexes.Length();
657 const Standard_Integer aNextPtIdx = isClockwiseOrdered ? (aIdx + 1) % anIndexes.Length() : aIdx;
658 BRepMesh_Edge anEdge (anIndexes.Value (aPtIdx),
659 anIndexes.Value (aNextPtIdx),
661 aMeshStructure->AddLink (anEdge);
666 BRepMesh_Delaun aTriangulation (aMeshStructure, anIndexes);
667 const IMeshData::MapOfInteger& aTriangles = aMeshStructure->ElementsOfDomain();
668 if (aTriangles.Extent() < 1)
670 return triangulatePolygonFan (theIndices);
673 Standard_Integer aNbTrisAdded = 0;
674 for (IMeshData::MapOfInteger::Iterator aTriIter (aTriangles); aTriIter.More(); aTriIter.Next())
676 const Standard_Integer aTriangleId = aTriIter.Key();
677 const BRepMesh_Triangle& aTriangle = aMeshStructure->GetElement (aTriangleId);
678 if (aTriangle.Movability() == BRepMesh_Deleted)
684 aMeshStructure->ElementNodes (aTriangle, aTri2d);
685 if (!isClockwiseOrdered)
687 std::swap (aTri2d[1], aTri2d[2]);
689 const BRepMesh_Vertex& aVertex1 = aMeshStructure->GetNode (aTri2d[0]);
690 const BRepMesh_Vertex& aVertex2 = aMeshStructure->GetNode (aTri2d[1]);
691 const BRepMesh_Vertex& aVertex3 = aMeshStructure->GetNode (aTri2d[2]);
692 addElement (aVertex1.Location3d(), aVertex2.Location3d(), aVertex3.Location3d(), -1);
697 catch (Standard_Failure const& theFailure)
699 Message::DefaultMessenger()->Send (TCollection_AsciiString ("Error: exception raised during polygon split\n[")
700 + theFailure.GetMessageString() + "]", Message_Warning);
702 return triangulatePolygonFan (theIndices);
705 // =======================================================================
706 // function : pushObject
708 // =======================================================================
709 void RWObj_Reader::pushObject (const char* theObjectName)
711 TCollection_AsciiString aNewObject;
712 if (!RWObj_Tools::ReadName (theObjectName, aNewObject))
714 // empty group name is OK
716 if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewObject))
718 myPackedIndices.Clear(); // vertices might be duplicated after this point...
720 myActiveSubMesh.Object = aNewObject;
723 // =======================================================================
724 // function : pushGroup
726 // =======================================================================
727 void RWObj_Reader::pushGroup (const char* theGroupName)
729 TCollection_AsciiString aNewGroup;
730 if (!RWObj_Tools::ReadName (theGroupName, aNewGroup))
732 // empty group name is OK
734 if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewGroup))
736 myPackedIndices.Clear(); // vertices might be duplicated after this point...
738 myActiveSubMesh.Group = aNewGroup;
741 // =======================================================================
742 // function : pushSmoothGroup
744 // =======================================================================
745 void RWObj_Reader::pushSmoothGroup (const char* theSmoothGroupIndex)
747 TCollection_AsciiString aNewSmoothGroup;
748 RWObj_Tools::ReadName (theSmoothGroupIndex, aNewSmoothGroup);
749 if (aNewSmoothGroup == "off"
750 || aNewSmoothGroup == "0")
752 aNewSmoothGroup.Clear();
754 if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewSmoothGroup))
756 myPackedIndices.Clear(); // vertices might be duplicated after this point...
758 myActiveSubMesh.SmoothGroup = aNewSmoothGroup;
761 // =======================================================================
762 // function : pushMaterial
764 // =======================================================================
765 void RWObj_Reader::pushMaterial (const char* theMaterialName)
767 TCollection_AsciiString aNewMat;
768 if (!RWObj_Tools::ReadName (theMaterialName, aNewMat))
770 // empty material name is allowed by specs
772 else if (!myMaterials.IsBound (aNewMat))
774 Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: use of undefined OBJ material at line ")
775 + myNbLines, Message_Warning);
778 if (myActiveSubMesh.Material.IsEqual (aNewMat))
783 // implicitly create a new group to split materials
784 if (addMesh (myActiveSubMesh, RWObj_SubMeshReason_NewMaterial))
786 myPackedIndices.Clear(); // vertices might be duplicated after this point...
788 myActiveSubMesh.Material = aNewMat;
791 // =======================================================================
792 // function : readMaterialLib
794 // =======================================================================
795 void RWObj_Reader::readMaterialLib (const char* theFileName)
797 TCollection_AsciiString aMatPath;
798 if (!RWObj_Tools::ReadName (theFileName, aMatPath))
800 Message::DefaultMessenger()->Send (TCollection_AsciiString("Warning: invalid OBJ syntax at line ")
801 + myNbLines, Message_Warning);
805 RWObj_MtlReader aMatReader (myMaterials);
806 if (aMatReader.Read (myFolder, aMatPath))
808 myExternalFiles.Add (myFolder + aMatPath);
812 // =======================================================================
813 // function : checkMemory
815 // =======================================================================
816 bool RWObj_Reader::checkMemory()
818 if (myMemEstim < myMemLimitBytes
824 Message::DefaultMessenger()->Send (TCollection_AsciiString("Error: OBJ file content does not fit into ")
825 + Standard_Integer(myMemLimitBytes / (1024 * 1024)) + " MiB limit."
826 + "\nMesh data will be truncated.", Message_Fail);