Part: Fix regressions in MultiCommon boolean operation

This commit addresses two regressions in the MultiCommon feature:

1. Computation Logic: Fixed an issue where the common operation was
   calculated as the intersection of the first shape with the union of
   the rest (the default behavior of makeElementBoolean). It now
   correctly computes the intersection of all shapes sequentially.

2. Compound Handling: Added logic to expand a single compound input
   into its constituent shapes. Previously, a compound was treated
   as a single entity, leading to incorrect intersection results.

To maintain backward compatibility, a hidden 'Behavior' property is
introduced. This ensures that documents created in FreeCAD 1.0, which
rely on the previous behavior, continue to render as originally
intended while new objects use the corrected logic.
This commit is contained in:
Kacper Donat
2026-01-18 20:21:41 +01:00
committed by Chris Hennes
parent d3d6459484
commit 4dda92e599
2 changed files with 63 additions and 4 deletions

View File

@@ -34,6 +34,8 @@
#include "TopoShapeOpCode.h"
#include "modelRefine.h"
#include <Base/ProgramVersion.h>
using namespace Part;
@@ -45,7 +47,6 @@ extern bool getRefineModelParameter();
PROPERTY_SOURCE(Part::Common, Part::Boolean)
Common::Common() = default;
const char* Common::opCode() const
@@ -63,6 +64,7 @@ BRepAlgoAPI_BooleanOperation* Common::makeOperation(const TopoDS_Shape& base, co
PROPERTY_SOURCE(Part::MultiCommon, Part::Feature)
const char* MultiCommon::BehaviorEnums[] = {"CommonOfAllShapes", "CommonOfFirstAndRest", nullptr};
MultiCommon::MultiCommon()
{
@@ -85,17 +87,38 @@ MultiCommon::MultiCommon()
"Refine shape (clean up redundant edges) after this boolean operation"
);
ADD_PROPERTY_TYPE(
Behavior,
(CommonOfAllShapes),
"Compatibility",
App::Prop_Hidden,
"Determines how the common operation is computed: either as the intersection of all "
"shapes, or the intersection of the first shape with all remaining shapes (for "
"compatibility with FreeCAD 1.0)."
);
Behavior.setEnums(BehaviorEnums);
this->Refine.setValue(getRefineModelParameter());
}
short MultiCommon::mustExecute() const
{
if (Shapes.isTouched()) {
if (Shapes.isTouched() || Behavior.isTouched()) {
return 1;
}
return 0;
}
void MultiCommon::Restore(Base::XMLReader& reader)
{
Feature::Restore(reader);
// For 1.0 and 1.0 only the order was common of first and the rest due to a bug
if (Base::getVersion(reader.ProgramVersion) == Base::Version::v1_0) {
Behavior.setValue(CommonOfFirstAndRest);
}
}
App::DocumentObjectExecReturn* MultiCommon::execute()
{
std::vector<TopoShape> shapes;
@@ -107,8 +130,31 @@ App::DocumentObjectExecReturn* MultiCommon::execute()
shapes.push_back(sh);
}
TopoShape res {0};
res.makeElementBoolean(Part::OpCodes::Common, shapes);
TopoShape res;
if (Behavior.getValue() == CommonOfAllShapes) {
// special case - if there is only one argument, and it is compound - expand it
if (shapes.size() == 1) {
TopoShape shape = shapes.front();
if (shape.shapeType() == TopAbs_COMPOUND) {
shapes.clear();
std::ranges::copy(shape.getSubTopoShapes(), std::back_inserter(shapes));
}
}
res = shapes.front();
// to achieve common of all shapes, we need to do it one shape at a time
for (const auto& tool : shapes) {
res = res.makeElementBoolean(OpCodes::Common, {res, tool});
}
}
else {
res = TopoShape(0);
res.makeElementBoolean(OpCodes::Common, shapes);
}
if (res.isNull()) {
throw Base::RuntimeError("Resulting shape is null");
}

View File

@@ -49,6 +49,12 @@ protected:
//@}
};
enum CommonBehavior
{
CommonOfAllShapes,
CommonOfFirstAndRest,
};
class PartExport MultiCommon: public Part::Feature
{
PROPERTY_HEADER_WITH_OVERRIDE(Part::MultiCommon);
@@ -59,6 +65,7 @@ public:
App::PropertyLinkList Shapes;
PropertyShapeHistory History;
App::PropertyBool Refine;
App::PropertyEnumeration Behavior;
/** @name methods override feature */
//@{
@@ -66,11 +73,17 @@ public:
App::DocumentObjectExecReturn* execute() override;
short mustExecute() const override;
//@}
void Restore(Base::XMLReader& reader) override;
/// returns the type name of the ViewProvider
const char* getViewProviderName() const override
{
return "PartGui::ViewProviderMultiCommon";
}
private:
static const char* BehaviorEnums[];
};
} // namespace Part