Part: Allow deleting children recursively

Add feature to allow deletion of all children of compounds and booleans.
This commit is contained in:
tetektoza
2025-08-19 22:39:42 +02:00
committed by Chris Hennes
parent bd3c1647d6
commit ec7bb3ddae
3 changed files with 160 additions and 45 deletions

View File

@@ -44,7 +44,7 @@ using namespace Gui;
namespace {
// helper function to recursively delete group contents while respecting view provider onDelete methods
void deleteGroupContentsRecursively(App::GroupExtension* group, ViewProvider* groupViewProvider) {
void deleteGroupContentsRecursively(App::GroupExtension* group) {
if (!group) {
return;
}
@@ -59,9 +59,7 @@ namespace {
// if the child is a group, recursively delete its contents first
if (child->hasExtension(App::GroupExtension::getExtensionClassTypeId())) {
auto* childGroup = child->getExtensionByType<App::GroupExtension>();
Gui::Document* guiDoc = Application::Instance->getDocument(child->getDocument());
ViewProvider* childVP = guiDoc ? guiDoc->getViewProvider(child) : nullptr;
deleteGroupContentsRecursively(childGroup, childVP);
deleteGroupContentsRecursively(childGroup);
}
Gui::Document* guiDoc = Application::Instance->getDocument(child->getDocument());
@@ -69,7 +67,7 @@ namespace {
ViewProvider* vp = guiDoc->getViewProvider(child);
if (vp) {
// give group_recursive_deletion marker to the VP to mark that the deletion
// is supposed to delete all of it's children
// is supposed to delete all of its children
std::vector<std::string> groupDeletionMarker = {"group_recursive_deletion"};
bool shouldDelete = vp->onDelete(groupDeletionMarker);
@@ -217,7 +215,7 @@ void ViewProviderGroupExtension::extensionHide() {
ViewProviderExtension::extensionHide();
}
bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::string >& subNames) {
bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::string >&) {
auto* group = getExtendedViewProvider()->getObject()->getExtensionByType<App::GroupExtension>();
@@ -228,13 +226,9 @@ bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::strin
return true;
}
std::vector<App::DocumentObject*> allDescendants;
if (getExtendedViewProvider()->getObject()->isDerivedFrom<App::DocumentObjectGroup>()) {
auto* docGroup = static_cast<App::DocumentObjectGroup*>(getExtendedViewProvider()->getObject());
allDescendants = docGroup->getAllChildren();
} else {
allDescendants = directChildren;
}
const auto* docGroup =
freecad_cast<App::DocumentObjectGroup*>(getExtendedViewProvider()->getObject());
auto allDescendants = docGroup ? docGroup->getAllChildren() : directChildren;
QString message;
if (allDescendants.size() == directChildren.size()) {
@@ -254,16 +248,17 @@ bool ViewProviderGroupExtension::extensionOnDelete(const std::vector< std::strin
QObject::tr("Delete group contents recursively?"),
message,
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::Yes
QMessageBox::No
);
if (choice == QMessageBox::Cancel) {
// don't delete anything if user has cancelled
return false;
}
else if (choice == QMessageBox::Yes) {
if (choice == QMessageBox::Yes) {
// delete all of the children recursively and call their viewprovider method
deleteGroupContentsRecursively(group, getExtendedViewProvider());
deleteGroupContentsRecursively(group);
}
// if user has specified "No" then delete the group but move children to the parent or root

View File

@@ -23,10 +23,11 @@
# include <TopExp.hxx>
# include <TopTools_IndexedMapOfShape.hxx>
# include <QMessageBox>
#include <App/Document.h>
#include <Gui/Application.h>
#include <Gui/BitmapFactory.h>
#include <Gui/MainWindow.h>
#include <Mod/Part/App/FeaturePartCommon.h>
#include <Mod/Part/App/FeaturePartFuse.h>
@@ -35,6 +36,65 @@
using namespace PartGui;
namespace {
// helper function for Boolean operation deletion with user confirmation
bool handleBooleanDeletion(const std::vector<std::string>& subNames,
const QString& operationName,
const QString& objectLabel,
const std::vector<App::DocumentObject*>& inputObjects,
const QString& inputDescription)
{
if (inputObjects.empty()) {
return true;
}
// if we are in group deletion context it means user is deleting group that contains
// this boolean and they have accepted to delete all of the group objects recursively
// so delete everything automatically
bool inGroupDeletion = !subNames.empty() && subNames[0] == "group_recursive_deletion";
if (inGroupDeletion) {
for (auto obj : inputObjects) {
if (obj && obj->isAttachedToDocument() && !obj->isRemoving()) {
obj->getDocument()->removeObject(obj->getNameInDocument());
}
}
return true;
}
QMessageBox::StandardButton choice = QMessageBox::question(
Gui::getMainWindow(),
QObject::tr("Delete %1 content?").arg(operationName),
QObject::tr("The %1 '%2' has %3. Do you want to delete them as well?")
.arg(operationName.toLower())
.arg(objectLabel)
.arg(inputDescription),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::No
);
if (choice == QMessageBox::Cancel) {
return false;
}
if (choice == QMessageBox::Yes) {
for (auto obj : inputObjects) {
if (obj && obj->isAttachedToDocument() && !obj->isRemoving()) {
obj->getDocument()->removeObject(obj->getNameInDocument());
}
}
return true;
}
for (auto obj : inputObjects) {
if (obj) {
Gui::Application::Instance->showViewProvider(obj);
}
}
return true;
}
}
PROPERTY_SOURCE(PartGui::ViewProviderBoolean,PartGui::ViewProviderPart)
ViewProviderBoolean::ViewProviderBoolean() = default;
@@ -138,19 +198,37 @@ void ViewProviderBoolean::updateData(const App::Property* prop)
}
}
bool ViewProviderBoolean::onDelete(const std::vector<std::string> &)
bool ViewProviderBoolean::onDelete(const std::vector<std::string> &subNames)
{
// get the input shapes
Part::Boolean* pBool = getObject<Part::Boolean>();
App::DocumentObject *pBase = pBool->Base.getValue();
App::DocumentObject *pTool = pBool->Tool.getValue();
if (pBase)
Gui::Application::Instance->showViewProvider(pBase);
if (pTool)
Gui::Application::Instance->showViewProvider(pTool);
// Prepare input objects list and description
std::vector<App::DocumentObject*> inputObjects;
if (pBase) {
inputObjects.push_back(pBase);
}
return true;
if (pTool) {
inputObjects.push_back(pTool);
}
QString inputDescription;
if (pBase && pTool) {
inputDescription = QObject::tr("base and tool objects");
} else if (pBase) {
inputDescription = QObject::tr("base object");
} else if (pTool) {
inputDescription = QObject::tr("tool object");
}
return handleBooleanDeletion(subNames,
QObject::tr("Boolean operation"),
QString::fromUtf8(pBool->Label.getValue()),
inputObjects,
inputDescription);
}
PROPERTY_SOURCE(PartGui::ViewProviderMultiFuse,PartGui::ViewProviderPart)
@@ -229,18 +307,19 @@ void ViewProviderMultiFuse::updateData(const App::Property* prop)
}
}
bool ViewProviderMultiFuse::onDelete(const std::vector<std::string> &)
bool ViewProviderMultiFuse::onDelete(const std::vector<std::string> &subNames)
{
// get the input shapes
Part::MultiFuse* pBool = getObject<Part::MultiFuse>();
std::vector<App::DocumentObject*> pShapes = pBool->Shapes.getValues();
for (auto it : pShapes) {
if (it) {
Gui::Application::Instance->showViewProvider(it);
}
}
return true;
QString inputDescription = QObject::tr("%1 input objects").arg(pShapes.size());
return handleBooleanDeletion(subNames,
QObject::tr("Fusion"),
QString::fromUtf8(pBool->Label.getValue()),
pShapes,
inputDescription);
}
bool ViewProviderMultiFuse::canDragObjects() const
@@ -364,18 +443,19 @@ void ViewProviderMultiCommon::updateData(const App::Property* prop)
}
}
bool ViewProviderMultiCommon::onDelete(const std::vector<std::string> &)
bool ViewProviderMultiCommon::onDelete(const std::vector<std::string> &subNames)
{
// get the input shapes
Part::MultiCommon* pBool = getObject<Part::MultiCommon>();
std::vector<App::DocumentObject*> pShapes = pBool->Shapes.getValues();
for (auto it : pShapes) {
if (it) {
Gui::Application::Instance->showViewProvider(it);
}
}
return true;
QString inputDescription = QObject::tr("%1 input objects").arg(pShapes.size());
return handleBooleanDeletion(subNames,
QObject::tr("Intersection"),
QString::fromUtf8(pBool->Label.getValue()),
pShapes,
inputDescription);
}
bool ViewProviderMultiCommon::canDragObjects() const

View File

@@ -23,9 +23,11 @@
# include <TopExp.hxx>
# include <TopTools_IndexedMapOfShape.hxx>
# include <QMessageBox>
#include <App/Document.h>
#include <Gui/Application.h>
#include <Gui/MainWindow.h>
#include <Mod/Part/App/FeatureCompound.h>
#include "ViewProviderCompound.h"
@@ -47,14 +49,52 @@ std::vector<App::DocumentObject*> ViewProviderCompound::claimChildren() const
return getObject<Part::Compound>()->Links.getValues();
}
bool ViewProviderCompound::onDelete(const std::vector<std::string> &)
bool ViewProviderCompound::onDelete(const std::vector<std::string> &subNames)
{
// get the input shapes
Part::Compound* pComp = getObject<Part::Compound>();
std::vector<App::DocumentObject*> pLinks = pComp->Links.getValues();
for (auto pLink : pLinks) {
if (pLink)
Gui::Application::Instance->showViewProvider(pLink);
if (!pLinks.empty()) {
// check group deletion marker -> it means group called this VP to delete it's content
// so delete everything recursively
bool inGroupDeletion = !subNames.empty() && subNames[0] == "group_recursive_deletion";
if (inGroupDeletion) {
for (auto pLink : pLinks) {
if (pLink && pLink->isAttachedToDocument() && !pLink->isRemoving()) {
pLink->getDocument()->removeObject(pLink->getNameInDocument());
}
}
return true;
}
QMessageBox::StandardButton choice = QMessageBox::question(
Gui::getMainWindow(),
QObject::tr("Delete compound content?"),
QObject::tr("The compound '%1' has %2 child objects. Do you want to delete them as well?")
.arg(QString::fromUtf8(pComp->Label.getValue()))
.arg(pLinks.size()),
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel,
QMessageBox::No
);
if (choice == QMessageBox::Cancel) {
return false;
}
if (choice == QMessageBox::Yes) {
for (auto pLink : pLinks) {
if (pLink && pLink->isAttachedToDocument() && !pLink->isRemoving()) {
pLink->getDocument()->removeObject(pLink->getNameInDocument());
}
}
return true;
}
for (auto pLink : pLinks) {
if (pLink)
Gui::Application::Instance->showViewProvider(pLink);
}
}
return true;