From 46230c9a93bada5d147df70dba4fdecd7236d990 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Sat, 3 Feb 2024 12:23:03 -0500 Subject: [PATCH] Add Test for MakeElementRefine --- src/Mod/Part/App/TopoShape.cpp | 9 ++- src/Mod/Part/App/TopoShape.h | 33 +++++--- src/Mod/Part/App/TopoShapeExpansion.cpp | 57 ++++++++------ src/Mod/Part/App/TopoShapeMapper.cpp | 76 ++++++++++++++----- src/Mod/Part/App/TopoShapeMapper.h | 25 +++++- tests/src/Mod/Part/App/CMakeLists.txt | 1 + tests/src/Mod/Part/App/TopoShapeExpansion.cpp | 3 +- .../Part/App/TopoShapeMakeElementRefine.cpp | 53 +++++++++++++ 8 files changed, 201 insertions(+), 56 deletions(-) create mode 100644 tests/src/Mod/Part/App/TopoShapeMakeElementRefine.cpp diff --git a/src/Mod/Part/App/TopoShape.cpp b/src/Mod/Part/App/TopoShape.cpp index dd19832eda..eab95306cd 100644 --- a/src/Mod/Part/App/TopoShape.cpp +++ b/src/Mod/Part/App/TopoShape.cpp @@ -4042,14 +4042,15 @@ TopoShape &TopoShape::makeFace(const std::vector &shapes, const char return *this; } -TopoShape &TopoShape::makeRefine(const TopoShape &shape, const char *op, bool no_fail) +TopoShape &TopoShape::makeRefine(const TopoShape &shape, const char *op, RefineFail no_fail) { (void)op; _Shape.Nullify(); if(shape.isNull()) { - if(!no_fail) + if (no_fail == RefineFail::throwException) { HANDLE_NULL_SHAPE; + } return *this; } try { @@ -4057,7 +4058,9 @@ TopoShape &TopoShape::makeRefine(const TopoShape &shape, const char *op, bool no _Shape = mkRefine.Shape(); return *this; }catch (Standard_Failure &) { - if(!no_fail) throw; + if(no_fail == RefineFail::throwException ) { + throw; + } } *this = shape; return *this; diff --git a/src/Mod/Part/App/TopoShape.h b/src/Mod/Part/App/TopoShape.h index 2ed5c3c38b..870e891125 100644 --- a/src/Mod/Part/App/TopoShape.h +++ b/src/Mod/Part/App/TopoShape.h @@ -104,6 +104,13 @@ enum class HistoryTraceType followTypeChange }; +/// Behavior of refines when a problem arises; either leave the shape untouched or throw an exception. +/// This replaces a boolean parameter in the original Toponaming branch by realthunder.. +enum class RefineFail +{ + shapeUntouched, + throwException +}; /** The representation for a CAD Shape */ @@ -589,33 +596,41 @@ public: * @param source: input shape * @param op: optional string to be encoded into topo naming for indicating * the operation - * @param no_fail: if true, throw exception if failed to refine. Or else, - * the shape remains untouched if failed. + * @param no_fail: if throwException, throw exception if failed to refine. Or else, + * if shapeUntouched the shape remains untouched if failed. * * @return The original content of this TopoShape is discarded and replaced * with the refined shape. The function returns the TopoShape * itself as a self reference so that multiple operations can be * carried out for the same shape in the same line of code. */ - TopoShape &makeElementRefine(const TopoShape &source, const char *op=nullptr, bool no_fail=true); + TopoShape& makeElementRefine(const TopoShape& source, + const char* op = nullptr, + RefineFail no_fail = RefineFail::throwException); /** Refine the input shape by merging faces/edges that share the same geometry * * @param source: input shape * @param op: optional string to be encoded into topo naming for indicating * the operation - * @param no_fail: if true, throw exception if failed to refine. Or else, - * the shape remains untouched if failed. + * @param no_fail: if throwException, throw exception if failed to refine. Or else, + * if shapeUntouched the shape remains untouched if failed. * * @return Return a refined shape. The shape itself is not modified */ - TopoShape makeElementRefine(const char *op=nullptr, bool no_fail=true) const { - return TopoShape(Tag,Hasher).makeElementRefine(*this,op,no_fail); + TopoShape makeElementRefine(const char* op = nullptr, + RefineFail no_fail = RefineFail::throwException) const + { + return TopoShape(Tag, Hasher).makeElementRefine(*this, op, no_fail); } - TopoShape& makeRefine(const TopoShape& shape, const char* op = nullptr, bool no_fail = true); - TopoShape makeRefine(const char* op = nullptr, bool no_fail = true) const + TopoShape& makeRefine(const TopoShape& shape, + const char* op = nullptr, + RefineFail no_fail = RefineFail::throwException); + + TopoShape makeRefine(const char* op = nullptr, + RefineFail no_fail = RefineFail::throwException) const { return TopoShape().makeRefine(*this, op, no_fail); } diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp index 29d74e4003..a8b0cc2b68 100644 --- a/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -754,7 +754,7 @@ TopoShape& TopoShape::makeShapeWithElementMap(const TopoDS_Shape& shape, // First, collect names from other shapes that generates or modifies the // new shape - for (auto& pinfo : infos) { + for (auto& pinfo : infos) { // Walk Vertexes, then Edges, then Faces auto& info = *pinfo; for (const auto & incomingShape : shapes) { if (!canMapElement(incomingShape)) { @@ -1517,7 +1517,7 @@ TopoShape& TopoShape::makeElementFace(const std::vector& shapes, class MyRefineMaker : public BRepBuilderAPI_RefineModel { public: - MyRefineMaker(const TopoDS_Shape &s) + explicit MyRefineMaker(const TopoDS_Shape &s) :BRepBuilderAPI_RefineModel(s) {} @@ -1531,14 +1531,18 @@ public: } }; -TopoShape &TopoShape::makeElementRefine(const TopoShape &shape, const char *op, bool no_fail) { - if(shape.isNull()) { - if(!no_fail) +TopoShape& TopoShape::makeElementRefine(const TopoShape& shape, const char* op, RefineFail no_fail) +{ + if (shape.isNull()) { + if (no_fail == RefineFail::throwException) { FC_THROWM(NullShapeException, "Null shape"); + } _Shape.Nullify(); return *this; } - if(!op) op = Part::OpCodes::Refine; + if (!op) { + op = Part::OpCodes::Refine; + } bool closed = shape.isClosed(); try { MyRefineMaker mkRefine(shape.getShape()); @@ -1548,10 +1552,14 @@ TopoShape &TopoShape::makeElementRefine(const TopoShape &shape, const char *op, makeShapeWithElementMap(mkRefine.Shape(), mapper, {shape}, op); // For some reason, refine operation may reverse the solid fixSolidOrientation(); - if (isClosed() == closed) + if (isClosed() == closed) { return *this; - }catch (Standard_Failure &) { - if(!no_fail) throw; + } + } + catch (Standard_Failure&) { + if (no_fail == RefineFail::throwException) { + throw; + } } *this = shape; return *this; @@ -1899,45 +1907,50 @@ TopoShape& TopoShape::makeElementShellFromWires(const std::vector& wi bool TopoShape::fixSolidOrientation() { - if (isNull()) + if (isNull()) { return false; + } if (shapeType() == TopAbs_SOLID) { TopoDS_Solid solid = TopoDS::Solid(_Shape); BRepLib::OrientClosedSolid(solid); - if (solid.IsEqual(_Shape)) + if (solid.IsEqual(_Shape)) { return false; + } setShape(solid, false); return true; } - if (shapeType() == TopAbs_COMPOUND - || shapeType() == TopAbs_COMPSOLID) - { + if (shapeType() == TopAbs_COMPOUND || shapeType() == TopAbs_COMPSOLID) { auto shapes = getSubTopoShapes(); bool touched = false; - for (auto &s : shapes) { - if (s.fixSolidOrientation()) + for (auto& s : shapes) { + if (s.fixSolidOrientation()) { touched = true; + } } - if (!touched) + if (!touched) { return false; + } BRep_Builder builder; if (shapeType() == TopAbs_COMPOUND) { TopoDS_Compound comp; builder.MakeCompound(comp); - for(auto &s : shapes) { - if (!s.isNull()) + for (auto& s : shapes) { + if (!s.isNull()) { builder.Add(comp, s.getShape()); + } } setShape(comp, false); - } else { + } + else { TopoDS_CompSolid comp; builder.MakeCompSolid(comp); - for(auto &s : shapes) { - if (!s.isNull()) + for (auto& s : shapes) { + if (!s.isNull()) { builder.Add(comp, s.getShape()); + } } setShape(comp, false); } diff --git a/src/Mod/Part/App/TopoShapeMapper.cpp b/src/Mod/Part/App/TopoShapeMapper.cpp index 97f571d59d..b72797288b 100644 --- a/src/Mod/Part/App/TopoShapeMapper.cpp +++ b/src/Mod/Part/App/TopoShapeMapper.cpp @@ -1,7 +1,28 @@ -#include +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2002 Jürgen Riegel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + #include #include -#include "PreCompiled.h" #include "TopoShapeMapper.h" #include "Geometry.h" @@ -100,14 +121,25 @@ void ShapeMapper::insert(MappingStatus status, } }; -void GenericShapeMapper::init(const TopoShape &src, const TopoDS_Shape &dst) +void GenericShapeMapper::init(const TopoShape& src, const TopoDS_Shape& dst) { for (TopExp_Explorer exp(dst, TopAbs_FACE); exp.More(); exp.Next()) { - const TopoDS_Shape &dstFace = exp.Current(); - if (src.findShape(dstFace)) + const TopoDS_Shape& dstFace = exp.Current(); + if (src.findShape(dstFace)) { continue; - + } +#if OCC_VERSION_HEX < 0x070800 + struct TopoDS_ShapeHasher + { + std::size_t operator()(const TopoDS_Shape& key) const + { + return key.HashCode(IntegerLast()); + } + }; + std::unordered_map map; +#else std::unordered_map map; +#endif bool found = false; // Try to find a face in the src that shares at least two edges (or one @@ -115,14 +147,14 @@ void GenericShapeMapper::init(const TopoShape &src, const TopoDS_Shape &dst) // TODO: consider degenerative cases of two or more edges on the same line. for (TopExp_Explorer it(dstFace, TopAbs_EDGE); it.More(); it.Next()) { int idx = src.findShape(it.Current()); - if (!idx) + if (!idx) { continue; + } TopoDS_Edge e = TopoDS::Edge(it.Current()); - if(BRep_Tool::IsClosed(e)) - { + if (BRep_Tool::IsClosed(e)) { // closed edge, one face is enough - TopoDS_Shape face = src.findAncestorShape( - src.getSubShape(TopAbs_EDGE,idx), TopAbs_FACE); + TopoDS_Shape face = + src.findAncestorShape(src.getSubShape(TopAbs_EDGE, idx), TopAbs_FACE); if (!face.IsNull()) { this->insert(MappingStatus::Generated, face, dstFace); found = true; @@ -130,27 +162,33 @@ void GenericShapeMapper::init(const TopoShape &src, const TopoDS_Shape &dst) } continue; } - for (auto &face : src.findAncestorsShapes(src.getSubShape(TopAbs_EDGE,idx), TopAbs_FACE)) { - int &cnt = map[face]; + for (auto& face : + src.findAncestorsShapes(src.getSubShape(TopAbs_EDGE, idx), TopAbs_FACE)) { + int& cnt = map[face]; if (++cnt == 2) { this->insert(MappingStatus::Generated, face, dstFace); found = true; break; } - if (found) - break; + if (found) { + break; + } } } - if (found) continue; + if (found) { + continue; + } // if no face matches, try search by geometry surface std::unique_ptr g(Geometry::fromShape(dstFace)); - if (!g) continue; + if (!g) { + continue; + } - for (auto &v : map) { + for (auto& v : map) { std::unique_ptr g2(Geometry::fromShape(v.first)); - if (g2 && g2->isSame(*g,1e-7,1e-12)) { + if (g2 && g2->isSame(*g, 1e-7, 1e-12)) { this->insert(MappingStatus::Generated, v.first, dstFace); break; } diff --git a/src/Mod/Part/App/TopoShapeMapper.h b/src/Mod/Part/App/TopoShapeMapper.h index 69cbcc0791..dd613b2aa5 100644 --- a/src/Mod/Part/App/TopoShapeMapper.h +++ b/src/Mod/Part/App/TopoShapeMapper.h @@ -1,9 +1,32 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2002 Jürgen Riegel * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + #include #include #include #include -#include +#include #include #include "TopoShape.h" diff --git a/tests/src/Mod/Part/App/CMakeLists.txt b/tests/src/Mod/Part/App/CMakeLists.txt index 68f026f412..eaf57d414d 100644 --- a/tests/src/Mod/Part/App/CMakeLists.txt +++ b/tests/src/Mod/Part/App/CMakeLists.txt @@ -15,6 +15,7 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/TopoShape.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeCache.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeExpansion.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeElementRefine.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShapeWithElementMap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMapper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TopoShapeMakeShape.cpp diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp index f1b82a9822..a03ad2bd03 100644 --- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp +++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp @@ -642,7 +642,6 @@ TEST_F(TopoShapeExpansionTest, makeElementShellIntersecting) auto transform {gp_Trsf()}; transform.SetTranslation(gp_Pnt(0.0, 0.0, 0.0), gp_Pnt(0.5, 0.5, 0.0)); cube2.Move(TopLoc_Location(transform)); - // Arrange Part::TopoShape topoShape {cube1}; std::vector shapes; for (const auto& face : topoShape.getSubShapes(TopAbs_FACE)) { @@ -655,7 +654,7 @@ TEST_F(TopoShapeExpansionTest, makeElementShellIntersecting) // Act Part::TopoShape topoShape1 {1L}; topoShape1.makeElementCompound(shapes, "D"); - // Act / Assert + // Assert EXPECT_THROW(topoShape1.makeElementShell(false, nullptr), Base::CADKernelError); } diff --git a/tests/src/Mod/Part/App/TopoShapeMakeElementRefine.cpp b/tests/src/Mod/Part/App/TopoShapeMakeElementRefine.cpp new file mode 100644 index 0000000000..e823ace748 --- /dev/null +++ b/tests/src/Mod/Part/App/TopoShapeMakeElementRefine.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "gtest/gtest.h" + +#include + +#include "PartTestHelpers.h" + +class FeaturePartMakeElementRefineTest: public ::testing::Test, + public PartTestHelpers::PartTestHelperClass +{ +protected: + static void SetUpTestSuite() + { + tests::initApplication(); + } + + + void SetUp() override + { + createTestDoc(); + } + + void TearDown() override + {} +}; + +TEST_F(FeaturePartMakeElementRefineTest, makeElementRefineBoxes) +{ + // Arrange + auto _doc = App::GetApplication().getActiveDocument(); + auto _fuse = dynamic_cast(_doc->addObject("Part::Fuse")); + _fuse->Base.setValue(_boxes[0]); + _fuse->Tool.setValue(_boxes[3]); + // Act + _fuse->execute(); + Part::TopoShape ts = _fuse->Shape.getValue(); + Part::TopoShape refined = ts.makeElementRefine(); + double volume = PartTestHelpers::getVolume(ts.getShape()); + double refinedVolume = PartTestHelpers::getVolume(refined.getShape()); + Base::BoundBox3d bb = ts.getBoundBox(); + // Assert + EXPECT_TRUE(bb.IsValid()); + EXPECT_DOUBLE_EQ(volume, 12.0); + EXPECT_DOUBLE_EQ(refinedVolume, 12.0); // Refine shouldn't change the volume + EXPECT_EQ(ts.countSubElements("Face"), 10); // Two boxes touching each loose one face + EXPECT_EQ(ts.countSubElements("Edge"), 20); // Two boxes touching loose 4 edges + EXPECT_EQ(refined.countSubElements("Face"), 6); // After refining it is one box + EXPECT_EQ(refined.countSubElements("Edge"), 12); // 12 edges in a box + // TODO: Make sure we have an elementMap for the refine. + // Refine doesn't work on compounds, so we're going to need a binary operation or the + // like, and those don't exist yet. Once they do, this test can be expanded +}