Merge pull request #9594 from wwmayer/issue_9549_new

Part: fixes #9549: Part Fuse not working inside Part container
This commit is contained in:
sliptonic
2023-07-24 10:16:51 -05:00
committed by GitHub
10 changed files with 262 additions and 129 deletions

View File

@@ -182,6 +182,15 @@ Return -1 if element visibility is not supported or element not found, 0 if invi
in a single group, hence only a single return value.</UserDocu>
</Documentation>
</Methode>
<Methode Name="getParent">
<Documentation>
<UserDocu>Returns the group the object is in or None if it is not part of a group.
Note that an object can only be in a single group, hence only a single return
value.
The parent can be a simple group as with getParentGroup() or a
GeoFeature group as with getParentGeoFeatureGroup().</UserDocu>
</Documentation>
</Methode>
<Methode Name="getPathsByOutList">
<Documentation>
<UserDocu>Get all paths from this object to another object following the OutList.</UserDocu>

View File

@@ -673,6 +673,24 @@ PyObject* DocumentObjectPy::getParentGeoFeatureGroup(PyObject *args)
}
}
PyObject* DocumentObjectPy::getParent(PyObject *args)
{
if (!PyArg_ParseTuple(args, ""))
return nullptr;
try {
auto grp = getDocumentObjectPtr()->getFirstParent();
if(!grp) {
Py_INCREF(Py_None);
return Py_None;
}
return grp->getPyObject();
}
catch (const Base::Exception& e) {
throw Py::RuntimeError(e.what());
}
}
Py::Boolean DocumentObjectPy::getMustExecute() const
{
try {

View File

@@ -291,6 +291,30 @@ std::string Base::Tools::escapeEncodeFilename(const std::string& s)
return result;
}
std::string Base::Tools::quoted(const char* name)
{
std::stringstream str;
str << "\"" << name << "\"";
return str.str();
}
std::string Base::Tools::quoted(const std::string& name)
{
std::stringstream str;
str << "\"" << name << "\"";
return str.str();
}
std::string Base::Tools::joinList(const std::vector<std::string>& vec,
const std::string& sep)
{
std::stringstream str;
for (const auto& it : vec) {
str << it << sep;
}
return str.str();
}
// ----------------------------------------------------------------------------
using namespace Base;

View File

@@ -274,6 +274,29 @@ struct BaseExport Tools
static inline QString fromStdString(const std::string & s) {
return QString::fromUtf8(s.c_str(), static_cast<int>(s.size()));
}
/**
* @brief quoted Creates a quoted string.
* @param String to be quoted.
* @return A quoted std::string.
*/
static std::string quoted(const char*);
/**
* @brief quoted Creates a quoted string.
* @param String to be quoted.
* @return A quoted std::string.
*/
static std::string quoted(const std::string&);
/**
* @brief joinList
* Join the vector of strings \a vec using the separator \a sep
* @param vec
* @param sep
* @return
*/
static std::string joinList(const std::vector<std::string>& vec,
const std::string& sep = ", ");
};

View File

