From e1d576bf3103060320b9d8b1fdf2608ac152230e Mon Sep 17 00:00:00 2001 From: aml Date: Thu, 30 Jun 2022 11:47:41 +0300 Subject: [PATCH] 0033046: Modeling algorithms - improve performance of per-facet shape construction Add new class (BRepBuilderAPI_MakeShapeOnMesh) to reconstruct shape from triangulation on per-facet basis. --- .../BRepBuilderAPI_MakeShapeOnMesh.cxx | 247 ++++++++++++++++++ .../BRepBuilderAPI_MakeShapeOnMesh.hxx | 46 ++++ src/BRepBuilderAPI/FILES | 2 + src/StlAPI/StlAPI_Reader.cxx | 65 +---- tests/bugs/fclasses/bug23192_1 | 2 +- tests/bugs/fclasses/bug23192_2 | 2 +- tests/bugs/stlvrml/bug27622 | 4 +- tests/bugs/xde/bug22670_1 | 2 +- tests/bugs/xde/bug22670_2 | 2 +- tests/perf/de/bug33046 | 17 ++ 10 files changed, 327 insertions(+), 62 deletions(-) create mode 100644 src/BRepBuilderAPI/BRepBuilderAPI_MakeShapeOnMesh.cxx create mode 100644 src/BRepBuilderAPI/BRepBuilderAPI_MakeShapeOnMesh.hxx create mode 100644 tests/perf/de/bug33046 diff --git a/src/BRepBuilderAPI/BRepBuilderAPI_MakeShapeOnMesh.cxx b/src/BRepBuilderAPI/BRepBuilderAPI_MakeShapeOnMesh.cxx new file mode 100644 index 0000000000..5d74c71856 --- /dev/null +++ b/src/BRepBuilderAPI/BRepBuilderAPI_MakeShapeOnMesh.cxx @@ -0,0 +1,247 @@ +// Created on: 2022-06-30 +// Created by: Alexander MALYSHEV +// Copyright (c) 2022-2022 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + //! Structure representing mesh edge. + struct Edge + { + //! Constructor. Sets edge nodes. + Edge(const Standard_Integer TheIdx1, + const Standard_Integer TheIdx2) + : Idx1(Min(TheIdx1, TheIdx2)), + Idx2(Max(TheIdx1, TheIdx2)) + {} + + //! Comparison operator. + Standard_Boolean operator<(const Edge& other) const + { + if (Idx1 < other.Idx1 || + (Idx1 == other.Idx1 && Idx2 < other.Idx2)) + { + return Standard_True; + } + + return Standard_False; + } + + //! First index. It is lower or equal than the second. + Standard_Integer Idx1; + + //! Second index. + Standard_Integer Idx2; + }; + + //! Hasher of Edge structure. + struct EdgeHasher + { + + //! Returns hash code for the given edge. + static Standard_Integer HashCode(const Edge& theEdge, + const Standard_Integer theUpperBound) + { + // Circle-based collisions. + return ::HashCode(theEdge.Idx1 * theEdge.Idx1 + theEdge.Idx2 * theEdge.Idx2, theUpperBound); + } + + //! Returns true if two edges are equal. + static Standard_Boolean IsEqual(const Edge& theEdge1, + const Edge& theEdge2) + { + return theEdge1.Idx1 == theEdge2.Idx1 && theEdge1.Idx2 == theEdge2.Idx2; + } + + }; +} + +//======================================================================= +//function : Build +//purpose : +//======================================================================= +void BRepBuilderAPI_MakeShapeOnMesh::Build(const Message_ProgressRange& theRange) +{ + // Generally, this method guarantees topology sharing by mapping mesh primitives + // into topological counterparts. + // mesh points -> topological vertices + // mesh edges -> topological edges + + // Cannot reconstruct anything from null or empty mesh. + if (myMesh.IsNull() || myMesh->NbNodes() == 0 || myMesh->NbTriangles() == 0) + return; + + const Standard_Integer aNbNodes = myMesh->NbNodes(); + const Standard_Integer aNbTriangles = myMesh->NbTriangles(); + + // We are going to have three loops: iterate once over nodes and iterate twice + // over triangles of input mesh. + Message_ProgressScope aPS(theRange, + "Per-facet shape construction", + Standard_Real(aNbNodes + 2 * aNbTriangles)); + + // Build shared vertices. + NCollection_IndexedDataMap aPnt2VertexMap; + + for (Standard_Integer i = 1; i <= aNbNodes; ++i) + { + aPS.Next(); + if (aPS.UserBreak()) + return; + + const gp_Pnt aP = myMesh->Node(i); + const TopoDS_Vertex aV = BRepBuilderAPI_MakeVertex(aP); + aPnt2VertexMap.Add(i, aV); + } + + // Build shared edges. + NCollection_IndexedDataMap anEdgeToTEgeMap; + for (Standard_Integer i = 1; i <= aNbTriangles; ++i) + { + aPS.Next(); + if (aPS.UserBreak()) + return; + + Standard_Integer anIdx[3]; + const Poly_Triangle& aTriangle = myMesh->Triangle(i); + aTriangle.Get(anIdx[0], anIdx[1], anIdx[2]); + + // Skip degenerated triangles. + if (anIdx[0] == anIdx[1] || anIdx[0] == anIdx[2] || anIdx[1] == anIdx[2]) + continue; + + const gp_Pnt aP1 = myMesh->Node(anIdx[0]); + const gp_Pnt aP2 = myMesh->Node(anIdx[1]); + const gp_Pnt aP3 = myMesh->Node(anIdx[2]); + const Standard_Real aD1 = aP1.SquareDistance(aP2); + const Standard_Real aD2 = aP1.SquareDistance(aP3); + const Standard_Real aD3 = aP2.SquareDistance(aP3); + if (aD1 < gp::Resolution() || + aD2 < gp::Resolution() || + aD3 < gp::Resolution()) + { + continue; + } + + // Edges are constructed in forward order for the existing normals orientation. + // In Poly_Triangulation, positive direction is defined as cross product: + // (aV1, aV2) x (aV1, aV3). + const TopoDS_Vertex& aV1 = aPnt2VertexMap.FindFromKey(anIdx[0]); + const TopoDS_Vertex& aV2 = aPnt2VertexMap.FindFromKey(anIdx[1]); + const TopoDS_Vertex& aV3 = aPnt2VertexMap.FindFromKey(anIdx[2]); + + const Edge aMeshEdge1(anIdx[0], anIdx[1]); + const Edge aMeshEdge2(anIdx[1], anIdx[2]); + const Edge aMeshEdge3(anIdx[2], anIdx[0]); + + BRepBuilderAPI_MakeEdge aMaker1(aV1, aV2); + BRepBuilderAPI_MakeEdge aMaker2(aV2, aV3); + BRepBuilderAPI_MakeEdge aMaker3(aV3, aV1); + + TopoDS_Edge aTE1 = aMaker1.Edge(); + if (anIdx[1] < anIdx[0]) + aTE1.Reverse(); + + TopoDS_Edge aTE2 = aMaker2.Edge(); + if (anIdx[2] < anIdx[1]) + aTE2.Reverse(); + + TopoDS_Edge aTE3 = aMaker3.Edge(); + if (anIdx[0] < anIdx[2]) + aTE3.Reverse(); + + anEdgeToTEgeMap.Add(aMeshEdge1, aTE1); + anEdgeToTEgeMap.Add(aMeshEdge2, aTE2); + anEdgeToTEgeMap.Add(aMeshEdge3, aTE3); + } + + // Construct planar faces using shared topology. + TopoDS_Compound aResult; + BRep_Builder aBB; + aBB.MakeCompound(aResult); + for (Standard_Integer i = 1; i <= aNbTriangles; ++i) + { + aPS.Next(); + if (aPS.UserBreak()) + return; + + Standard_Integer anIdx[3]; + const Poly_Triangle& aTriangle = myMesh->Triangle(i); + aTriangle.Get(anIdx[0], anIdx[1], anIdx[2]); + + const Edge aMeshEdge1(anIdx[0], anIdx[1]); + const Edge aMeshEdge2(anIdx[1], anIdx[2]); + const Edge aMeshEdge3(anIdx[2], anIdx[0]); + const Standard_Boolean isReversed1 = anIdx[1] < anIdx[0]; + const Standard_Boolean isReversed2 = anIdx[2] < anIdx[1]; + const Standard_Boolean isReversed3 = anIdx[0] < anIdx[2]; + + // Edges can be skipped in case of mesh defects - topologically or geometrically + // degenerated triangles. + const Standard_Boolean aHasAllEdges = anEdgeToTEgeMap.Contains(aMeshEdge1) && + anEdgeToTEgeMap.Contains(aMeshEdge2) && + anEdgeToTEgeMap.Contains(aMeshEdge3) ; + if (!aHasAllEdges) + continue; + + TopoDS_Edge aTEdge1 = anEdgeToTEgeMap.FindFromKey(aMeshEdge1); + if (isReversed1) + aTEdge1.Reverse(); + TopoDS_Edge aTEdge2 = anEdgeToTEgeMap.FindFromKey(aMeshEdge2); + if (isReversed2) + aTEdge2.Reverse(); + TopoDS_Edge aTEdge3 = anEdgeToTEgeMap.FindFromKey(aMeshEdge3); + if (isReversed3) + aTEdge3.Reverse(); + + BRepBuilderAPI_MakeWire aWireMaker; + aWireMaker.Add(aTEdge1); + aWireMaker.Add(aTEdge2); + aWireMaker.Add(aTEdge3); + const TopoDS_Wire aWire = aWireMaker.Wire(); + + // Construct plane explicitly since it is faster than automatic construction + // within BRepBuilderAPI_MakeFace. + BRepAdaptor_Curve aC1(aTEdge1); + BRepAdaptor_Curve aC2(aTEdge2); + const gp_Dir aD1 = aC1.Line().Direction(); + const gp_Dir aD2 = aC2.Line().Direction(); + gp_XYZ aN = aD1.XYZ().Crossed(aD2.XYZ()); + if (aN.SquareModulus() < Precision::SquareConfusion()) + continue; + if (aTEdge1.Orientation() == TopAbs_REVERSED) + aN.Reverse(); + if (aTEdge2.Orientation() == TopAbs_REVERSED) + aN.Reverse(); + const gp_Dir aNorm(aN); + gp_Pln aPln(myMesh->Node(anIdx[0]), aNorm); + + BRepBuilderAPI_MakeFace aFaceMaker(aPln, aWire); + const TopoDS_Face aFace = aFaceMaker.Face(); + + aBB.Add(aResult, aFace); + } + + this->Done(); + myShape = aResult; +} diff --git a/src/BRepBuilderAPI/BRepBuilderAPI_MakeShapeOnMesh.hxx b/src/BRepBuilderAPI/BRepBuilderAPI_MakeShapeOnMesh.hxx new file mode 100644 index 0000000000..17a5db828a --- /dev/null +++ b/src/BRepBuilderAPI/BRepBuilderAPI_MakeShapeOnMesh.hxx @@ -0,0 +1,46 @@ +// Created on: 2022-06-30 +// Created by: Alexander MALYSHEV +// Copyright (c) 2022-2022 OPEN CASCADE SAS +// +// This file is part of Open CASCADE Technology software library. +// +// This library is free software; you can redistribute it and/or modify it under +// the terms of the GNU Lesser General Public License version 2.1 as published +// by the Free Software Foundation, with special exception defined in the file +// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT +// distribution for complete text of the license and disclaimer of any warranty. +// +// Alternatively, this file may be used under the terms of Open CASCADE +// commercial license or contractual agreement. + +#ifndef _BRepBuilderAPI_MakeShapeOnMesh_HeaderFile +#define _BRepBuilderAPI_MakeShapeOnMesh_HeaderFile + +#include +#include + +//! Builds shape on per-facet basis on the input mesh. Resulting shape has shared +//! edges by construction, but no maximization (unify same domain) is applied. +//! No generation history is provided. +class BRepBuilderAPI_MakeShapeOnMesh : public BRepBuilderAPI_MakeShape +{ +public: + + DEFINE_STANDARD_ALLOC + + //! Ctor. Sets mesh to process. + //! @param theMesh [in] - Mesh to construct shape for. + BRepBuilderAPI_MakeShapeOnMesh(const Handle(Poly_Triangulation)& theMesh) + : myMesh(theMesh) + {} + + //! Builds shape on mesh. + Standard_EXPORT virtual void Build(const Message_ProgressRange& theRange = Message_ProgressRange()) Standard_OVERRIDE; + +private: + + Handle(Poly_Triangulation) myMesh; + +}; + +#endif // _BRepBuilderAPI_MakeShapeOnMesh_HeaderFile diff --git a/src/BRepBuilderAPI/FILES b/src/BRepBuilderAPI/FILES index 4cd5112637..aff1334a50 100644 --- a/src/BRepBuilderAPI/FILES +++ b/src/BRepBuilderAPI/FILES @@ -26,6 +26,8 @@ BRepBuilderAPI_MakePolygon.cxx BRepBuilderAPI_MakePolygon.hxx BRepBuilderAPI_MakeShape.cxx BRepBuilderAPI_MakeShape.hxx +BRepBuilderAPI_MakeShapeOnMesh.cxx +BRepBuilderAPI_MakeShapeOnMesh.hxx BRepBuilderAPI_MakeShell.cxx BRepBuilderAPI_MakeShell.hxx BRepBuilderAPI_MakeSolid.cxx diff --git a/src/StlAPI/StlAPI_Reader.cxx b/src/StlAPI/StlAPI_Reader.cxx index 748d6557eb..347b89f403 100644 --- a/src/StlAPI/StlAPI_Reader.cxx +++ b/src/StlAPI/StlAPI_Reader.cxx @@ -13,18 +13,9 @@ #include -#include -#include -#include -#include +#include #include -#include #include -#include -#include -#include -#include -#include //============================================================================= //function : Read @@ -35,55 +26,17 @@ Standard_Boolean StlAPI_Reader::Read (TopoDS_Shape& theShape, { Handle(Poly_Triangulation) aMesh = RWStl::ReadFile (theFileName); if (aMesh.IsNull()) - { return Standard_False; - } - TopoDS_Vertex aTriVertexes[3]; - TopoDS_Face aFace; - TopoDS_Wire aWire; - BRepBuilderAPI_Sewing aSewingTool; - aSewingTool.Init (1.0e-06, Standard_True); + BRepBuilderAPI_MakeShapeOnMesh aConverter(aMesh); + aConverter.Build(); + if (!aConverter.IsDone()) + return Standard_False; - TopoDS_Compound aComp; - BRep_Builder BuildTool; - BuildTool.MakeCompound (aComp); + TopoDS_Shape aResult = aConverter.Shape(); + if (aResult.IsNull()) + return Standard_False; - for (Standard_Integer aTriIdx = 1; aTriIdx <= aMesh->NbTriangles(); ++aTriIdx) - { - const Poly_Triangle aTriangle = aMesh->Triangle (aTriIdx); - - Standard_Integer anId[3]; - aTriangle.Get(anId[0], anId[1], anId[2]); - - const gp_Pnt aPnt1 = aMesh->Node (anId[0]); - const gp_Pnt aPnt2 = aMesh->Node (anId[1]); - const gp_Pnt aPnt3 = aMesh->Node (anId[2]); - if (!(aPnt1.IsEqual (aPnt2, 0.0)) - && !(aPnt1.IsEqual (aPnt3, 0.0))) - { - aTriVertexes[0] = BRepBuilderAPI_MakeVertex (aPnt1); - aTriVertexes[1] = BRepBuilderAPI_MakeVertex (aPnt2); - aTriVertexes[2] = BRepBuilderAPI_MakeVertex (aPnt3); - - aWire = BRepBuilderAPI_MakePolygon (aTriVertexes[0], aTriVertexes[1], aTriVertexes[2], Standard_True); - if (!aWire.IsNull()) - { - aFace = BRepBuilderAPI_MakeFace (aWire); - if (!aFace.IsNull()) - { - BuildTool.Add (aComp, aFace); - } - } - } - } - - aSewingTool.Load (aComp); - aSewingTool.Perform(); - theShape = aSewingTool.SewedShape(); - if (theShape.IsNull()) - { - theShape = aComp; - } + theShape = aResult; return Standard_True; } diff --git a/tests/bugs/fclasses/bug23192_1 b/tests/bugs/fclasses/bug23192_1 index 56a7b162a5..00032fc677 100755 --- a/tests/bugs/fclasses/bug23192_1 +++ b/tests/bugs/fclasses/bug23192_1 @@ -24,7 +24,7 @@ catch {exec chmod 777 ${aFile}} if { [file exists ${aFile}] } { readstl result ${aFile} -brep -checknbshapes result -vertex 8 -edge 18 -wire 12 -face 12 -shell 1 -solid 0 -compsolid 0 -compound 0 -shape 51 +checknbshapes result -vertex 8 -edge 18 -wire 12 -face 12 -shell 0 -solid 0 -compsolid 0 -compound 1 -shape 51 # Check file size set size_status 0 set filesize [ file size ${aFile} ] diff --git a/tests/bugs/fclasses/bug23192_2 b/tests/bugs/fclasses/bug23192_2 index 3081072bd2..53775e0908 100755 --- a/tests/bugs/fclasses/bug23192_2 +++ b/tests/bugs/fclasses/bug23192_2 @@ -24,7 +24,7 @@ catch {exec chmod 777 ${aFile}} if { [file exists ${aFile}] } { readstl result ${aFile} -brep -checknbshapes result -vertex 8 -edge 18 -wire 12 -face 12 -shell 1 -solid 0 -compsolid 0 -compound 0 -shape 51 +checknbshapes result -vertex 8 -edge 18 -wire 12 -face 12 -shell 0 -solid 0 -compsolid 0 -compound 1 -shape 51 # Check file size set size_status 0 set filesize [ file size ${aFile} ] diff --git a/tests/bugs/stlvrml/bug27622 b/tests/bugs/stlvrml/bug27622 index a19f4d2fc8..94b46b50a7 100644 --- a/tests/bugs/stlvrml/bug27622 +++ b/tests/bugs/stlvrml/bug27622 @@ -14,10 +14,10 @@ Number of shapes in result EDGE : 5 WIRE : 2 FACE : 2 - SHELL : 1 + SHELL : 0 SOLID : 0 COMPSOLID : 0 - COMPOUND : 0 + COMPOUND : 1 SHAPE : 14 " checknbshapes result -ref ${nbshapes_expected} -t -m "Result of STL-reading operation" diff --git a/tests/bugs/xde/bug22670_1 b/tests/bugs/xde/bug22670_1 index 1718c22cac..24a19a081e 100755 --- a/tests/bugs/xde/bug22670_1 +++ b/tests/bugs/xde/bug22670_1 @@ -21,5 +21,5 @@ catch {exec chmod 777 ${aFile}} readstl result ${aFile} -brep -checknbshapes result -vertex 8 -edge 18 -wire 12 -face 12 -shell 1 -solid 0 -compsolid 0 -compound 0 -shape 51 +checknbshapes result -vertex 8 -edge 18 -wire 12 -face 12 -shell 0 -solid 0 -compsolid 0 -compound 1 -shape 51 checkview -display result -2d -path ${imagedir}/${test_image}.png diff --git a/tests/bugs/xde/bug22670_2 b/tests/bugs/xde/bug22670_2 index d9d4938f01..2c50324fc5 100755 --- a/tests/bugs/xde/bug22670_2 +++ b/tests/bugs/xde/bug22670_2 @@ -32,7 +32,7 @@ if { [catch { readstl res_mesh $filepath -brep } catch_result] } { readstl result ${aFile} -brep - checknbshapes result -vertex 8 -edge 18 -wire 12 -face 12 -shell 1 -solid 0 -compsolid 0 -compound 0 -shape 51 + checknbshapes result -vertex 8 -edge 18 -wire 12 -face 12 -shell 0 -solid 0 -compsolid 0 -compound 1 -shape 51 } checkprops result -s 600 checkshape result diff --git a/tests/perf/de/bug33046 b/tests/perf/de/bug33046 new file mode 100644 index 0000000000..5cc1831f31 --- /dev/null +++ b/tests/perf/de/bug33046 @@ -0,0 +1,17 @@ +puts "========" +puts "0033046: Modeling algorithms - improve performance of per-facet shape construction" +puts "Checks execution time of new shape construction method from STL file." +puts "========" + +cpulimit 25 + +# Run stl loading and translation to the shape. +chrono c reset; chrono c start; +readstl m [locate_data_file model_stl_025.stl] -brep +chrono c stop; chrono c show; + +# Check that model is converted succesfully. +checknbshapes m -face 54337; + +# Visual check +checkview -display m -2d -path ${imagedir}/${test_image}.png \ No newline at end of file