Part: bugfix #17468 recursively unpack compounds for boolean fuse (#17469)

* fix #17468  recursively unpack compounds for boolean fuse

* fix and into &&, add ctest case for multifuse with compounds and recursive compounds

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* remove unneeded duplicated code - allow Part::Common to be created with a single object (compound or shape) - will result in Unity and possibly a warning message

* prevent endless loop in case of endless recursive compounds as suggested in chat

* Update src/Mod/Part/App/FeaturePartFuse.cpp

Co-authored-by: Benjamin Nauck <benjamin@nauck.se>

* implemented suggestion by wwmayer

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Benjamin Nauck <benjamin@nauck.se>
This commit is contained in:
Eric Price
2024-11-11 17:51:56 +01:00
committed by GitHub
parent 3d5e4f7cd8
commit 03cb520215
3 changed files with 71 additions and 20 deletions

View File

@@ -102,12 +102,15 @@ App::DocumentObjectExecReturn *MultiFuse::execute()
TopoShape compoundOfArguments;
// if only one source shape, and it is a compound - fuse children of the compound
if (shapes.size() == 1) {
const int maxIterations = 1'000'000; // will trigger "not enough shape objects linked" error below if ever reached
for (int i = 0; shapes.size() == 1 && i < maxIterations; ++i) {
compoundOfArguments = shapes[0];
if (compoundOfArguments.getShape().ShapeType() == TopAbs_COMPOUND) {
shapes.clear();
shapes = compoundOfArguments.getSubTopoShapes();
argumentsAreInCompound = true;
} else {
break;
}
}

View File

@@ -368,23 +368,7 @@ void CmdPartCommon::activated(int iMsg)
std::vector<Gui::SelectionObject> Sel =
getSelection().getSelectionEx(nullptr, App::DocumentObject::getClassTypeId(), Gui::ResolveMode::FollowLink);
//test if selected object is a compound, and if it is, look how many children it has...
std::size_t numShapes = 0;
if (Sel.size() == 1){
numShapes = 1; //to be updated later in code, if
Gui::SelectionObject selobj = Sel[0];
TopoDS_Shape sh = Part::Feature::getShape(selobj.getObject());
if (sh.ShapeType() == TopAbs_COMPOUND) {
numShapes = 0;
TopoDS_Iterator it(sh);
for (; it.More(); it.Next()) {
++numShapes;
}
}
} else {
numShapes = Sel.size();
}
if (numShapes < 2) {
if (Sel.empty()) {
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"),
QObject::tr("Please select two shapes or more. Or, select one compound containing two or more shapes to compute the intersection between."));
return;
@@ -450,12 +434,15 @@ void CmdPartFuse::activated(int iMsg)
numShapes = 1; //to be updated later in code
Gui::SelectionObject selobj = Sel[0];
TopoDS_Shape sh = Part::Feature::getShape(selobj.getObject());
if (sh.ShapeType() == TopAbs_COMPOUND) {
while (numShapes==1 && sh.ShapeType() == TopAbs_COMPOUND) {
numShapes = 0;
TopoDS_Iterator it(sh);
TopoDS_Shape last;
for (; it.More(); it.Next()) {
++numShapes;
last = it.Value();
}
sh = last;
}
} else {
numShapes = Sel.size();

View File

@@ -4,6 +4,7 @@
#include "Mod/Part/App/FeaturePartFuse.h"
#include <src/App/InitApplication.h>
#include "Mod/Part/App/FeatureCompound.h"
#include "PartTestHelpers.h"
@@ -20,12 +21,14 @@ protected:
{
createTestDoc();
_fuse = dynamic_cast<Part::Fuse*>(_doc->addObject("Part::Fuse"));
_multiFuse = dynamic_cast<Part::MultiFuse*>(_doc->addObject("Part::MultiFuse"));
}
void TearDown() override
{}
Part::Fuse* _fuse = nullptr; // NOLINT Can't be private in a test framework
Part::Fuse* _fuse = nullptr; // NOLINT Can't be private in a test framework
Part::MultiFuse* _multiFuse = nullptr; // NOLINT Can't be private in a test framework
};
TEST_F(FeaturePartFuseTest, testIntersecting)
@@ -51,6 +54,64 @@ TEST_F(FeaturePartFuseTest, testIntersecting)
EXPECT_DOUBLE_EQ(bb.MaxZ, 3.0);
}
TEST_F(FeaturePartFuseTest, testCompound)
{
// Arrange
Part::Compound* _compound = nullptr;
_compound = dynamic_cast<Part::Compound*>(_doc->addObject("Part::Compound"));
_compound->Links.setValues({_boxes[0], _boxes[1]});
_multiFuse->Shapes.setValues({_compound});
// Act
_compound->execute();
_multiFuse->execute();
Part::TopoShape ts = _multiFuse->Shape.getValue();
double volume = PartTestHelpers::getVolume(ts.getShape());
Base::BoundBox3d bb = ts.getBoundBox();
// Assert
EXPECT_DOUBLE_EQ(volume, 9.0);
// double check using bounds:
EXPECT_DOUBLE_EQ(bb.MinX, 0.0);
EXPECT_DOUBLE_EQ(bb.MinY, 0.0);
EXPECT_DOUBLE_EQ(bb.MinZ, 0.0);
EXPECT_DOUBLE_EQ(bb.MaxX, 1.0);
EXPECT_DOUBLE_EQ(bb.MaxY, 3.0);
EXPECT_DOUBLE_EQ(bb.MaxZ, 3.0);
}
TEST_F(FeaturePartFuseTest, testRecursiveCompound)
{
// Arrange
Part::Compound* _compound[3] = {nullptr};
int t;
for (t = 0; t < 3; t++) {
_compound[t] = dynamic_cast<Part::Compound*>(_doc->addObject("Part::Compound"));
}
_compound[0]->Links.setValues({_boxes[0], _boxes[1]});
_compound[1]->Links.setValues({_compound[0]});
_compound[2]->Links.setValues({_compound[1]});
_multiFuse->Shapes.setValues({_compound[2]});
// Act
for (t = 0; t < 3; t++) {
_compound[t]->execute();
}
_multiFuse->execute();
Part::TopoShape ts = _multiFuse->Shape.getValue();
double volume = PartTestHelpers::getVolume(ts.getShape());
Base::BoundBox3d bb = ts.getBoundBox();
// Assert
EXPECT_DOUBLE_EQ(volume, 9.0);
// double check using bounds:
EXPECT_DOUBLE_EQ(bb.MinX, 0.0);
EXPECT_DOUBLE_EQ(bb.MinY, 0.0);
EXPECT_DOUBLE_EQ(bb.MinZ, 0.0);
EXPECT_DOUBLE_EQ(bb.MaxX, 1.0);
EXPECT_DOUBLE_EQ(bb.MaxY, 3.0);
EXPECT_DOUBLE_EQ(bb.MaxZ, 3.0);
}
TEST_F(FeaturePartFuseTest, testNonIntersecting)
{
// Arrange