@@ -0,0 +1,107 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * Copyright (c) 2023 Werner Mayer <wmayer[at]users.sourceforge.net> *
# * *
# * 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/>. *
# * *
# ***************************************************************************
__title__ = "BOPTools.BOPFeatures module"
__author__ = "Werner Mayer"
__url__ = "http://www.freecad.org"
__doc__ = "Helper class to create the features for Boolean operations."
import FreeCAD
import Part
class BOPFeatures:
def __init__(self, doc):
self.doc = doc
def make_section(self, inputNames):
obj = self.doc.addObject("Part::Section", "Section")
obj.Base = self.doc.getObject(inputNames[0])
obj.Tool = self.doc.getObject(inputNames[1])
self.copy_visual_attributes(obj, obj.Base)
target = self.move_input_objects([obj.Base, obj.Tool])
if target:
target.addObject(obj)
return obj
def make_cut(self, inputNames):
obj = self.doc.addObject("Part::Cut", "Cut")
obj.Base = self.doc.getObject(inputNames[0])
obj.Tool = self.doc.getObject(inputNames[1])
self.copy_visual_attributes(obj, obj.Base)
target = self.move_input_objects([obj.Base, obj.Tool])
if target:
target.addObject(obj)
return obj
def make_common(self, inputNames):
obj = self.doc.addObject("Part::Common", "Common")
obj.Base = self.doc.getObject(inputNames[0])
obj.Tool = self.doc.getObject(inputNames[1])
self.copy_visual_attributes(obj, obj.Base)
target = self.move_input_objects([obj.Base, obj.Tool])
if target:
target.addObject(obj)
return obj
def make_multi_common(self, inputNames):
obj = self.doc.addObject("Part::MultiCommon", "Common")
obj.Shapes = [self.doc.getObject(name) for name in inputNames]
self.copy_visual_attributes(obj, obj.Shapes[0])
target = self.move_input_objects(obj.Shapes)
if target:
target.addObject(obj)
return obj
def make_fuse(self, inputNames):
obj = self.doc.addObject("Part::Fuse", "Fusion")
obj.Base = self.doc.getObject(inputNames[0])
obj.Tool = self.doc.getObject(inputNames[1])
self.copy_visual_attributes(obj, obj.Base)
target = self.move_input_objects([obj.Base, obj.Tool])
if target:
target.addObject(obj)
return obj
def make_multi_fuse(self, inputNames):
obj = self.doc.addObject("Part::MultiFuse", "Fusion")
obj.Shapes = [self.doc.getObject(name) for name in inputNames]
self.copy_visual_attributes(obj, obj.Shapes[0])
target = self.move_input_objects(obj.Shapes)
if target:
target.addObject(obj)
return obj
def move_input_objects(self, objects):
targetGroup = None
for obj in objects:
obj.Visibility = False
parent = obj.getParent()
if parent:
parent.removeObject(obj)
targetGroup = parent
return targetGroup
def copy_visual_attributes(self, target, source):
if target.ViewObject:
target.ViewObject.ShapeColor = source.ViewObject.ShapeColor
target.ViewObject.DisplayMode = source.ViewObject.DisplayMode

View File

