Download src/Mod/PartDesign/Gui/CommandBody.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 46.3 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/Gui/CommandBody.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/PartDesign/Gui/CommandBody.cpp
-
curl -L -o CommandBody.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/Gui/CommandBody.cpp
46.3 kB
| /*************************************************************************** | |
| * Copyright (C) 2015 Alexander Golubev (Fat-Zer) <fatzer2@gmail.com> * | |
| * * | |
| * 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 * | |
| * * | |
| ***************************************************************************/ | |
| //=========================================================================== | |
| // 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<App::Part*>(PARTKEY); | |
| if (!rv) { | |
| Gui::CommandManager& rcCmdMgr = Gui::Application::Instance->commandManager(); | |
| rcCmdMgr.runCommandByName("Std_Part"); | |
| rv = Gui::Application::Instance->activeView()->getActiveObject<App::Part*>(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<App::DocumentObject*> features = getSelection().getObjectsOfType( | |
| Part::Feature::getClassTypeId() | |
| ); | |
| App::DocumentObject* baseFeature = nullptr; | |
| bool addtogroup = false; | |
| Base::Reference<ParameterGrp> 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<Sketcher::SketchObject>()) { | |
| // 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<PartDesign::Feature>()) { | |
| const TopoDS_Shape& shape | |
| = static_cast<Part::Feature*>(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<PartDesign::Body*>( | |
| baseFeature->getDocument()->getObject(bodyString) | |
| ); | |
| if (body) { | |
| std::vector<App::DocumentObject*> links = body->Group.getValues(); | |
| for (auto it : links) { | |
| if (it->isDerivedFrom<PartDesign::FeatureBase>()) { | |
| PartDesign::FeatureBase* base = static_cast<PartDesign::FeatureBase*>(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<App::DocumentObject*> planes; | |
| std::vector<PartDesignGui::TaskFeaturePick::featureStatus> 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<App::DocumentObject*>& features) -> bool { | |
| return !features.empty(); | |
| }; | |
| // Called by dialog when user hits "OK" and accepter returns true | |
| auto worker = [baseFeature](const std::vector<App::DocumentObject*>& 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<App::Plane*>(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<PartDesign::Feature*> 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<PartDesign::Feature*>(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<std::list<PartDesign::Feature*>> featureChains; | |
| std::list<PartDesign::Feature*> 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<PartDesign::Feature*>(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<PartDesign::Feature*>(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<PartDesign::Feature*>::iterator baseFeatIt; | |
| auto isChain = | |
| [baseFeat, &baseFeatIt](std::list<PartDesign::Feature*>& 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()) { | |
| if (chainIt->empty()) { // prevent crash in release in case of errors | |
| continue; | |
| } | |
| assert(!chainIt->empty()); | |
| 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<ParameterGrp> 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<PartDesign::ProfileBased*>(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<Sketcher::SketchObject*>(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<App::DocumentObject*> features = getSelection().getObjectsOfType( | |
| Part::Feature::getClassTypeId() | |
| ); | |
| App::DocumentObject* selFeature; | |
| PartDesign::Body* body = nullptr; | |
| if (features.size() == 1) { | |
| selFeature = features.front(); | |
| if (selFeature->isDerivedFrom<PartDesign::Body>()) { | |
| body = static_cast<PartDesign::Body*>(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<App::DocumentObject*> 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<App::DocumentObject*> afterFeatures = getDocument()->getObjects(); | |
| std::vector<App::DocumentObject*> 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<App::DocumentObject*> 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<App::DocumentObject*> 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<App::DocumentObject*> bodies = getDocument()->getObjectsOfType( | |
| Part::BodyBase::getClassTypeId() | |
| ); | |
| std::set<App::DocumentObject*> 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<App::DocumentObject*>(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<App::DocumentObject*> 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<PartDesign::Body*>(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>()) { | |
| Sketcher::SketchObject *sketch = static_cast<Sketcher::SketchObject*>(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<App::DocumentObject*> features = getSelection().getObjectsOfType( | |
| Part::Feature::getClassTypeId() | |
| ); | |
| // also check and include datum objects, ie. plane, line, and point | |
| std::vector<App::DocumentObject*> 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<App::DocumentObject*>& 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<App::DocumentObject*> bodyFeatures; | |
| std::map<App::DocumentObject*, size_t> orders; | |
| for (auto obj : body->Group.getValues()) { | |
| if (obj->isDerivedFrom<PartDesign::Feature>()) { | |
| 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<PartDesign::Feature>()) { | |
| 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<PartDesign::Feature>()) { | |
| 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()); | |
| } | |