/*************************************************************************** * Copyright (C) 2015 Alexander Golubev (Fat-Zer) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "TaskFeaturePick.h" #include "Utils.h" #include "WorkflowManager.h" //=========================================================================== // Shared functions //=========================================================================== namespace PartDesignGui { /// Returns active part, if there is no such, creates a new part, if it fails, shows a message App::Part* assertActivePart() { App::Part* rv = Gui::Application::Instance->activeView()->getActiveObject(PARTKEY); if (!rv) { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.runCommandByName("Std_Part"); rv = Gui::Application::Instance->activeView()->getActiveObject(PARTKEY); if (!rv) { QMessageBox::critical( nullptr, QObject::tr("Part creation failed"), QObject::tr("Failed to create a part object.") ); } } return rv; } } // namespace PartDesignGui // PartDesign_Body //=========================================================================== DEF_STD_CMD_A(CmdPartDesignBody) CmdPartDesignBody::CmdPartDesignBody() : Command("PartDesign_Body") { sAppModule = "PartDesign"; sGroup = QT_TR_NOOP("PartDesign"); sMenuText = QT_TR_NOOP("New Body"); sToolTipText = QT_TR_NOOP("Creates a new body and activates it"); sWhatsThis = "PartDesign_Body"; sStatusTip = sToolTipText; sPixmap = "PartDesign_Body"; } void CmdPartDesignBody::activated(int iMsg) { Q_UNUSED(iMsg); App::Part* actPart = PartDesignGui::getActivePart(); App::Part* partOfBaseFeature = nullptr; std::vector features = getSelection().getObjectsOfType( Part::Feature::getClassTypeId() ); App::DocumentObject* baseFeature = nullptr; bool addtogroup = false; Base::Reference hGrp = App::GetApplication().GetUserParameter().GetGroup( "BaseApp/Preferences/Mod/PartDesign" ); bool allowCompound = hGrp->GetBool("AllowCompoundDefault", true); if (!features.empty()) { if (features.size() == 1) { baseFeature = features[0]; if (baseFeature->isDerivedFrom(PartDesign::Feature::getClassTypeId()) && PartDesign::Body::findBodyOf(baseFeature)) { // Prevent creating bodies based on features already belonging to other bodies QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Bad base feature"), QObject::tr("A body cannot be based on a Part Design feature.") ); baseFeature = nullptr; } else if (PartDesign::Body::findBodyOf(baseFeature)) { QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Bad base feature"), QObject::tr("%1 already belongs to a body and cannot be used as a base feature for another body.") .arg(QString::fromUtf8(baseFeature->Label.getValue())) ); baseFeature = nullptr; } else if (baseFeature->isDerivedFrom(Part::BodyBase::getClassTypeId())) { // Prevent creating bodies based on bodies (but don't pop-up a dialog) baseFeature = nullptr; } else { partOfBaseFeature = App::Part::getPartOfObject(baseFeature); if (partOfBaseFeature && partOfBaseFeature != actPart) { // prevent cross-part mess QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Bad base feature"), QObject::tr("Base feature (%1) belongs to other part.") .arg(QString::fromUtf8(baseFeature->Label.getValue())) ); baseFeature = nullptr; } else if (baseFeature->isDerivedFrom()) { // Add sketcher to the body's group property addtogroup = true; } // if a standard Part feature (not a PartDesign feature) is selected then check // the number of solids/shells else if (!baseFeature->isDerivedFrom()) { const TopoDS_Shape& shape = static_cast(baseFeature)->Shape.getValue(); if (!shape.IsNull()) { int numSolids = 0; int numShells = 0; for (TopExp_Explorer xp(shape, TopAbs_SOLID); xp.More(); xp.Next()) { numSolids++; } for (TopExp_Explorer xp(shape, TopAbs_SHELL, TopAbs_SOLID); xp.More(); xp.Next()) { numShells++; } QString warning; if (numSolids > 1 && numShells == 0) { warning = QObject::tr( "The selected shape consists of multiple solids.\n" "This may lead to unexpected results." ); } else if (numShells > 1 && numSolids == 0) { warning = QObject::tr( "The selected shape consists of multiple shells.\n" "This may lead to unexpected results." ); } else if (numShells == 1 && numSolids == 0) { warning = QObject::tr( "The selected shape consists of only a shell.\n" "This may lead to unexpected results." ); } else if (numSolids + numShells > 1) { warning = QObject::tr( "The selected shape consists of multiple solids or shells.\n" "This may lead to unexpected results." ); } if (!warning.isEmpty()) { QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Base feature"), warning ); } } } } } else { QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Bad base feature"), QObject::tr("Body may be based on no more than one feature.") ); return; } } openCommand(QT_TRANSLATE_NOOP("Command", "Add a Body")); std::string bodyName = getUniqueObjectName("Body"); const char* bodyString = bodyName.c_str(); // add the Body feature itself, and make it active doCommand(Doc, "App.activeDocument().addObject('PartDesign::Body','%s')", bodyString); // set Label for i18n/L10N std::string labelString = QObject::tr("Body").toUtf8().toStdString(); labelString = Base::Tools::escapeEncodeString(labelString); doCommand(Doc, "App.ActiveDocument.getObject('%s').Label = '%s'", bodyString, labelString.c_str()); doCommand( Doc, "App.ActiveDocument.getObject('%s').AllowCompound = %s", bodyString, allowCompound ? "True" : "False" ); if (baseFeature) { if (partOfBaseFeature) { // withdraw base feature from Part, otherwise visibility madness results doCommand( Doc, "App.activeDocument().%s.removeObject(App.activeDocument().%s)", partOfBaseFeature->getNameInDocument(), baseFeature->getNameInDocument() ); } if (addtogroup) { doCommand( Doc, "App.activeDocument().%s.Group = [App.activeDocument().%s]", bodyString, baseFeature->getNameInDocument() ); } else { doCommand( Doc, "App.activeDocument().%s.BaseFeature = App.activeDocument().%s", bodyString, baseFeature->getNameInDocument() ); } } addModule(Gui, "PartDesignGui"); // import the Gui module only once a session if (actPart) { doCommand( Doc, "App.activeDocument().%s.addObject(App.ActiveDocument.%s)", actPart->getNameInDocument(), bodyString ); } doCommand( Gui::Command::Gui, "Gui.activateView('Gui::View3DInventor', True)\n" "Gui.activeView().setActiveObject('%s', App.activeDocument().%s)", PDBODYKEY, bodyString ); // Make the "Create sketch" prompt appear in the task panel doCommand(Gui, "Gui.Selection.clearSelection()"); doCommand(Gui, "Gui.Selection.addSelection(App.ActiveDocument.%s)", bodyString); // check if a proxy object has been created for the base feature inside the body if (baseFeature) { PartDesign::Body* body = dynamic_cast( baseFeature->getDocument()->getObject(bodyString) ); if (body) { std::vector links = body->Group.getValues(); for (auto it : links) { if (it->isDerivedFrom()) { PartDesign::FeatureBase* base = static_cast(it); if (base && base->BaseFeature.getValue() == baseFeature) { Gui::Application::Instance->hideViewProvider(baseFeature); break; } } } // for sketches open the feature dialog to rebase it to a new plane // as requested in issue #0002862 if (addtogroup) { std::vector planes; std::vector status; unsigned validPlaneCount = 0; for (auto plane : body->getOrigin()->planes()) { planes.push_back(plane); status.push_back(PartDesignGui::TaskFeaturePick::basePlane); validPlaneCount++; } if (validPlaneCount > 1) { // Determines if user made a valid selection in dialog auto accepter = [](const std::vector& features) -> bool { return !features.empty(); }; // Called by dialog when user hits "OK" and accepter returns true auto worker = [baseFeature](const std::vector& features) { // may happen when the user switched to an empty document while the // dialog is open if (features.empty()) { return; } App::Plane* plane = static_cast(features.front()); std::string supportString = Gui::Command::getObjectCmd(plane, "(", ", [''])"); FCMD_OBJ_CMD(baseFeature, "AttachmentSupport = " << supportString); FCMD_OBJ_CMD( baseFeature, "MapMode = '" << Attacher::AttachEngine::getModeName(Attacher::mmFlatFace) << "'" ); Gui::Command::updateActive(); }; // Called by dialog for "Cancel", or "OK" if accepter returns false std::string docname = getDocument()->getName(); auto quitter = [docname]() { Gui::Document* document = Gui::Application::Instance->getDocument( docname.c_str() ); if (document) { document->abortCommand(); } }; // Show dialog and let user pick plane Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); if (!dlg) { Gui::Selection().clearSelection(); Gui::Control().showDialog( new PartDesignGui::TaskDlgFeaturePick(planes, status, accepter, worker, true, quitter) ); } } } } } updateActive(); } bool CmdPartDesignBody::isActive() { return hasActiveDocument(); } //=========================================================================== // PartDesign_Migrate //=========================================================================== DEF_STD_CMD_A(CmdPartDesignMigrate) CmdPartDesignMigrate::CmdPartDesignMigrate() : Command("PartDesign_Migrate") { sAppModule = "PartDesign"; sGroup = QT_TR_NOOP("PartDesign"); sMenuText = QT_TR_NOOP("Migrate"); sToolTipText = QT_TR_NOOP("Migrates the document to the modern Part Design workflow"); sWhatsThis = "PartDesign_Migrate"; sStatusTip = sToolTipText; sPixmap = "PartDesign_Migrate"; } void CmdPartDesignMigrate::activated(int iMsg) { Q_UNUSED(iMsg); App::Document* doc = getDocument(); std::set migrateFeatures; // Retrieve all PartDesign Features objects and filter out features already belonging to some body for (const auto& feat : doc->getObjects()) { if (feat->isDerivedFrom(PartDesign::Feature::getClassTypeId()) && !PartDesign::Body::findBodyOf(feat) && PartDesign::Body::isSolidFeature(feat)) { migrateFeatures.insert(static_cast(feat)); } } if (migrateFeatures.empty()) { if (!PartDesignGui::isModernWorkflow(doc)) { // If there is nothing to migrate and workflow is still old just set it to modern PartDesignGui::WorkflowManager::instance()->forceWorkflow( doc, PartDesignGui::Workflow::Modern ); } else { // Huh? nothing to migrate? QMessageBox::warning( nullptr, QObject::tr("Nothing to migrate"), QObject::tr( "No Part Design features without body found" " Nothing to migrate." ) ); } return; } // Note: this action is undoable, should it be? PartDesignGui::WorkflowManager::instance()->forceWorkflow(doc, PartDesignGui::Workflow::Modern); // Put features into chains. Each chain should become a separate body. std::list> featureChains; std::list chain; //< the current chain we are working on for (auto featIt = migrateFeatures.begin(); !migrateFeatures.empty();) { Part::Feature* base = (*featIt)->getBaseObject(/*silent =*/true); chain.push_front(*featIt); if (!base || !base->isDerivedFrom(PartDesign::Feature::getClassTypeId()) || PartDesignGui::isAnyNonPartDesignLinksTo( static_cast(base), /*respectGroups=*/true )) { // a feature based on nothing as well as on non-partdesign solid starts a new chain auto newChainIt = featureChains.emplace(featureChains.end()); newChainIt->splice(newChainIt->end(), chain); } else { // we are basing on some partdesign feature which supposed to belong to some body PartDesign::Feature* baseFeat = static_cast(base); auto baseFeatSetIt = migrateFeatures.find(baseFeat); if (baseFeatSetIt != migrateFeatures.end()) { // base feature is pending for migration, switch to it and continue over migrateFeatures.erase(featIt); featIt = baseFeatSetIt; continue; } else { // The base feature seems already assigned to some chain. Find which std::list::iterator baseFeatIt; auto isChain = [baseFeat, &baseFeatIt](std::list& fchain) mutable -> bool { baseFeatIt = std::ranges::find(fchain, baseFeat); return baseFeatIt != fchain.end(); }; if (auto chainIt = std::ranges::find_if(featureChains, isChain); chainIt != featureChains.end()) { assert(baseFeatIt != chainIt->end()); if (std::next(baseFeatIt) == chainIt->end()) { // just append our chain to already found chainIt->splice(chainIt->end(), chain); // TODO: If we will hit a third part everything will be messed up again. // Probably it will require a yet another smart-ass find_if. // (2015-08-10, Fat-Zer) } else { // We have a fork of a partDesign feature here // add a chain for current body auto newChainIt = featureChains.emplace(featureChains.end()); newChainIt->splice(newChainIt->end(), chain); // add a chain for forked one newChainIt = featureChains.emplace(featureChains.end()); newChainIt->splice( newChainIt->end(), *chainIt, std::next(baseFeatIt), chainIt->end() ); } } else { // The feature is not present in list pending for migration, // This generally shouldn't happen but may be if we run into some broken file // Try to find out the body we should insert into // TODO: Some error/warning is needed here (2015-08-10, Fat-Zer) auto newChainIt = featureChains.emplace(featureChains.end()); newChainIt->splice(newChainIt->end(), chain); } } } migrateFeatures.erase(featIt); featIt = migrateFeatures.begin(); // TODO: Align visibility (2015-08-17, Fat-Zer) } /* for */ // TODO: make it work without parts (2015-09-04, Fat-Zer) // add a part if there is no active yet App::Part* actPart = PartDesignGui::assertActivePart(); if (!actPart) { return; } // do the actual migration Gui::Command::openCommand( QT_TRANSLATE_NOOP("Command", "Migrate legacy Part Design features to bodies") ); for (auto chainIt = featureChains.begin(); !featureChains.empty(); featureChains.erase(chainIt), chainIt = featureChains.begin()) { #ifndef FC_DEBUG if (chainIt->empty()) { // prevent crash in release in case of errors continue; } #else assert(!chainIt->empty()); #endif Part::Feature* base = chainIt->front()->getBaseObject(/*silent =*/true); // Find a suitable chain to work with for (; chainIt != featureChains.end(); chainIt++) { base = chainIt->front()->getBaseObject(/*silent =*/true); if (!base || !base->isDerivedFrom(PartDesign::Feature::getClassTypeId())) { break; // no base is ok } else { // The base feature is a PartDesign, it's a fork, try to reassign it to a body... base = PartDesign::Body::findBodyOf(base); if (base) { break; } } } if (chainIt == featureChains.end()) { // Shouldn't happen, may be only in case of some circular dependency? // TODO Some error message (2015-08-11, Fat-Zer) chainIt = featureChains.begin(); base = chainIt->front()->getBaseObject(/*silent =*/true); } // Construct a Pretty Body name based on the Tip std::string bodyName = getUniqueObjectName( std::string(chainIt->back()->getNameInDocument()).append("Body").c_str() ); Base::Reference hGrp = App::GetApplication().GetUserParameter().GetGroup( "BaseApp/Preferences/Mod/PartDesign" ); bool allowCompound = hGrp->GetBool("AllowCompoundDefault", true); // Create a body for the chain doCommand(Doc, "App.activeDocument().addObject('PartDesign::Body','%s')", bodyName.c_str()); doCommand( Doc, "App.ActiveDocument.getObject('%s').AllowCompound = %s", bodyName.c_str(), allowCompound ? "True" : "False" ); doCommand( Doc, "App.activeDocument().%s.addObject(App.ActiveDocument.%s)", actPart->getNameInDocument(), bodyName.c_str() ); if (base) { doCommand( Doc, "App.activeDocument().%s.BaseFeature = App.activeDocument().%s", bodyName.c_str(), base->getNameInDocument() ); } // Fill the body with features for (auto feature : *chainIt) { if (feature->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { // add the sketch and also reroute it if needed PartDesign::ProfileBased* sketchBased = static_cast(feature); Part::Part2DObject* sketch = sketchBased->getVerifiedSketch(/*silent =*/true); if (sketch) { doCommand( Doc, "App.activeDocument().%s.addObject(App.activeDocument().%s)", bodyName.c_str(), sketch->getNameInDocument() ); if (sketch->isDerivedFrom(Sketcher::SketchObject::getClassTypeId())) { try { PartDesignGui::fixSketchSupport( static_cast(sketch) ); } catch (Base::Exception&) { QMessageBox::critical( Gui::getMainWindow(), QObject::tr("Sketch plane cannot be migrated"), QObject::tr( "Please edit '%1' and redefine it to use a Base or " "Datum plane as the sketch plane." ) .arg(QString::fromUtf8(sketch->Label.getValue())) ); } } else { // TODO: Message that sketchbased is based not on a sketch (2015-08-11, Fat-Zer) } } } doCommand( Doc, "App.activeDocument().%s.addObject(App.activeDocument().%s)", bodyName.c_str(), feature->getNameInDocument() ); PartDesignGui::relinkToBody(feature); } } updateActive(); } bool CmdPartDesignMigrate::isActive() { return hasActiveDocument(); } //=========================================================================== // PartDesign_MoveTip //=========================================================================== DEF_STD_CMD_A(CmdPartDesignMoveTip) CmdPartDesignMoveTip::CmdPartDesignMoveTip() : Command("PartDesign_MoveTip") { sAppModule = "PartDesign"; sGroup = QT_TR_NOOP("PartDesign"); sMenuText = QT_TR_NOOP("Set Tip"); sToolTipText = QT_TR_NOOP("Moves the tip of the body to the selected feature"); sWhatsThis = "PartDesign_MoveTip"; sStatusTip = sToolTipText; sPixmap = "PartDesign_MoveTip"; } void CmdPartDesignMoveTip::activated(int iMsg) { Q_UNUSED(iMsg); std::vector features = getSelection().getObjectsOfType( Part::Feature::getClassTypeId() ); App::DocumentObject* selFeature; PartDesign::Body* body = nullptr; if (features.size() == 1) { selFeature = features.front(); if (selFeature->isDerivedFrom()) { body = static_cast(selFeature); } else { body = PartDesignGui::getBodyFor(selFeature, /* messageIfNot =*/false); } } else { selFeature = nullptr; } if (!selFeature) { QMessageBox::warning( nullptr, QObject::tr("Selection error"), QObject::tr("Select exactly one Part Design feature or a body.") ); return; } else if (!body) { QMessageBox::warning( nullptr, QObject::tr("Selection error"), QObject::tr( "Could not determine a body for the selected feature '%s'.", selFeature->Label.getValue() ) ); return; } else if (!selFeature->isDerivedFrom(PartDesign::Feature::getClassTypeId()) && selFeature != body && body->BaseFeature.getValue() != selFeature) { QMessageBox::warning( nullptr, QObject::tr("Selection error"), QObject::tr("Only a solid feature can be the tip of a body.") ); return; } App::DocumentObject* oldTip = body->Tip.getValue(); if (oldTip == selFeature) { // it's not generally an error, so print only a console message Base::Console().message("%s is already the tip of the body\n", selFeature->getNameInDocument()); return; } openCommand(QT_TRANSLATE_NOOP("Command", "Move tip to selected feature")); if (selFeature == body) { FCMD_OBJ_CMD(body, "Tip = None"); } else { FCMD_OBJ_CMD(body, "Tip = " << getObjectCmd(selFeature)); // Adjust visibility to show only the Tip feature FCMD_OBJ_SHOW(selFeature); } // TODO: Hide all datum features after the Tip feature? But the user might have already hidden // some and wants to see others, so we would have to remember their state somehow updateActive(); } bool CmdPartDesignMoveTip::isActive() { return hasActiveDocument(); } //=========================================================================== // PartDesign_DuplicateSelection //=========================================================================== DEF_STD_CMD_A(CmdPartDesignDuplicateSelection) CmdPartDesignDuplicateSelection::CmdPartDesignDuplicateSelection() : Command("PartDesign_DuplicateSelection") { sAppModule = "PartDesign"; sGroup = QT_TR_NOOP("PartDesign"); sMenuText = QT_TR_NOOP("Duplicate &Object"); sToolTipText = QT_TR_NOOP("Duplicates the selected object and adds it to the active body"); sWhatsThis = "PartDesign_DuplicateSelection"; sStatusTip = sToolTipText; } void CmdPartDesignDuplicateSelection::activated(int iMsg) { Q_UNUSED(iMsg); PartDesign::Body* pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */ false); std::vector beforeFeatures = getDocument()->getObjects(); openCommand(QT_TRANSLATE_NOOP("Command", "Duplicate a Part Design object")); doCommand(Doc, "FreeCADGui.runCommand('Std_DuplicateSelection')"); if (pcActiveBody) { // Find the features that were added std::vector afterFeatures = getDocument()->getObjects(); std::vector newFeatures; std::sort(beforeFeatures.begin(), beforeFeatures.end()); std::sort(afterFeatures.begin(), afterFeatures.end()); std::set_difference( afterFeatures.begin(), afterFeatures.end(), beforeFeatures.begin(), beforeFeatures.end(), std::back_inserter(newFeatures) ); for (auto feature : newFeatures) { if (PartDesign::Body::isAllowed(feature)) { // if feature already is in a body, then we don't put it into the active body issue #6278 auto body = App::GeoFeatureGroupExtension::getGroupOfObject(feature); if (!body) { FCMD_OBJ_CMD(pcActiveBody, "addObject(" << getObjectCmd(feature) << ")"); FCMD_OBJ_HIDE(feature); } } } // Adjust visibility of features if (!newFeatures.empty()) { FCMD_OBJ_SHOW(newFeatures.back()); } } updateActive(); } bool CmdPartDesignDuplicateSelection::isActive() { return hasActiveDocument(); } //=========================================================================== // PartDesign_MoveFeature //=========================================================================== DEF_STD_CMD_A(CmdPartDesignMoveFeature) CmdPartDesignMoveFeature::CmdPartDesignMoveFeature() : Command("PartDesign_MoveFeature") { sAppModule = "PartDesign"; sGroup = QT_TR_NOOP("PartDesign"); sMenuText = QT_TR_NOOP("Move Object To…"); sToolTipText = QT_TR_NOOP("Moves the selected object to another body"); sWhatsThis = "PartDesign_MoveFeature"; sStatusTip = sToolTipText; sPixmap = "PartDesign_MoveFeature"; } void CmdPartDesignMoveFeature::activated(int iMsg) { Q_UNUSED(iMsg); std::vector features = getSelection().getObjectsOfType( Part::Feature::getClassTypeId() ); if (features.empty()) { return; } // Check if all features are valid to move if (std::any_of(std::begin(features), std::end(features), [](App::DocumentObject* obj) { return !PartDesignGui::isFeatureMovable(obj); })) { // show messagebox and cancel QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Features cannot be moved"), QObject::tr("Some of the selected features have dependencies in the source body") ); return; } // Collect dependencies of the selected features std::vector dependencies = PartDesignGui::collectMovableDependencies( features ); if (!dependencies.empty()) { features.insert(std::end(features), std::begin(dependencies), std::end(dependencies)); } // Create a list of all bodies in this part std::vector bodies = getDocument()->getObjectsOfType( Part::BodyBase::getClassTypeId() ); std::set source_bodies; for (auto feat : features) { // Note: 'source' can be null which means that the feature doesn't belong to a body. PartDesign::Body* source = PartDesign::Body::findBodyOf(feat); source_bodies.insert(static_cast(source)); } if (source_bodies.size() != 1) { // show messagebox and cancel QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Features cannot be moved"), QObject::tr("Only features of a single source body can be moved") ); return; } auto source_body = *source_bodies.begin(); std::vector target_bodies; for (auto body : bodies) { if (!source_bodies.count(body)) { target_bodies.push_back(body); } } if (target_bodies.empty()) { QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Features cannot be moved"), QObject::tr("There are no other bodies to move to") ); return; } // Ask user to select the target body (remove source bodies from list) bool ok; QStringList items; for (auto body : target_bodies) { items.push_back(QString::fromUtf8(body->Label.getValue())); } QString text = QInputDialog::getItem( Gui::getMainWindow(), qApp->translate("PartDesign_MoveFeature", "Select Body"), qApp->translate("PartDesign_MoveFeature", "Select a body from the list"), items, 0, false, &ok, Qt::MSWindowsFixedSizeDialogHint ); if (!ok) { return; } int index = items.indexOf(text); if (index < 0) { return; } PartDesign::Body* target = static_cast(target_bodies[index]); openCommand(QT_TRANSLATE_NOOP("Command", "Move an object")); std::stringstream stream; stream << "features_ = [" << getObjectCmd(features.back()); features.pop_back(); for (auto feat : features) { stream << ", " << getObjectCmd(feat); } stream << "]"; runCommand(Doc, stream.str().c_str()); FCMD_OBJ_CMD(source_body, "removeObjects(features_)"); FCMD_OBJ_CMD(target, "addObjects(features_)"); /* // Find body of this feature Part::BodyBase* source = PartDesign::Body::findBodyOf(feat); bool featureWasTip = false; if (source == target) continue; // Remove from the source body if the feature belonged to a body if (source) { featureWasTip = (source->Tip.getValue() == feat); doCommand(Doc,"App.activeDocument().%s.removeObject(App.activeDocument().%s)", source->getNameInDocument(), (feat)->getNameInDocument()); } App::DocumentObject* targetOldTip = target->Tip.getValue(); // Add to target body (always at the Tip) doCommand(Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)", target->getNameInDocument(), (feat)->getNameInDocument()); // Recompute to update the shape doCommand(Gui,"App.activeDocument().recompute()"); // Adjust visibility of features // TODO: May be something can be done in view provider (2015-08-05, Fat-Zer) // If we removed the tip of the source body, make the new tip visible if ( featureWasTip ) { App::DocumentObject * sourceNewTip = source->Tip.getValue(); if (sourceNewTip) doCommand(Gui,"Gui.activeDocument().show(\"%s\")", sourceNewTip->getNameInDocument()); } // Hide old tip and show new tip (the moved feature) of the target body App::DocumentObject* targetNewTip = target->Tip.getValue(); if ( targetOldTip != targetNewTip ) { if ( targetOldTip ) { doCommand(Gui,"Gui.activeDocument().hide(\"%s\")", targetOldTip->getNameInDocument()); } if (targetNewTip) { doCommand(Gui,"Gui.activeDocument().show(\"%s\")", targetNewTip->getNameInDocument()); } } // Fix sketch support if (feat->isDerivedFrom()) { Sketcher::SketchObject *sketch = static_cast(feat); try { PartDesignGui::fixSketchSupport(sketch); } catch (Base::Exception &) { QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Sketch plane cannot be migrated"), QObject::tr("Please edit '%1' and redefine it to use a Base or Datum plane as the sketch plane."). arg( QString::fromLatin1( sketch->Label.getValue () ) ) ); } } //relink origin for sketches and datums (coordinates) PartDesignGui::relinkToOrigin(feat, target); }*/ updateActive(); } bool CmdPartDesignMoveFeature::isActive() { return hasActiveDocument(); } DEF_STD_CMD_A(CmdPartDesignMoveFeatureInTree) CmdPartDesignMoveFeatureInTree::CmdPartDesignMoveFeatureInTree() : Command("PartDesign_MoveFeatureInTree") { sAppModule = "PartDesign"; sGroup = QT_TR_NOOP("PartDesign"); sMenuText = QT_TR_NOOP("Move Feature After…"); sToolTipText = QT_TR_NOOP("Moves the selected feature after another feature in the same body"); sWhatsThis = "PartDesign_MoveFeatureInTree"; sStatusTip = sToolTipText; sPixmap = "PartDesign_MoveFeatureInTree"; } void CmdPartDesignMoveFeatureInTree::activated(int iMsg) { Q_UNUSED(iMsg); std::vector features = getSelection().getObjectsOfType( Part::Feature::getClassTypeId() ); // also check and include datum objects, ie. plane, line, and point std::vector datums = getSelection().getObjectsOfType( App::DatumElement::getClassTypeId() ); features.insert(features.end(), datums.begin(), datums.end()); if (features.empty()) { return; } PartDesign::Body* body = PartDesignGui::getBodyFor(features.front(), false); App::DocumentObject* bodyBase = nullptr; // sanity check bool allFeaturesFromSameBody = true; if (body) { bodyBase = body->BaseFeature.getValue(); for (auto feat : features) { if (!body->hasObject(feat)) { allFeaturesFromSameBody = false; break; } if (bodyBase == feat) { QMessageBox::warning( nullptr, QObject::tr("Selection error"), QObject::tr("Impossible to move the base feature of a body.") ); return; } } } if (!body || !allFeaturesFromSameBody) { QMessageBox::warning( nullptr, QObject::tr("Selection error"), QObject::tr("Select one or more features from the same body.") ); return; } // Create a list of all features in this body const std::vector& model = body->Group.getValues(); // Ask user to select the target feature bool ok; QStringList items; if (bodyBase) { items.push_back(QString::fromUtf8(bodyBase->Label.getValue())); } else { items.push_back(QObject::tr("Beginning of the body")); } for (auto feat : model) { items.push_back(QString::fromUtf8(feat->Label.getValue())); } QString text = QInputDialog::getItem( Gui::getMainWindow(), qApp->translate("PartDesign_MoveFeatureInTree", "Move Feature After…"), qApp->translate("PartDesign_MoveFeatureInTree", "Select a feature from the list"), items, 0, false, &ok, Qt::MSWindowsFixedSizeDialogHint ); if (!ok) { return; } int index = items.indexOf(text); // first object is the beginning of the body App::DocumentObject* target = index != 0 ? model[index - 1] : nullptr; openCommand(QT_TRANSLATE_NOOP("Command", "Move a feature inside body")); App::DocumentObject* lastObject = target; for (auto feat : features) { if (feat == target) { continue; } // Remove and re-insert the feature to/from the Body, preserving their order. // TODO: if tip was moved the new position of tip is quite undetermined (2015-08-07, Fat-Zer) // TODO: warn the user if we are moving an object to some place before the object's link // (2015-08-07, Fat-Zer) FCMD_OBJ_CMD(body, "removeObject(" << getObjectCmd(feat) << ")"); FCMD_OBJ_CMD( body, "insertObject(" << getObjectCmd(feat) << "," << getObjectCmd(lastObject) << ", True)" ); lastObject = feat; } // Dependency order check. // We must make sure the resulting objects of PartDesign::Feature do not // depend on later objects std::vector bodyFeatures; std::map orders; for (auto obj : body->Group.getValues()) { if (obj->isDerivedFrom()) { orders.emplace(obj, bodyFeatures.size()); bodyFeatures.push_back(obj); } } bool failed = false; std::ostringstream ss; for (size_t i = 0; i < bodyFeatures.size(); ++i) { auto feat = bodyFeatures[i]; for (auto obj : feat->getOutList()) { if (obj->isDerivedFrom()) { continue; } for (auto dep : App::Document::getDependencyList({obj})) { auto it = orders.find(dep); if (it != orders.end() && it->second > i) { ss << feat->Label.getValue() << ", " << obj->Label.getValue() << " -> " << it->first->Label.getValue(); if (!failed) { failed = true; } else { ss << std::endl; } } } } } if (failed) { QMessageBox::critical( nullptr, QObject::tr("Dependency violation"), QObject::tr("Early feature must not depend on later feature.\n\n") + QString::fromUtf8(ss.str().c_str()) ); abortCommand(); return; } // If the selected objects have been moved after the current tip then ask the // user if they want the last object to be the new tip. // Only do this for features that can hold a tip (not for e.g. datums) if (lastObject != target && body->Tip.getValue() == target && lastObject->isDerivedFrom()) { QMessageBox msgBox(Gui::getMainWindow()); msgBox.setIcon(QMessageBox::Question); msgBox.setWindowTitle(qApp->translate("PartDesign_MoveFeatureInTree", "Move Tip")); msgBox.setText(qApp->translate( "PartDesign_MoveFeatureInTree", "The moved feature appears after the currently set tip." )); msgBox.setInformativeText( qApp->translate("PartDesign_MoveFeatureInTree", "Set tip to last feature?") ); msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); msgBox.setDefaultButton(QMessageBox::No); int ret = msgBox.exec(); if (ret == QMessageBox::Yes) { FCMD_OBJ_CMD(body, "Tip = " << getObjectCmd(lastObject)); } } updateActive(); } bool CmdPartDesignMoveFeatureInTree::isActive() { return hasActiveDocument(); } //=========================================================================== // Initialization //=========================================================================== void CreatePartDesignBodyCommands() { Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); rcCmdMgr.addCommand(new CmdPartDesignBody()); rcCmdMgr.addCommand(new CmdPartDesignMigrate()); rcCmdMgr.addCommand(new CmdPartDesignMoveTip()); rcCmdMgr.addCommand(new CmdPartDesignDuplicateSelection()); rcCmdMgr.addCommand(new CmdPartDesignMoveFeature()); rcCmdMgr.addCommand(new CmdPartDesignMoveFeatureInTree()); }