@@ -44,6 +44,7 @@ endif(BUILD_GUI)
set(BOPTools_Scripts
BOPTools/__init__.py
BOPTools/BOPFeatures.py
BOPTools/GeneralFuseResult.py
BOPTools/JoinAPI.py
BOPTools/JoinFeatures.py

View File

@@ -316,6 +316,7 @@ void CmdPartCut::activated(int iMsg)
}
bool askUser = false;
std::vector<std::string> names;
for (std::vector<Gui::SelectionObject>::iterator it = Sel.begin(); it != Sel.end(); ++it) {
App::DocumentObject* obj = it->getObject();
const TopoDS_Shape& shape = Part::Feature::getShape(obj);
@@ -327,34 +328,14 @@ void CmdPartCut::activated(int iMsg)
return;
askUser = true;
}
}
std::string FeatName = getUniqueObjectName("Cut");
names.push_back(Base::Tools::quoted(it->getFeatName()));
}
openCommand(QT_TRANSLATE_NOOP("Command", "Part Cut"));
doCommand(Doc,"App.activeDocument().addObject(\"Part::Cut\",\"%s\")",FeatName.c_str());
doCommand(Doc,"App.activeDocument().%s.Base = App.activeDocument().%s",FeatName.c_str(),Sel[0].getFeatName());
doCommand(Doc,"App.activeDocument().%s.Tool = App.activeDocument().%s",FeatName.c_str(),Sel[1].getFeatName());
// hide the input objects and remove them from the parent group
App::DocumentObjectGroup* targetGroup = nullptr;
for (std::vector<Gui::SelectionObject>::iterator it = Sel.begin(); it != Sel.end(); ++it) {
doCommand(Gui,"Gui.activeDocument().%s.Visibility=False",it->getFeatName());
App::DocumentObjectGroup* group = it->getObject()->getGroup();
if (group) {
targetGroup = group;
doCommand(Doc, "App.activeDocument().%s.removeObject(App.activeDocument().%s)",
group->getNameInDocument(), it->getFeatName());
}
}
if (targetGroup) {
doCommand(Doc, "App.activeDocument().%s.addObject(App.activeDocument().%s)",
targetGroup->getNameInDocument(), FeatName.c_str());
}
copyVisual(FeatName.c_str(), "ShapeColor", Sel[0].getFeatName());
copyVisual(FeatName.c_str(), "DisplayMode", Sel[0].getFeatName());
doCommand(Doc, "from BOPTools import BOPFeatures");
doCommand(Doc, "bp = BOPFeatures.BOPFeatures(App.activeDocument())");
doCommand(Doc, "bp.make_cut([%s])", Base::Tools::joinList(names).c_str());
updateActive();
commitCommand();
}
@@ -411,11 +392,7 @@ void CmdPartCommon::activated(int iMsg)
}
bool askUser = false;
std::string FeatName = getUniqueObjectName("Common");
std::stringstream str;
std::vector<Gui::SelectionObject> partObjects;
str << "App.activeDocument()." << FeatName << ".Shapes = [";
std::vector<std::string> names;
for (std::vector<Gui::SelectionObject>::iterator it = Sel.begin(); it != Sel.end(); ++it) {
App::DocumentObject* obj = it->getObject();
const TopoDS_Shape& shape = Part::Feature::getShape(obj);
@@ -427,34 +404,14 @@ void CmdPartCommon::activated(int iMsg)
return;
askUser = true;
}
str << "App.activeDocument()." << it->getFeatName() << ",";
partObjects.push_back(*it);
names.push_back(Base::Tools::quoted(it->getFeatName()));
}
str << "]";
openCommand(QT_TRANSLATE_NOOP("Command", "Common"));
doCommand(Doc,"App.activeDocument().addObject(\"Part::MultiCommon\",\"%s\")",FeatName.c_str());
runCommand(Doc,str.str().c_str());
// hide the input objects and remove them from the parent group
App::DocumentObjectGroup* targetGroup = nullptr;
for (std::vector<Gui::SelectionObject>::iterator it = partObjects.begin(); it != partObjects.end(); ++it) {
doCommand(Gui,"Gui.activeDocument().%s.Visibility=False",it->getFeatName());
App::DocumentObjectGroup* group = it->getObject()->getGroup();
if (group) {
targetGroup = group;
doCommand(Doc, "App.activeDocument().%s.removeObject(App.activeDocument().%s)",
group->getNameInDocument(), it->getFeatName());
}
}
if (targetGroup) {
doCommand(Doc, "App.activeDocument().%s.addObject(App.activeDocument().%s)",
targetGroup->getNameInDocument(), FeatName.c_str());
}
copyVisual(FeatName.c_str(), "ShapeColor", partObjects.front().getFeatName());
copyVisual(FeatName.c_str(), "DisplayMode", partObjects.front().getFeatName());
doCommand(Doc, "from BOPTools import BOPFeatures");
doCommand(Doc, "bp = BOPFeatures.BOPFeatures(App.activeDocument())");
doCommand(Doc, "bp.make_multi_common([%s])", Base::Tools::joinList(names).c_str());
updateActive();
commitCommand();
}
@@ -511,11 +468,7 @@ void CmdPartFuse::activated(int iMsg)
}
bool askUser = false;
std::string FeatName = getUniqueObjectName("Fusion");
std::stringstream str;
std::vector<Gui::SelectionObject> partObjects;
str << "App.activeDocument()." << FeatName << ".Shapes = [";
std::vector<std::string> names;
for (std::vector<Gui::SelectionObject>::iterator it = Sel.begin(); it != Sel.end(); ++it) {
App::DocumentObject* obj = it->getObject();
const TopoDS_Shape& shape = Part::Feature::getShape(obj);
@@ -527,34 +480,14 @@ void CmdPartFuse::activated(int iMsg)
return;
askUser = true;
}
str << "App.activeDocument()." << it->getFeatName() << ",";
partObjects.push_back(*it);
names.push_back(Base::Tools::quoted(it->getFeatName()));
}
str << "]";
openCommand(QT_TRANSLATE_NOOP("Command", "Fusion"));
doCommand(Doc,"App.activeDocument().addObject(\"Part::MultiFuse\",\"%s\")",FeatName.c_str());
runCommand(Doc,str.str().c_str());
// hide the input objects and remove them from the parent group
App::DocumentObjectGroup* targetGroup = nullptr;
for (std::vector<Gui::SelectionObject>::iterator it = partObjects.begin(); it != partObjects.end(); ++it) {
doCommand(Gui,"Gui.activeDocument().%s.Visibility=False",it->getFeatName());
App::DocumentObjectGroup* group = it->getObject()->getGroup();
if (group) {
targetGroup = group;
doCommand(Doc, "App.activeDocument().%s.removeObject(App.activeDocument().%s)",
group->getNameInDocument(), it->getFeatName());
}
}
if (targetGroup) {
doCommand(Doc, "App.activeDocument().%s.addObject(App.activeDocument().%s)",
targetGroup->getNameInDocument(), FeatName.c_str());
}
copyVisual(FeatName.c_str(), "ShapeColor", partObjects.front().getFeatName());
copyVisual(FeatName.c_str(), "DisplayMode", partObjects.front().getFeatName());
doCommand(Doc, "from BOPTools import BOPFeatures");
doCommand(Doc, "bp = BOPFeatures.BOPFeatures(App.activeDocument())");
doCommand(Doc, "bp.make_multi_fuse([%s])", Base::Tools::joinList(names).c_str());
updateActive();
commitCommand();
}

