1219 lines
45 KiB
C++
1219 lines
45 KiB
C++
/***************************************************************************
|
|
* 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 *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
#include <QApplication>
|
|
#include <QInputDialog>
|
|
#include <QMessageBox>
|
|
#include <TopExp_Explorer.hxx>
|
|
|
|
#include <App/Datums.h>
|
|
#include <App/Document.h>
|
|
#include <App/GeoFeatureGroupExtension.h>
|
|
#include <App/Origin.h>
|
|
#include <App/Part.h>
|
|
#include <Base/Console.h>
|
|
#include <Base/Tools.h>
|
|
#include <Gui/Command.h>
|
|
#include <Gui/Control.h>
|
|
#include <Gui/Document.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/MainWindow.h>
|
|
#include <Gui/MDIView.h>
|
|
#include <Mod/Sketcher/App/SketchObject.h>
|
|
#include <Mod/PartDesign/App/Body.h>
|
|
#include <Mod/PartDesign/App/FeatureBase.h>
|
|
#include <Mod/PartDesign/App/FeatureSketchBased.h>
|
|
|
|
#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<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()) {
|
|
#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<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());
|
|
}
|