Add Test for MakeElementRefine
This commit is contained in:
@@ -4042,14 +4042,15 @@ TopoShape &TopoShape::makeFace(const std::vector<TopoShape> &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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<TopoShape>& 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<TopoShape>& 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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
#include <TopoDS.hxx>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
#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<TopoDS_Shape, int, TopoDS_ShapeHasher> map;
|
||||
#else
|
||||
std::unordered_map<TopoDS_Shape, int> 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<Geometry> g(Geometry::fromShape(dstFace));
|
||||
if (!g) continue;
|
||||
if (!g) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (auto &v : map) {
|
||||
for (auto& v : map) {
|
||||
std::unique_ptr<Geometry> 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;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2002 Jürgen Riegel <juergen.riegel@web.de> *
|
||||
* *
|
||||
* 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 *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <Standard_Version.hxx>
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include "TopoShape.h"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Part::TopoShape> 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);
|
||||
}
|
||||
|
||||
|
||||
53
tests/src/Mod/Part/App/TopoShapeMakeElementRefine.cpp
Normal file
53
tests/src/Mod/Part/App/TopoShapeMakeElementRefine.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include <src/App/InitApplication.h>
|
||||
|
||||
#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<Part::Fuse*>(_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
|
||||
}
|
||||
Reference in New Issue
Block a user