View File

@@ -29,6 +29,7 @@
#endif
#include <Base/Exception.h>
#include <Base/Tools.h>
#include <App/Application.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
@@ -405,7 +406,7 @@ void DlgBooleanOperation::accept()
return;
}
std::string type, objName;
std::string method;
App::DocumentObject* obj1 = activeDoc->getObject(shapeOne.c_str());
App::DocumentObject* obj2 = activeDoc->getObject(shapeTwo.c_str());
if (!obj1 || !obj2) {
@@ -421,8 +422,7 @@ void DlgBooleanOperation::accept()
tr("Performing union on non-solids is not possible"));
return;
}
type = "Part::Fuse";
objName = activeDoc->getUniqueObjectName("Fusion");
method = "make_fuse";
}
else if (ui->interButton->isChecked()) {
if (!hasSolids(obj1) || !hasSolids(obj2)) {
@@ -430,8 +430,7 @@ void DlgBooleanOperation::accept()
tr("Performing intersection on non-solids is not possible"));
return;
}
type = "Part::Common";
objName = activeDoc->getUniqueObjectName("Common");
method = "make_common";
}
else if (ui->diffButton->isChecked()) {
if (!hasSolids(obj1) || !hasSolids(obj2)) {
@@ -439,55 +438,24 @@ void DlgBooleanOperation::accept()
tr("Performing difference on non-solids is not possible"));
return;
}
type = "Part::Cut";
objName = activeDoc->getUniqueObjectName("Cut");
method = "make_cut";
}
else if (ui->sectionButton->isChecked()) {
type = "Part::Section";
objName = activeDoc->getUniqueObjectName("Section");
method = "make_section";
}
try {
Gui::WaitCursor wc;
activeDoc->openTransaction("Boolean operation");
std::vector<std::string> names;
names.push_back(Base::Tools::quoted(shapeOne.c_str()));
names.push_back(Base::Tools::quoted(shapeTwo.c_str()));
Gui::Command::doCommand(Gui::Command::Doc,
"App.activeDocument().addObject(\"%s\",\"%s\")",
type.c_str(), objName.c_str());
"from BOPTools import BOPFeatures");
Gui::Command::doCommand(Gui::Command::Doc,
"App.activeDocument().%s.Base = App.activeDocument().%s",
objName.c_str(),shapeOne.c_str());
"bp = BOPFeatures.BOPFeatures(App.activeDocument())");
Gui::Command::doCommand(Gui::Command::Doc,
"App.activeDocument().%s.Tool = App.activeDocument().%s",
objName.c_str(),shapeTwo.c_str());
Gui::Command::doCommand(Gui::Command::Gui,
"Gui.activeDocument().hide(\"%s\")",shapeOne.c_str());
Gui::Command::doCommand(Gui::Command::Gui,
"Gui.activeDocument().hide(\"%s\")",shapeTwo.c_str());
// add/remove fromgroup if needed
App::DocumentObjectGroup* targetGroup = nullptr;
App::DocumentObjectGroup* group1 = obj1->getGroup();
if (group1) {
targetGroup = group1;
Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.removeObject(App.activeDocument().%s)",
group1->getNameInDocument(), obj1->getNameInDocument());
}
App::DocumentObjectGroup* group2 = obj2->getGroup();
if (group2) {
targetGroup = group2;
Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.removeObject(App.activeDocument().%s)",
group2->getNameInDocument(), obj2->getNameInDocument());
}
if (targetGroup) {
Gui::Command::doCommand(Gui::Command::Doc, "App.activeDocument().%s.addObject(App.activeDocument().%s)",
targetGroup->getNameInDocument(), objName.c_str());
}
Gui::Command::copyVisual(objName.c_str(), "ShapeColor", shapeOne.c_str());
Gui::Command::copyVisual(objName.c_str(), "DisplayMode", shapeOne.c_str());
"bp.%s([%s])", method.c_str(), Base::Tools::joinList(names).c_str());
activeDoc->commitTransaction();
activeDoc->recompute();
}

View File

@@ -831,3 +831,43 @@ class PartTestShapeFix(unittest.TestCase):
fix.fixGap3d(1, False)
fix.fixGap2d(1, False)
fix.fixTails()
class PartBOPTestContainer(unittest.TestCase):
def setUp(self):
self.Doc = FreeCAD.newDocument()
def testMakeFuse(self):
box = self.Doc.addObject("Part::Box", "Box")
cyl = self.Doc.addObject("Part::Cylinder", "Cylinder")
part = self.Doc.addObject("App::Part", "Part")
part.addObject(box)
part.addObject(cyl)
from BOPTools import BOPFeatures
bp = BOPFeatures.BOPFeatures(self.Doc)
fuse = bp.make_multi_fuse([cyl.Name, box.Name])
self.assertEqual(part, fuse.getParent())
def testMakeCut(self):
box = self.Doc.addObject("Part::Box", "Box")
cyl = self.Doc.addObject("Part::Cylinder", "Cylinder")
part = self.Doc.addObject("App::Part", "Part")
part.addObject(box)
part.addObject(cyl)
from BOPTools import BOPFeatures
bp = BOPFeatures.BOPFeatures(self.Doc)
fuse = bp.make_cut([cyl.Name, box.Name])
self.assertEqual(part, fuse.getParent())
def testMakeCommon(self):
box = self.Doc.addObject("Part::Box", "Box")
cyl = self.Doc.addObject("Part::Cylinder", "Cylinder")
part = self.Doc.addObject("App::Part", "Part")
part.addObject(box)
part.addObject(cyl)
from BOPTools import BOPFeatures
bp = BOPFeatures.BOPFeatures(self.Doc)
fuse = bp.make_multi_common([cyl.Name, box.Name])
self.assertEqual(part, fuse.getParent())
def tearDown(self):
FreeCAD.closeDocument(self.Doc.Name)

View File

@@ -40,3 +40,13 @@ TEST(BaseToolsSuite, TestUniqueName8)
{
EXPECT_EQ(Base::Tools::getUniqueName("Body12345", {"Body"}, 3), "Body12346");
}
TEST(BaseToolsSuite, TestQuote)
{
EXPECT_EQ(Base::Tools::quoted("Test"), "\"Test\"");
}
TEST(BaseToolsSuite, TestJoinList)
{
EXPECT_EQ(Base::Tools::joinList({"AB", "CD"}), "AB, CD, ");
}