
AttachableObjects are desired in multiple occasions, and the current AttachableObject is not flexible enough to handle all cases. Hence the code is portet to an extension, which gives the needed flexibility.
1999 lines
76 KiB
C++
1999 lines
76 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2008 Jürgen Riegel (juergen.riegel@web.de) *
|
|
* *
|
|
* 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 "PreCompiled.h"
|
|
|
|
#ifndef _PreComp_
|
|
# include <TopoDS_Face.hxx>
|
|
# include <TopoDS.hxx>
|
|
# include <BRepAdaptor_Surface.hxx>
|
|
# include <TopExp_Explorer.hxx>
|
|
# include <QMessageBox>
|
|
#endif
|
|
|
|
#include <sstream>
|
|
#include <algorithm>
|
|
|
|
#include <App/DocumentObjectGroup.h>
|
|
#include <App/Origin.h>
|
|
#include <App/OriginFeature.h>
|
|
#include <App/Part.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/Command.h>
|
|
#include <Gui/Control.h>
|
|
#include <Gui/Selection.h>
|
|
#include <Gui/MainWindow.h>
|
|
#include <Gui/Document.h>
|
|
|
|
#include <Mod/Sketcher/App/SketchObject.h>
|
|
|
|
#include <Mod/PartDesign/App/Body.h>
|
|
#include <Mod/PartDesign/App/FeatureGroove.h>
|
|
#include <Mod/PartDesign/App/FeatureRevolution.h>
|
|
#include <Mod/PartDesign/App/FeatureTransformed.h>
|
|
#include <Mod/PartDesign/App/FeatureMultiTransform.h>
|
|
#include <Mod/PartDesign/App/DatumPoint.h>
|
|
#include <Mod/PartDesign/App/DatumLine.h>
|
|
#include <Mod/PartDesign/App/DatumPlane.h>
|
|
#include <Mod/PartDesign/App/ShapeBinder.h>
|
|
|
|
#include "TaskFeaturePick.h"
|
|
#include "ReferenceSelection.h"
|
|
#include "Utils.h"
|
|
#include "WorkflowManager.h"
|
|
|
|
// TODO Remove this header after fixing code so it won;t be needed here (2015-10-20, Fat-Zer)
|
|
#include "ui_DlgReference.h"
|
|
|
|
using namespace std;
|
|
using namespace Attacher;
|
|
|
|
|
|
//===========================================================================
|
|
// PartDesign_Datum
|
|
//===========================================================================
|
|
|
|
/**
|
|
* @brief UnifiedDatumCommand is a common routine called by datum plane, line and point commands
|
|
* @param cmd (i/o) command, to have shortcuts to doCommand, etc.
|
|
* @param type (input)
|
|
* @param name (input). Is used to generate new name for an object, and to fill undo messages.
|
|
*
|
|
*/
|
|
void UnifiedDatumCommand(Gui::Command &cmd, Base::Type type, std::string name)
|
|
{
|
|
try{
|
|
std::string fullTypeName (type.getName());
|
|
|
|
App::PropertyLinkSubList support;
|
|
cmd.getSelection().getAsPropertyLinkSubList(support);
|
|
|
|
bool bEditSelected = false;
|
|
if (support.getSize() == 1 && support.getValue() ) {
|
|
if (support.getValue()->isDerivedFrom(type))
|
|
bEditSelected = true;
|
|
}
|
|
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */false);
|
|
|
|
if (bEditSelected) {
|
|
std::string tmp = std::string("Edit ")+name;
|
|
cmd.openCommand(tmp.c_str());
|
|
cmd.doCommand(Gui::Command::Gui,"Gui.activeDocument().setEdit('%s')",support.getValue()->getNameInDocument());
|
|
} else if (pcActiveBody) {
|
|
|
|
// TODO Check how this will work outside of a body (2015-10-20, Fat-Zer)
|
|
std::string FeatName = cmd.getUniqueObjectName(name.c_str());
|
|
|
|
std::string tmp = std::string("Create ")+name;
|
|
|
|
cmd.openCommand(tmp.c_str());
|
|
cmd.doCommand(Gui::Command::Doc,"App.activeDocument().addObject('%s','%s')",fullTypeName.c_str(),FeatName.c_str());
|
|
|
|
//test if current selection fits a mode.
|
|
if (support.getSize() > 0) {
|
|
Part::AttachExtension* pcDatum = cmd.getDocument()->getObject(FeatName.c_str())->getExtensionByType<Part::AttachExtension>();
|
|
pcDatum->attacher().references.Paste(support);
|
|
SuggestResult sugr;
|
|
pcDatum->attacher().suggestMapModes(sugr);
|
|
if (sugr.message == Attacher::SuggestResult::srOK) {
|
|
//fits some mode. Populate support property.
|
|
cmd.doCommand(Gui::Command::Doc,"App.activeDocument().%s.Support = %s",FeatName.c_str(),support.getPyReprString().c_str());
|
|
cmd.doCommand(Gui::Command::Doc,"App.activeDocument().%s.MapMode = '%s'",FeatName.c_str(),AttachEngine::getModeName(sugr.bestFitMode).c_str());
|
|
} else {
|
|
QMessageBox::information(Gui::getMainWindow(),QObject::tr("Invalid selection"), QObject::tr("There are no attachment modes that fit seleted objects. Select something else."));
|
|
}
|
|
}
|
|
if (pcActiveBody) {
|
|
cmd.doCommand(Gui::Command::Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)",
|
|
pcActiveBody->getNameInDocument(), FeatName.c_str());
|
|
}
|
|
cmd.doCommand(Gui::Command::Doc,"App.activeDocument().recompute()"); // recompute the feature based on its references
|
|
cmd.doCommand(Gui::Command::Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str());
|
|
}
|
|
} catch (Base::Exception &e) {
|
|
QMessageBox::warning(Gui::getMainWindow(),QObject::tr("Error"),QString::fromLatin1(e.what()));
|
|
} catch (Standard_Failure &e) {
|
|
QMessageBox::warning(Gui::getMainWindow(),QObject::tr("Error"),QString::fromLatin1(e.GetMessageString()));
|
|
}
|
|
}
|
|
|
|
/* Datum feature commands =======================================================*/
|
|
|
|
DEF_STD_CMD_A(CmdPartDesignPlane);
|
|
|
|
CmdPartDesignPlane::CmdPartDesignPlane()
|
|
:Command("PartDesign_Plane")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Create a datum plane");
|
|
sToolTipText = QT_TR_NOOP("Create a new datum plane");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Plane";
|
|
}
|
|
|
|
void CmdPartDesignPlane::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
UnifiedDatumCommand(*this, Base::Type::fromName("PartDesign::Plane"),"DatumPlane");
|
|
}
|
|
|
|
bool CmdPartDesignPlane::isActive(void)
|
|
{
|
|
if (getActiveGuiDocument())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
DEF_STD_CMD_A(CmdPartDesignLine);
|
|
|
|
CmdPartDesignLine::CmdPartDesignLine()
|
|
:Command("PartDesign_Line")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Create a datum line");
|
|
sToolTipText = QT_TR_NOOP("Create a new datum line");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Line";
|
|
}
|
|
|
|
void CmdPartDesignLine::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
UnifiedDatumCommand(*this, Base::Type::fromName("PartDesign::Line"),"DatumLine");
|
|
}
|
|
|
|
bool CmdPartDesignLine::isActive(void)
|
|
{
|
|
if (getActiveGuiDocument())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
DEF_STD_CMD_A(CmdPartDesignPoint);
|
|
|
|
CmdPartDesignPoint::CmdPartDesignPoint()
|
|
:Command("PartDesign_Point")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Create a datum point");
|
|
sToolTipText = QT_TR_NOOP("Create a new datum point");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Point";
|
|
}
|
|
|
|
void CmdPartDesignPoint::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
UnifiedDatumCommand(*this, Base::Type::fromName("PartDesign::Point"),"DatumPoint");
|
|
}
|
|
|
|
bool CmdPartDesignPoint::isActive(void)
|
|
{
|
|
if (getActiveGuiDocument())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_ShapeBinder
|
|
//===========================================================================
|
|
|
|
DEF_STD_CMD_A(CmdPartDesignShapeBinder);
|
|
|
|
CmdPartDesignShapeBinder::CmdPartDesignShapeBinder()
|
|
:Command("PartDesign_ShapeBinder")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Create a shape binder");
|
|
sToolTipText = QT_TR_NOOP("Create a new shape binder");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_ShapeBinder";
|
|
}
|
|
|
|
void CmdPartDesignShapeBinder::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
App::PropertyLinkSubList support;
|
|
getSelection().getAsPropertyLinkSubList(support);
|
|
|
|
bool bEditSelected = false;
|
|
if (support.getSize() == 1 && support.getValue() ){
|
|
if (support.getValue()->isDerivedFrom(PartDesign::ShapeBinder::getClassTypeId()))
|
|
bEditSelected = true;
|
|
}
|
|
|
|
if (bEditSelected) {
|
|
// TODO probably we not should handle edit here (2015-10-26, Fat-Zer)
|
|
std::string tmp = std::string("Edit ShapeBinder");
|
|
openCommand(tmp.c_str());
|
|
doCommand(Gui::Command::Gui,"Gui.activeDocument().setEdit('%s')",
|
|
support.getValue()->getNameInDocument());
|
|
} else {
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */true);
|
|
if (pcActiveBody == 0)
|
|
return;
|
|
|
|
std::string FeatName = getUniqueObjectName("ShapeBinder");
|
|
std::string tmp = std::string("Create ShapeBinder");
|
|
|
|
openCommand(tmp.c_str());
|
|
|
|
doCommand(Gui::Command::Doc,"App.activeDocument().addObject('%s','%s')",
|
|
"PartDesign::ShapeBinder",FeatName.c_str());
|
|
|
|
//test if current selection fits a mode.
|
|
if (support.getSize() > 0) {
|
|
doCommand(Gui::Command::Doc,"App.activeDocument().%s.Support = %s",
|
|
FeatName.c_str(), support.getPyReprString().c_str());
|
|
}
|
|
doCommand(Gui::Command::Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)",
|
|
pcActiveBody->getNameInDocument(), FeatName.c_str());
|
|
doCommand(Gui::Command::Doc,"App.activeDocument().recompute()"); // recompute the feature based on its references
|
|
doCommand(Gui::Command::Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str());
|
|
}
|
|
// TODO do a proper error processing (2015-09-11, Fat-Zer)
|
|
}
|
|
|
|
bool CmdPartDesignShapeBinder::isActive(void)
|
|
{
|
|
return hasActiveDocument ();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Sketch
|
|
//===========================================================================
|
|
|
|
/* Sketch commands =======================================================*/
|
|
DEF_STD_CMD_A(CmdPartDesignNewSketch);
|
|
|
|
CmdPartDesignNewSketch::CmdPartDesignNewSketch()
|
|
:Command("PartDesign_NewSketch")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Create sketch");
|
|
sToolTipText = QT_TR_NOOP("Create a new sketch");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "Sketcher_NewSketch";
|
|
}
|
|
|
|
|
|
void CmdPartDesignNewSketch::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
App::Document *doc = getDocument ();
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(
|
|
/*messageIfNot = */ PartDesignGui::assureModernWorkflow ( doc ) );
|
|
|
|
// No PartDesign feature without Body past FreeCAD 0.13
|
|
if ( !pcActiveBody ) {
|
|
// Call normal sketch command for old workflow
|
|
if ( PartDesignGui::isLegacyWorkflow ( doc) ) {
|
|
Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
rcCmdMgr.runCommandByName("Sketcher_NewSketch");
|
|
}
|
|
return;
|
|
}
|
|
|
|
Gui::SelectionFilter SketchFilter("SELECT Sketcher::SketchObject COUNT 1");
|
|
Gui::SelectionFilter FaceFilter ("SELECT Part::Feature SUBELEMENT Face COUNT 1");
|
|
Gui::SelectionFilter PlaneFilter ("SELECT App::Plane COUNT 1");
|
|
Gui::SelectionFilter PlaneFilter2("SELECT PartDesign::Plane COUNT 1");
|
|
if (PlaneFilter2.match())
|
|
PlaneFilter = PlaneFilter2;
|
|
|
|
if (SketchFilter.match()) {
|
|
Sketcher::SketchObject *Sketch = static_cast<Sketcher::SketchObject*>(SketchFilter.Result[0][0].getObject());
|
|
openCommand("Edit Sketch");
|
|
doCommand(Gui,"Gui.activeDocument().setEdit('%s')",Sketch->getNameInDocument());
|
|
}
|
|
else if (FaceFilter.match() || PlaneFilter.match()) {
|
|
// get the selected object
|
|
std::string supportString;
|
|
App::DocumentObject* obj;
|
|
|
|
if (FaceFilter.match()) {
|
|
obj = FaceFilter.Result[0][0].getObject();
|
|
|
|
if(!obj->isDerivedFrom(Part::Feature::getClassTypeId()))
|
|
return;
|
|
|
|
Part::Feature* feat = static_cast<Part::Feature*>(obj);
|
|
|
|
const std::vector<std::string> &sub = FaceFilter.Result[0][0].getSubNames();
|
|
if (sub.size() > 1) {
|
|
// No assert for wrong user input!
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Several sub-elements selected"),
|
|
QObject::tr("You have to select a single face as support for a sketch!"));
|
|
return;
|
|
}
|
|
|
|
// get the selected sub shape (a Face)
|
|
const Part::TopoShape &shape = feat->Shape.getValue();
|
|
TopoDS_Shape sh = shape.getSubShape(sub[0].c_str());
|
|
const TopoDS_Face& face = TopoDS::Face(sh);
|
|
if (face.IsNull()) {
|
|
// No assert for wrong user input!
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No support face selected"),
|
|
QObject::tr("You have to select a face as support for a sketch!"));
|
|
return;
|
|
}
|
|
|
|
BRepAdaptor_Surface adapt(face);
|
|
if (adapt.GetType() != GeomAbs_Plane){
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No planar support"),
|
|
QObject::tr("You need a planar face as support for a sketch!"));
|
|
return;
|
|
}
|
|
|
|
supportString = FaceFilter.Result[0][0].getAsPropertyLinkSubString();
|
|
} else {
|
|
obj = static_cast<Part::Feature*>(PlaneFilter.Result[0][0].getObject());
|
|
supportString = std::string("(App.activeDocument().") + obj->getNameInDocument() + ", '')";
|
|
}
|
|
|
|
if (!pcActiveBody->hasFeature(obj)) {
|
|
if ( !obj->isDerivedFrom ( App::Plane::getClassTypeId() ) ) {
|
|
// TODO check here if the plane associated with right part/body (2015-09-01, Fat-Zer)
|
|
|
|
auto pcActivePart = PartDesignGui::getPartFor(pcActiveBody, false);
|
|
|
|
//check the prerequisites for the selected objects
|
|
//the user has to decide which option we should take if external references are used
|
|
// TODO share this with UnifiedDatumCommand() (2015-10-20, Fat-Zer)
|
|
QDialog* dia = new QDialog;
|
|
Ui_Dialog dlg;
|
|
dlg.setupUi(dia);
|
|
dia->setModal(true);
|
|
int result = dia->exec();
|
|
if(result == QDialog::DialogCode::Rejected)
|
|
return;
|
|
else if(!dlg.radioXRef->isChecked()) {
|
|
|
|
std::string sub;
|
|
if(FaceFilter.match())
|
|
sub = FaceFilter.Result[0][0].getSubNames()[0];
|
|
auto copy = PartDesignGui::TaskFeaturePick::makeCopy(obj, sub, dlg.radioIndependent->isChecked());
|
|
|
|
if(pcActiveBody)
|
|
pcActiveBody->addFeature(copy);
|
|
else if (pcActivePart)
|
|
pcActivePart->addObject(copy);
|
|
|
|
if(PlaneFilter.match())
|
|
supportString = std::string("(App.activeDocument().") + copy->getNameInDocument() + ", '')";
|
|
else
|
|
//it is ensured that only a single face is selected, hence it must always be Face1 of the shapebinder
|
|
supportString = std::string("(App.activeDocument().") + copy->getNameInDocument() + ", 'Face1')";
|
|
}
|
|
}
|
|
}
|
|
|
|
// create Sketch on Face or Plane
|
|
std::string FeatName = getUniqueObjectName("Sketch");
|
|
|
|
openCommand("Create a Sketch on Face");
|
|
doCommand(Doc,"App.activeDocument().addObject('Sketcher::SketchObject','%s')",FeatName.c_str());
|
|
doCommand(Doc,"App.activeDocument().%s.Support = %s",FeatName.c_str(),supportString.c_str());
|
|
doCommand(Doc,"App.activeDocument().%s.MapMode = '%s'",FeatName.c_str(),Attacher::AttachEngine::getModeName(Attacher::mmFlatFace).c_str());
|
|
doCommand(Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)",
|
|
pcActiveBody->getNameInDocument(), FeatName.c_str());
|
|
doCommand(Gui,"App.activeDocument().recompute()"); // recompute the sketch placement based on its support
|
|
//doCommand(Gui,"Gui.activeDocument().activeView().setCamera('%s')",cam.c_str());
|
|
doCommand(Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str());
|
|
}
|
|
else {
|
|
// Get a valid plane from the user
|
|
unsigned validPlanes = 0;
|
|
|
|
auto group = App::GeoFeatureGroupExtension::getGroupOfObject ( pcActiveBody );
|
|
App::GeoFeatureGroupExtension* geoGroup = nullptr;
|
|
if(group)
|
|
geoGroup = group->getExtensionByType<App::GeoFeatureGroupExtension>();
|
|
|
|
std::vector<App::DocumentObject*> planes;
|
|
std::vector<PartDesignGui::TaskFeaturePick::featureStatus> status;
|
|
|
|
// Baseplanes are preaprooved
|
|
if ( pcActiveBody ) {
|
|
try {
|
|
for ( auto plane: pcActiveBody->getOrigin ()->planes() ) {
|
|
planes.push_back (plane);
|
|
status.push_back(PartDesignGui::TaskFeaturePick::basePlane);
|
|
validPlanes++;
|
|
}
|
|
} catch (const Base::Exception &ex) {
|
|
Base::Console().Error ("%s\n", ex.what() );
|
|
}
|
|
}
|
|
|
|
std::vector<App::DocumentObject*> datumPlanes =
|
|
getDocument()->getObjectsOfType(PartDesign::Plane::getClassTypeId());
|
|
|
|
for (auto plane: datumPlanes) {
|
|
planes.push_back ( plane );
|
|
// Check whether this plane belongs to the active body
|
|
if ( pcActiveBody && pcActiveBody->hasFeature(plane) ) {
|
|
if ( !pcActiveBody->isAfterInsertPoint ( plane ) ) {
|
|
validPlanes++;
|
|
status.push_back(PartDesignGui::TaskFeaturePick::validFeature);
|
|
} else {
|
|
status.push_back(PartDesignGui::TaskFeaturePick::afterTip);
|
|
}
|
|
} else {
|
|
PartDesign::Body *planeBody = PartDesign::Body::findBodyOf (plane);
|
|
if ( planeBody ) {
|
|
if ( ( geoGroup && geoGroup->hasObject ( planeBody, true ) ) ||
|
|
!App::GeoFeatureGroupExtension::getGroupOfObject (planeBody) ) {
|
|
status.push_back ( PartDesignGui::TaskFeaturePick::otherBody );
|
|
} else {
|
|
status.push_back ( PartDesignGui::TaskFeaturePick::otherPart );
|
|
}
|
|
} else {
|
|
if ( ( geoGroup && geoGroup->hasObject ( plane, true ) ) ||
|
|
!App::GeoFeatureGroupExtension::getGroupOfObject ( plane ) ) {
|
|
status.push_back ( PartDesignGui::TaskFeaturePick::otherPart );
|
|
} else if (pcActiveBody) {
|
|
status.push_back ( PartDesignGui::TaskFeaturePick::notInBody );
|
|
} else { // if we are outside a body count it as valid
|
|
validPlanes++;
|
|
status.push_back(PartDesignGui::TaskFeaturePick::validFeature);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (validPlanes == 0) {
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No valid planes in this document"),
|
|
QObject::tr("Please create a plane first or select a face to sketch on"));
|
|
return;
|
|
}
|
|
|
|
auto accepter = [=](const std::vector<App::DocumentObject*>& features) -> bool {
|
|
|
|
if(features.empty())
|
|
return false;
|
|
|
|
return true;
|
|
};
|
|
|
|
auto worker = [=](const std::vector<App::DocumentObject*>& features) {
|
|
App::Plane* plane = static_cast<App::Plane*>(features.front());
|
|
std::string FeatName = getUniqueObjectName("Sketch");
|
|
std::string supportString = std::string("(App.activeDocument().") + plane->getNameInDocument() +
|
|
", [''])";
|
|
|
|
Gui::Command::openCommand("Create a new Sketch");
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().addObject('Sketcher::SketchObject','%s')",FeatName.c_str());
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.Support = %s",FeatName.c_str(),supportString.c_str());
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.MapMode = '%s'",FeatName.c_str(),Attacher::AttachEngine::getModeName(Attacher::mmFlatFace).c_str());
|
|
Gui::Command::updateActive(); // Make sure the Support's Placement property is updated
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)",
|
|
pcActiveBody->getNameInDocument(), FeatName.c_str());
|
|
//doCommand(Gui,"Gui.activeDocument().activeView().setCamera('%s')",cam.c_str());
|
|
Gui::Command::doCommand(Gui,"Gui.activeDocument().setEdit('%s')",FeatName.c_str());
|
|
};
|
|
|
|
// If there is more than one possibility, show dialog and let user pick plane
|
|
if (validPlanes > 1) {
|
|
|
|
Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog();
|
|
PartDesignGui::TaskDlgFeaturePick *pickDlg = qobject_cast<PartDesignGui::TaskDlgFeaturePick *>(dlg);
|
|
if (dlg && !pickDlg) {
|
|
QMessageBox msgBox;
|
|
msgBox.setText(QObject::tr("A dialog is already open in the task panel"));
|
|
msgBox.setInformativeText(QObject::tr("Do you want to close this dialog?"));
|
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
msgBox.setDefaultButton(QMessageBox::Yes);
|
|
int ret = msgBox.exec();
|
|
if (ret == QMessageBox::Yes)
|
|
Gui::Control().closeDialog();
|
|
else
|
|
return;
|
|
}
|
|
|
|
if(dlg)
|
|
Gui::Control().closeDialog();
|
|
|
|
Gui::Selection().clearSelection();
|
|
Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(planes, status, accepter, worker));
|
|
}
|
|
else {
|
|
worker(planes);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CmdPartDesignNewSketch::isActive(void)
|
|
{
|
|
if (getActiveGuiDocument())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
//===========================================================================
|
|
// Common utility functions for all features creating solids
|
|
//===========================================================================
|
|
|
|
void finishFeature(const Gui::Command* cmd, const std::string& FeatName,
|
|
App::DocumentObject* prevSolidFeature = nullptr, const bool hidePrevSolid = true)
|
|
{
|
|
PartDesign::Body *pcActiveBody;
|
|
|
|
if (prevSolidFeature) {
|
|
pcActiveBody = PartDesignGui::getBodyFor(prevSolidFeature, /*messageIfNot = */false);
|
|
} else { // insert into the same body as the given previous one
|
|
pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */false);
|
|
}
|
|
|
|
if (pcActiveBody) {
|
|
App::DocumentObject* lastSolidFeature = pcActiveBody->Tip.getValue();
|
|
if (!prevSolidFeature || prevSolidFeature == lastSolidFeature) {
|
|
// If the previous feature not given or is the Tip add Feature after it.
|
|
cmd->doCommand(cmd->Doc,"App.activeDocument().%s.addFeature(App.activeDocument().%s)",
|
|
pcActiveBody->getNameInDocument(), FeatName.c_str());
|
|
prevSolidFeature = lastSolidFeature;
|
|
} else {
|
|
// Insert the feature into the body after the given one.
|
|
cmd->doCommand(cmd->Doc,
|
|
"App.activeDocument().%s.insertFeature(App.activeDocument().%s, App.activeDocument().%s, True)",
|
|
pcActiveBody->getNameInDocument(), FeatName.c_str(), prevSolidFeature->getNameInDocument());
|
|
}
|
|
}
|
|
|
|
if (hidePrevSolid && prevSolidFeature && (prevSolidFeature != NULL))
|
|
cmd->doCommand(cmd->Gui,"Gui.activeDocument().hide(\"%s\")", prevSolidFeature->getNameInDocument());
|
|
|
|
cmd->updateActive();
|
|
// #0001721: use '0' as edit value to avoid switching off selection in
|
|
// ViewProviderGeometryObject::setEditViewer
|
|
cmd->doCommand(cmd->Gui,"Gui.activeDocument().setEdit('%s', 0)", FeatName.c_str());
|
|
cmd->doCommand(cmd->Gui,"Gui.Selection.clearSelection()");
|
|
//cmd->doCommand(cmd->Gui,"Gui.Selection.addSelection(App.ActiveDocument.ActiveObject)");
|
|
|
|
if (pcActiveBody) {
|
|
cmd->copyVisual(FeatName.c_str(), "ShapeColor", pcActiveBody->getNameInDocument());
|
|
cmd->copyVisual(FeatName.c_str(), "LineColor", pcActiveBody->getNameInDocument());
|
|
cmd->copyVisual(FeatName.c_str(), "PointColor", pcActiveBody->getNameInDocument());
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
// Common utility functions for ProfileBased features
|
|
//===========================================================================
|
|
|
|
// Take a list of Part2DObjects and classify them for creating a
|
|
// ProfileBased feature. FirstFreeSketch is the first free sketch in the same body
|
|
// or sketches.end() if non available. The returned number is the amount of free sketches
|
|
unsigned validateSketches(std::vector<App::DocumentObject*>& sketches,
|
|
std::vector<PartDesignGui::TaskFeaturePick::featureStatus>& status,
|
|
std::vector<App::DocumentObject*>::iterator& firstFreeSketch)
|
|
{
|
|
// TODO Review the function for non-part bodies (2015-09-04, Fat-Zer)
|
|
PartDesign::Body* pcActiveBody = PartDesignGui::getBody(false);
|
|
App::Part* pcActivePart = PartDesignGui::getPartFor(pcActiveBody, false);
|
|
|
|
// TODO: If the user previously opted to allow multiple use of sketches or use of sketches from other bodies,
|
|
// then count these as valid sketches!
|
|
unsigned freeSketches = 0;
|
|
firstFreeSketch = sketches.end();
|
|
|
|
for (std::vector<App::DocumentObject*>::iterator s = sketches.begin(); s != sketches.end(); s++) {
|
|
|
|
if (!pcActiveBody) {
|
|
// We work in the old style outside any body
|
|
if (PartDesign::Body::findBodyOf (*s)) {
|
|
status.push_back(PartDesignGui::TaskFeaturePick::otherPart);
|
|
continue;
|
|
}
|
|
} else if (!pcActiveBody->hasFeature(*s)) {
|
|
// Check whether this plane belongs to a body of the same part
|
|
PartDesign::Body* b = PartDesign::Body::findBodyOf(*s);
|
|
if(!b)
|
|
status.push_back(PartDesignGui::TaskFeaturePick::notInBody);
|
|
else if(pcActivePart && pcActivePart->hasObject(b, true))
|
|
status.push_back(PartDesignGui::TaskFeaturePick::otherBody);
|
|
else
|
|
status.push_back(PartDesignGui::TaskFeaturePick::otherPart);
|
|
|
|
continue;
|
|
}
|
|
|
|
//Base::Console().Error("Checking sketch %s\n", (*s)->getNameInDocument());
|
|
// Check whether this sketch is already being used by another feature
|
|
// Body features don't count...
|
|
std::vector<App::DocumentObject*> inList = (*s)->getInList();
|
|
std::vector<App::DocumentObject*>::iterator o = inList.begin();
|
|
while (o != inList.end()) {
|
|
//Base::Console().Error("Inlist: %s\n", (*o)->getNameInDocument());
|
|
if ((*o)->getTypeId().isDerivedFrom(PartDesign::Body::getClassTypeId()))
|
|
o = inList.erase(o); //ignore bodies
|
|
else if (!( (*o)->getTypeId().isDerivedFrom(PartDesign::Feature::getClassTypeId()) ))
|
|
o = inList.erase(o); //ignore non-partDesign
|
|
else
|
|
++o;
|
|
}
|
|
if (inList.size() > 0) {
|
|
status.push_back(PartDesignGui::TaskFeaturePick::isUsed);
|
|
continue;
|
|
}
|
|
|
|
if (pcActiveBody && pcActiveBody->isAfterInsertPoint(*s)){
|
|
status.push_back(PartDesignGui::TaskFeaturePick::afterTip);
|
|
continue;
|
|
}
|
|
|
|
// Check whether the sketch shape is valid
|
|
Part::Part2DObject* sketch = static_cast<Part::Part2DObject*>(*s);
|
|
const TopoDS_Shape& shape = sketch->Shape.getValue();
|
|
if (shape.IsNull()) {
|
|
status.push_back(PartDesignGui::TaskFeaturePick::invalidShape);
|
|
continue;
|
|
}
|
|
|
|
// count free wires
|
|
int ctWires=0;
|
|
TopExp_Explorer ex;
|
|
for (ex.Init(shape, TopAbs_WIRE); ex.More(); ex.Next()) {
|
|
ctWires++;
|
|
}
|
|
if (ctWires == 0) {
|
|
status.push_back(PartDesignGui::TaskFeaturePick::noWire);
|
|
continue;
|
|
}
|
|
|
|
// All checks passed - found a valid sketch
|
|
if (firstFreeSketch == sketches.end())
|
|
firstFreeSketch = s;
|
|
freeSketches++;
|
|
status.push_back(PartDesignGui::TaskFeaturePick::validFeature);
|
|
}
|
|
|
|
return freeSketches;
|
|
}
|
|
|
|
void prepareProfileBased(Gui::Command* cmd, const std::string& which,
|
|
boost::function<void (Part::Feature*, std::string)> func)
|
|
{
|
|
auto base_worker = [which, cmd, func](App::DocumentObject* feature, std::string sub) {
|
|
|
|
if (!feature || !feature->isDerivedFrom(Part::Feature::getClassTypeId()))
|
|
return;
|
|
|
|
// Related to #0002760: when an operation can't be performed due to a broken
|
|
// profile then make sure that it is recomputed when cancelling the operation
|
|
// otherwise it might be impossible to see that it's broken.
|
|
if (feature->isTouched())
|
|
feature->recomputeFeature();
|
|
|
|
std::string FeatName = cmd->getUniqueObjectName(which.c_str());
|
|
|
|
Gui::Command::openCommand((std::string("Make ") + which).c_str());
|
|
Gui::Command::doCommand(Gui::Command::Doc,"App.activeDocument().addObject(\"PartDesign::%s\",\"%s\")",
|
|
which.c_str(), FeatName.c_str());
|
|
|
|
if (feature->isDerivedFrom(Part::Part2DObject::getClassTypeId())) {
|
|
Gui::Command::doCommand(Gui::Command::Doc,"App.activeDocument().%s.Profile = App.activeDocument().%s",
|
|
FeatName.c_str(), feature->getNameInDocument());
|
|
}
|
|
else {
|
|
Gui::Command::doCommand(Gui::Command::Doc,"App.activeDocument().%s.Profile = (App.activeDocument().%s, [\"%s\"])",
|
|
FeatName.c_str(), feature->getNameInDocument(), sub.c_str());
|
|
}
|
|
|
|
func(static_cast<Part::Feature*>(feature), FeatName);
|
|
};
|
|
|
|
//if a profie is selected we can make our life easy and fast
|
|
auto selection = cmd->getSelection().getSelectionEx();
|
|
if (!selection.empty() && selection.front().hasSubNames()) {
|
|
base_worker(selection.front().getObject(), selection.front().getSubNames().front());
|
|
return;
|
|
}
|
|
|
|
//no face profile was selected, do he extended sketch logic
|
|
|
|
bool bNoSketchWasSelected = false;
|
|
// Get a valid sketch from the user
|
|
// First check selections
|
|
std::vector<App::DocumentObject*> sketches = cmd->getSelection().getObjectsOfType(Part::Part2DObject::getClassTypeId());
|
|
if (sketches.empty()) {//no sketches were selected. Let user pick an object from valid ones available in document
|
|
sketches = cmd->getDocument()->getObjectsOfType(Part::Part2DObject::getClassTypeId());
|
|
bNoSketchWasSelected = true;
|
|
}
|
|
|
|
if (sketches.empty()) {
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No sketch to work on"),
|
|
QObject::tr("No sketch is available in the document"));
|
|
return;
|
|
}
|
|
|
|
std::vector<PartDesignGui::TaskFeaturePick::featureStatus> status;
|
|
std::vector<App::DocumentObject*>::iterator firstFreeSketch;
|
|
int freeSketches = validateSketches(sketches, status, firstFreeSketch);
|
|
|
|
auto accepter = [=](const std::vector<App::DocumentObject*>& features) -> bool {
|
|
|
|
if(features.empty())
|
|
return false;
|
|
|
|
return true;
|
|
};
|
|
|
|
auto sketch_worker = [&](std::vector<App::DocumentObject*> features) {
|
|
|
|
base_worker(features.front(), "");
|
|
};
|
|
|
|
//if there is a sketch selected which is from annother body or part we need to bring up the
|
|
//pick task dialog to decide how those are handled
|
|
bool ext = std::find_if( status.begin(), status.end(),
|
|
[] (const PartDesignGui::TaskFeaturePick::featureStatus& s) {
|
|
return s == PartDesignGui::TaskFeaturePick::otherBody ||
|
|
s == PartDesignGui::TaskFeaturePick::otherPart ||
|
|
s == PartDesignGui::TaskFeaturePick::notInBody;
|
|
}
|
|
) != status.end();
|
|
|
|
// TODO Clean this up (2015-10-20, Fat-Zer)
|
|
auto* pcActiveBody = PartDesignGui::getBody(false);
|
|
if (pcActiveBody && !bNoSketchWasSelected && ext) {
|
|
|
|
auto* pcActivePart = PartDesignGui::getPartFor(pcActiveBody, false);
|
|
|
|
QDialog* dia = new QDialog;
|
|
Ui_Dialog dlg;
|
|
dlg.setupUi(dia);
|
|
dia->setModal(true);
|
|
int result = dia->exec();
|
|
if(result == QDialog::DialogCode::Rejected)
|
|
return;
|
|
else if(!dlg.radioXRef->isChecked()) {
|
|
|
|
auto copy = PartDesignGui::TaskFeaturePick::makeCopy(sketches[0], "", dlg.radioIndependent->isChecked());
|
|
auto oBody = PartDesignGui::getBodyFor(sketches[0], false);
|
|
if(oBody)
|
|
pcActiveBody->addFeature(copy);
|
|
else
|
|
pcActivePart->addObject(copy);
|
|
|
|
sketches[0] = copy;
|
|
firstFreeSketch = sketches.begin();
|
|
}
|
|
}
|
|
|
|
// Show sketch choose dialog and let user pick sketch if no sketch was selected and no free one available or
|
|
// multiple free ones are available
|
|
if (bNoSketchWasSelected && (freeSketches != 1) ) {
|
|
|
|
Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog();
|
|
PartDesignGui::TaskDlgFeaturePick *pickDlg = qobject_cast<PartDesignGui::TaskDlgFeaturePick *>(dlg);
|
|
if (dlg && !pickDlg) {
|
|
QMessageBox msgBox;
|
|
msgBox.setText(QObject::tr("A dialog is already open in the task panel"));
|
|
msgBox.setInformativeText(QObject::tr("Do you want to close this dialog?"));
|
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
msgBox.setDefaultButton(QMessageBox::Yes);
|
|
int ret = msgBox.exec();
|
|
if (ret == QMessageBox::Yes)
|
|
Gui::Control().closeDialog();
|
|
else
|
|
return;
|
|
}
|
|
|
|
if(dlg)
|
|
Gui::Control().closeDialog();
|
|
|
|
Gui::Selection().clearSelection();
|
|
pickDlg = new PartDesignGui::TaskDlgFeaturePick(sketches, status, accepter, sketch_worker);
|
|
if (!bNoSketchWasSelected && ext)
|
|
pickDlg->showExternal(true);
|
|
|
|
Gui::Control().showDialog(pickDlg);
|
|
}
|
|
else {
|
|
std::vector<App::DocumentObject*> theSketch;
|
|
if (!bNoSketchWasSelected)
|
|
theSketch.push_back(sketches[0]);
|
|
else
|
|
theSketch.push_back(*firstFreeSketch);
|
|
|
|
sketch_worker(theSketch);
|
|
}
|
|
}
|
|
|
|
void finishProfileBased(const Gui::Command* cmd, const Part::Feature* sketch, const std::string& FeatName)
|
|
{
|
|
if(sketch && sketch->isDerivedFrom(Part::Part2DObject::getClassTypeId()))
|
|
cmd->doCommand(cmd->Gui,"Gui.activeDocument().hide(\"%s\")", sketch->getNameInDocument());
|
|
finishFeature(cmd, FeatName);
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Pad
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignPad);
|
|
|
|
CmdPartDesignPad::CmdPartDesignPad()
|
|
: Command("PartDesign_Pad")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Pad");
|
|
sToolTipText = QT_TR_NOOP("Pad a selected sketch");
|
|
sWhatsThis = "PartDesign_Pad";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Pad";
|
|
}
|
|
|
|
void CmdPartDesignPad::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
App::Document *doc = getDocument();
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(
|
|
/*messageIfNot = */ PartDesignGui::assureModernWorkflow(doc));
|
|
|
|
// No PartDesign feature without Body past FreeCAD 0.16
|
|
if (!pcActiveBody && PartDesignGui::isModernWorkflow(doc))
|
|
return;
|
|
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](Part::Feature* profile, std::string FeatName) {
|
|
|
|
if (FeatName.empty()) return;
|
|
|
|
// specific parameters for Pad
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.Length = 10.0",FeatName.c_str());
|
|
App::DocumentObjectGroup* grp = profile->getGroup();
|
|
if (grp) {
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.addObject(App.activeDocument().%s)"
|
|
,grp->getNameInDocument(),FeatName.c_str());
|
|
if(profile->isDerivedFrom(Part::Part2DObject::getClassTypeId()))
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.removeObject(App.activeDocument().%s)"
|
|
,grp->getNameInDocument(), profile->getNameInDocument());
|
|
}
|
|
Gui::Command::updateActive();
|
|
|
|
Part::Part2DObject* sketch = dynamic_cast<Part::Part2DObject*>(profile);
|
|
finishProfileBased(cmd, sketch, FeatName);
|
|
cmd->adjustCameraPosition();
|
|
};
|
|
|
|
prepareProfileBased(this, "Pad", worker);
|
|
}
|
|
|
|
bool CmdPartDesignPad::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Pocket
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignPocket);
|
|
|
|
CmdPartDesignPocket::CmdPartDesignPocket()
|
|
: Command("PartDesign_Pocket")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Pocket");
|
|
sToolTipText = QT_TR_NOOP("Create a pocket with the selected sketch");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Pocket";
|
|
}
|
|
|
|
void CmdPartDesignPocket::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
App::Document *doc = getDocument();
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(
|
|
/*messageIfNot = */ PartDesignGui::assureModernWorkflow(doc));
|
|
|
|
// No PartDesign feature without Body past FreeCAD 0.16
|
|
if (!pcActiveBody && PartDesignGui::isModernWorkflow(doc))
|
|
return;
|
|
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](Part::Feature* sketch, std::string FeatName) {
|
|
|
|
if (FeatName.empty()) return;
|
|
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.Length = 5.0",FeatName.c_str());
|
|
finishProfileBased(cmd, sketch, FeatName);
|
|
cmd->adjustCameraPosition();
|
|
};
|
|
|
|
prepareProfileBased(this, "Pocket", worker);
|
|
}
|
|
|
|
bool CmdPartDesignPocket::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Revolution
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignRevolution);
|
|
|
|
CmdPartDesignRevolution::CmdPartDesignRevolution()
|
|
: Command("PartDesign_Revolution")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Revolution");
|
|
sToolTipText = QT_TR_NOOP("Revolve a selected sketch");
|
|
sWhatsThis = "PartDesign_Revolution";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Revolution";
|
|
}
|
|
|
|
void CmdPartDesignRevolution::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
App::Document *doc = getDocument();
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(
|
|
/*messageIfNot = */ PartDesignGui::assureModernWorkflow(doc));
|
|
|
|
// No PartDesign feature without Body past FreeCAD 0.16
|
|
if (!pcActiveBody && PartDesignGui::isModernWorkflow(doc))
|
|
return;
|
|
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](Part::Feature* sketch, std::string FeatName) {
|
|
|
|
if (FeatName.empty()) return;
|
|
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.ReferenceAxis = (App.activeDocument().%s,['V_Axis'])",
|
|
FeatName.c_str(), sketch->getNameInDocument());
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.Angle = 360.0",FeatName.c_str());
|
|
PartDesign::Revolution* pcRevolution = static_cast<PartDesign::Revolution*>(cmd->getDocument()->getObject(FeatName.c_str()));
|
|
if (pcRevolution && pcRevolution->suggestReversed())
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.Reversed = 1",FeatName.c_str());
|
|
|
|
finishProfileBased(cmd, sketch, FeatName);
|
|
cmd->adjustCameraPosition();
|
|
};
|
|
|
|
prepareProfileBased(this, "Revolution", worker);
|
|
}
|
|
|
|
bool CmdPartDesignRevolution::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Groove
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignGroove);
|
|
|
|
CmdPartDesignGroove::CmdPartDesignGroove()
|
|
: Command("PartDesign_Groove")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Groove");
|
|
sToolTipText = QT_TR_NOOP("Groove a selected sketch");
|
|
sWhatsThis = "PartDesign_Groove";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Groove";
|
|
}
|
|
|
|
void CmdPartDesignGroove::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
App::Document *doc = getDocument();
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(
|
|
/*messageIfNot = */ PartDesignGui::assureModernWorkflow(doc));
|
|
|
|
if (!pcActiveBody && PartDesignGui::isModernWorkflow(doc))
|
|
return;
|
|
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](Part::Feature* sketch, std::string FeatName) {
|
|
|
|
if (FeatName.empty()) return;
|
|
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.ReferenceAxis = (App.activeDocument().%s,['V_Axis'])",
|
|
FeatName.c_str(), sketch->getNameInDocument());
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.Angle = 360.0",FeatName.c_str());
|
|
PartDesign::Groove* pcGroove = static_cast<PartDesign::Groove*>(cmd->getDocument()->getObject(FeatName.c_str()));
|
|
if (pcGroove && pcGroove->suggestReversed())
|
|
Gui::Command::doCommand(Doc,"App.activeDocument().%s.Reversed = 1",FeatName.c_str());
|
|
|
|
finishProfileBased(cmd, sketch, FeatName);
|
|
cmd->adjustCameraPosition();
|
|
};
|
|
|
|
prepareProfileBased(this, "Groove", worker);
|
|
}
|
|
|
|
bool CmdPartDesignGroove::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Additive_Pipe
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignAdditivePipe);
|
|
|
|
CmdPartDesignAdditivePipe::CmdPartDesignAdditivePipe()
|
|
: Command("PartDesign_AdditivePipe")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Additive pipe");
|
|
sToolTipText = QT_TR_NOOP("Sweep a selected sketch along a path or to other profiles");
|
|
sWhatsThis = "PartDesign_Additive_Pipe";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Additive_Pipe";
|
|
}
|
|
|
|
void CmdPartDesignAdditivePipe::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */ true);
|
|
|
|
// No PartDesign feature without Body past FreeCAD 0.13
|
|
if (!pcActiveBody)
|
|
return;
|
|
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](Part::Feature* sketch, std::string FeatName) {
|
|
|
|
if (FeatName.empty()) return;
|
|
|
|
// specific parameters for pipe
|
|
Gui::Command::updateActive();
|
|
|
|
finishProfileBased(cmd, sketch, FeatName);
|
|
cmd->adjustCameraPosition();
|
|
};
|
|
|
|
prepareProfileBased(this, "AdditivePipe", worker);
|
|
}
|
|
|
|
bool CmdPartDesignAdditivePipe::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// PartDesign_Subtractive_Pipe
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignSubtractivePipe);
|
|
|
|
CmdPartDesignSubtractivePipe::CmdPartDesignSubtractivePipe()
|
|
: Command("PartDesign_SubtractivePipe")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Subtractive pipe");
|
|
sToolTipText = QT_TR_NOOP("Sweep a selected sketch along a path or to other profiles and remove it from the body");
|
|
sWhatsThis = "PartDesign_Subtractive_Pipe";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Subtractive_Pipe";
|
|
}
|
|
|
|
void CmdPartDesignSubtractivePipe::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */ true);
|
|
|
|
// No PartDesign feature without Body past FreeCAD 0.13
|
|
if (!pcActiveBody)
|
|
return;
|
|
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](Part::Feature* sketch, std::string FeatName) {
|
|
|
|
if (FeatName.empty()) return;
|
|
|
|
// specific parameters for pipe
|
|
Gui::Command::updateActive();
|
|
|
|
finishProfileBased(cmd, sketch, FeatName);
|
|
cmd->adjustCameraPosition();
|
|
};
|
|
|
|
prepareProfileBased(this, "SubtractivePipe", worker);
|
|
}
|
|
|
|
bool CmdPartDesignSubtractivePipe::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// PartDesign_Additive_Loft
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignAdditiveLoft);
|
|
|
|
CmdPartDesignAdditiveLoft::CmdPartDesignAdditiveLoft()
|
|
: Command("PartDesign_AdditiveLoft")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Additive loft");
|
|
sToolTipText = QT_TR_NOOP("Sweep a selected sketch along a path or to other profiles");
|
|
sWhatsThis = "PartDesign_Additive_Loft";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Additive_Loft";
|
|
}
|
|
|
|
void CmdPartDesignAdditiveLoft::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */ true);
|
|
|
|
// No PartDesign feature without Body past FreeCAD 0.13
|
|
if (!pcActiveBody)
|
|
return;
|
|
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](Part::Feature* sketch, std::string FeatName) {
|
|
|
|
if (FeatName.empty()) return;
|
|
|
|
// specific parameters for pipe
|
|
Gui::Command::updateActive();
|
|
|
|
finishProfileBased(cmd, sketch, FeatName);
|
|
cmd->adjustCameraPosition();
|
|
};
|
|
|
|
prepareProfileBased(this, "AdditiveLoft", worker);
|
|
}
|
|
|
|
bool CmdPartDesignAdditiveLoft::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// PartDesign_Subtractive_Loft
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignSubtractiveLoft);
|
|
|
|
CmdPartDesignSubtractiveLoft::CmdPartDesignSubtractiveLoft()
|
|
: Command("PartDesign_SubtractiveLoft")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Subtractive loft");
|
|
sToolTipText = QT_TR_NOOP("Sweep a selected sketch along a path or to other profiles and remove it from the body");
|
|
sWhatsThis = "PartDesign_Subtractive_Loft";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Subtractive_Loft";
|
|
}
|
|
|
|
void CmdPartDesignSubtractiveLoft::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */ true);
|
|
|
|
// No PartDesign feature without Body past FreeCAD 0.13
|
|
if (!pcActiveBody)
|
|
return;
|
|
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](Part::Feature* sketch, std::string FeatName) {
|
|
|
|
if (FeatName.empty()) return;
|
|
|
|
// specific parameters for pipe
|
|
Gui::Command::updateActive();
|
|
|
|
finishProfileBased(cmd, sketch, FeatName);
|
|
cmd->adjustCameraPosition();
|
|
};
|
|
|
|
prepareProfileBased(this, "SubtractiveLoft", worker);
|
|
}
|
|
|
|
bool CmdPartDesignSubtractiveLoft::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// Common utility functions for Dressup features
|
|
//===========================================================================
|
|
|
|
bool dressupGetSelected(Gui::Command* cmd, const std::string& which,
|
|
Gui::SelectionObject &selected)
|
|
{
|
|
std::vector<Gui::SelectionObject> selection = cmd->getSelection().getSelectionEx();
|
|
selection = cmd->getSelection().getSelectionEx();
|
|
|
|
if (selection.size() == 0) {
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge, face or body."));
|
|
return false;
|
|
} else if (selection.size() != 1) {
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"),
|
|
QObject::tr("Select an edge, face or body from a single body."));
|
|
return false;
|
|
}
|
|
|
|
Gui::Selection().clearSelection();
|
|
|
|
// set the
|
|
selected = selection[0];
|
|
|
|
if (!selected.isObjectTypeOf(Part::Feature::getClassTypeId())) {
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong object type"),
|
|
QObject::tr("%1 works only on parts.").arg(QString::fromStdString(which)));
|
|
return false;
|
|
}
|
|
|
|
Part::Feature *base = static_cast<Part::Feature*>(selected.getObject());
|
|
|
|
const Part::TopoShape& TopShape = base->Shape.getShape();
|
|
|
|
if (TopShape.getShape().IsNull()){
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"),
|
|
QObject::tr("Shape of the selected Part is empty"));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void finishDressupFeature(const Gui::Command* cmd, const std::string& which,
|
|
Part::Feature *base, const std::vector<std::string> & SubNames)
|
|
{
|
|
if (SubNames.size() == 0) {
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Wrong selection"),
|
|
QString::fromStdString(which) + QObject::tr(" not possible on selected faces/edges."));
|
|
return;
|
|
}
|
|
|
|
std::string SelString;
|
|
SelString += "(App.";
|
|
SelString += "ActiveDocument";
|
|
SelString += ".";
|
|
SelString += base->getNameInDocument();
|
|
SelString += ",[";
|
|
for(std::vector<std::string>::const_iterator it = SubNames.begin();it!=SubNames.end();++it){
|
|
SelString += "\"";
|
|
SelString += *it;
|
|
SelString += "\"";
|
|
if(it != --SubNames.end())
|
|
SelString += ",";
|
|
}
|
|
SelString += "])";
|
|
|
|
std::string FeatName = cmd->getUniqueObjectName(which.c_str());
|
|
|
|
cmd->openCommand((std::string("Make ") + which).c_str());
|
|
cmd->doCommand(cmd->Doc,"App.activeDocument().addObject(\"PartDesign::%s\",\"%s\")",which.c_str(), FeatName.c_str());
|
|
cmd->doCommand(cmd->Doc,"App.activeDocument().%s.Base = %s",FeatName.c_str(),SelString.c_str());
|
|
cmd->doCommand(cmd->Gui,"Gui.Selection.clearSelection()");
|
|
finishFeature(cmd, FeatName, base);
|
|
}
|
|
|
|
void makeChamferOrFillet(Gui::Command* cmd, const std::string& which)
|
|
{
|
|
Gui::SelectionObject selected;
|
|
if (!dressupGetSelected ( cmd, which, selected))
|
|
return;
|
|
|
|
Part::Feature *base = static_cast<Part::Feature*>(selected.getObject());
|
|
|
|
std::vector<std::string> SubNames = std::vector<std::string>(selected.getSubNames());
|
|
|
|
finishDressupFeature (cmd, which, base, SubNames);
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Fillet
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignFillet);
|
|
|
|
CmdPartDesignFillet::CmdPartDesignFillet()
|
|
:Command("PartDesign_Fillet")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Fillet");
|
|
sToolTipText = QT_TR_NOOP("Make a fillet on an edge, face or body");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Fillet";
|
|
}
|
|
|
|
void CmdPartDesignFillet::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
makeChamferOrFillet(this, "Fillet");
|
|
}
|
|
|
|
bool CmdPartDesignFillet::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Chamfer
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignChamfer);
|
|
|
|
CmdPartDesignChamfer::CmdPartDesignChamfer()
|
|
:Command("PartDesign_Chamfer")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Chamfer");
|
|
sToolTipText = QT_TR_NOOP("Chamfer the selected edges of a shape");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Chamfer";
|
|
}
|
|
|
|
void CmdPartDesignChamfer::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
makeChamferOrFillet(this, "Chamfer");
|
|
doCommand(Gui,"Gui.Selection.clearSelection()");
|
|
}
|
|
|
|
bool CmdPartDesignChamfer::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Draft
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignDraft);
|
|
|
|
CmdPartDesignDraft::CmdPartDesignDraft()
|
|
:Command("PartDesign_Draft")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Draft");
|
|
sToolTipText = QT_TR_NOOP("Make a draft on a face");
|
|
sWhatsThis = "PartDesign_Draft";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Draft";
|
|
}
|
|
|
|
void CmdPartDesignDraft::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
Gui::SelectionObject selected;
|
|
if (!dressupGetSelected ( this, "Draft", selected))
|
|
return;
|
|
|
|
Part::Feature *base = static_cast<Part::Feature*>(selected.getObject());
|
|
std::vector<std::string> SubNames = std::vector<std::string>(selected.getSubNames());
|
|
const Part::TopoShape& TopShape = base->Shape.getShape();
|
|
size_t i = 0;
|
|
|
|
// filter out the edges
|
|
while(i < SubNames.size())
|
|
{
|
|
std::string aSubName = static_cast<std::string>(SubNames.at(i));
|
|
|
|
if(aSubName.size() > 4 && aSubName.substr(0,4) == "Face") {
|
|
// Check for valid face types
|
|
TopoDS_Face face = TopoDS::Face(TopShape.getSubShape(aSubName.c_str()));
|
|
BRepAdaptor_Surface sf(face);
|
|
if ((sf.GetType() != GeomAbs_Plane) && (sf.GetType() != GeomAbs_Cylinder) && (sf.GetType() != GeomAbs_Cone))
|
|
SubNames.erase(SubNames.begin()+i);
|
|
} else {
|
|
// empty name or any other sub-element
|
|
SubNames.erase(SubNames.begin()+i);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
finishDressupFeature (this, "Draft", base, SubNames);
|
|
}
|
|
|
|
bool CmdPartDesignDraft::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// PartDesign_Thickness
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignThickness);
|
|
|
|
CmdPartDesignThickness::CmdPartDesignThickness()
|
|
:Command("PartDesign_Thickness")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Thickness");
|
|
sToolTipText = QT_TR_NOOP("Make a thick solid");
|
|
sWhatsThis = "PartDesign_Thickness";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Thickness";
|
|
}
|
|
|
|
void CmdPartDesignThickness::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
Gui::SelectionObject selected;
|
|
if (!dressupGetSelected ( this, "Thickness", selected))
|
|
return;
|
|
|
|
Part::Feature *base = static_cast<Part::Feature*>(selected.getObject());
|
|
std::vector<std::string> SubNames = std::vector<std::string>(selected.getSubNames());
|
|
size_t i = 0;
|
|
|
|
// filter out the edges
|
|
while(i < SubNames.size())
|
|
{
|
|
std::string aSubName = static_cast<std::string>(SubNames.at(i));
|
|
|
|
if(aSubName.size() > 4 && aSubName.substr(0,4) != "Face") {
|
|
// empty name or any other sub-element
|
|
SubNames.erase(SubNames.begin()+i);
|
|
}
|
|
i++;
|
|
}
|
|
|
|
finishDressupFeature (this, "Thickness", base, SubNames);
|
|
}
|
|
|
|
bool CmdPartDesignThickness::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// Common functions for all Transformed features
|
|
//===========================================================================
|
|
|
|
void prepareTransformed(Gui::Command* cmd, const std::string& which,
|
|
boost::function<void(std::string, std::vector<App::DocumentObject*>)> func)
|
|
{
|
|
std::string FeatName = cmd->getUniqueObjectName(which.c_str());
|
|
|
|
auto accepter = [=](std::vector<App::DocumentObject*> features) -> bool{
|
|
|
|
if(features.empty())
|
|
return false;
|
|
|
|
return true;
|
|
};
|
|
|
|
auto worker = [=](std::vector<App::DocumentObject*> features) {
|
|
std::stringstream str;
|
|
str << "App.activeDocument()." << FeatName << ".Originals = [";
|
|
for (std::vector<App::DocumentObject*>::iterator it = features.begin(); it != features.end(); ++it){
|
|
str << "App.activeDocument()." << (*it)->getNameInDocument() << ",";
|
|
}
|
|
str << "]";
|
|
|
|
Gui::Command::openCommand((std::string("Make ") + which + " feature").c_str());
|
|
Gui::Command::doCommand(Gui::Command::Doc,"App.activeDocument().addObject(\"PartDesign::%s\",\"%s\")",which.c_str(), FeatName.c_str());
|
|
// FIXME: There seems to be kind of a race condition here, leading to sporadic errors like
|
|
// Exception (Thu Sep 6 11:52:01 2012): 'App.Document' object has no attribute 'Mirrored'
|
|
Gui::Command::updateActive(); // Helps to ensure that the object already exists when the next command comes up
|
|
Gui::Command::doCommand(Gui::Command::Doc, str.str().c_str());
|
|
// TODO Wjat that function supposed to do? (2015-08-05, Fat-Zer)
|
|
func(FeatName, features);
|
|
};
|
|
|
|
// Get a valid original from the user
|
|
// First check selections
|
|
std::vector<App::DocumentObject*> features = cmd->getSelection().getObjectsOfType(PartDesign::FeatureAddSub::getClassTypeId());
|
|
// Next create a list of all eligible objects
|
|
if (features.size() == 0) {
|
|
features = cmd->getDocument()->getObjectsOfType(PartDesign::FeatureAddSub::getClassTypeId());
|
|
// If there is more than one selected or eligible object, show dialog and let user pick one
|
|
if (features.size() > 1) {
|
|
std::vector<PartDesignGui::TaskFeaturePick::featureStatus> status;
|
|
for (unsigned i = 0; i < features.size(); i++)
|
|
status.push_back(PartDesignGui::TaskFeaturePick::validFeature);
|
|
|
|
Gui::TaskView::TaskDialog *dlg = Gui::Control().activeDialog();
|
|
PartDesignGui::TaskDlgFeaturePick *pickDlg = qobject_cast<PartDesignGui::TaskDlgFeaturePick *>(dlg);
|
|
if (dlg && !pickDlg) {
|
|
QMessageBox msgBox;
|
|
msgBox.setText(QObject::tr("A dialog is already open in the task panel"));
|
|
msgBox.setInformativeText(QObject::tr("Do you want to close this dialog?"));
|
|
msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
|
|
msgBox.setDefaultButton(QMessageBox::Yes);
|
|
int ret = msgBox.exec();
|
|
if (ret == QMessageBox::Yes)
|
|
Gui::Control().closeDialog();
|
|
else
|
|
return;
|
|
}
|
|
|
|
if(dlg)
|
|
Gui::Control().closeDialog();
|
|
|
|
Gui::Selection().clearSelection();
|
|
Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick(features, status, accepter, worker));
|
|
} else {
|
|
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("No valid features in this document"),
|
|
QObject::tr("Please create a subtractive or additive feature first."));
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
worker(features);
|
|
}
|
|
}
|
|
|
|
void finishTransformed(Gui::Command* cmd, std::string& FeatName)
|
|
{
|
|
finishFeature(cmd, FeatName);
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Mirrored
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignMirrored);
|
|
|
|
CmdPartDesignMirrored::CmdPartDesignMirrored()
|
|
: Command("PartDesign_Mirrored")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Mirrored");
|
|
sToolTipText = QT_TR_NOOP("create a mirrored feature");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Mirrored";
|
|
}
|
|
|
|
void CmdPartDesignMirrored::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](std::string FeatName, std::vector<App::DocumentObject*> features) {
|
|
|
|
if (features.empty())
|
|
return;
|
|
|
|
bool direction = false;
|
|
if(features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) {
|
|
Part::Part2DObject *sketch = (static_cast<PartDesign::ProfileBased*>(features.front()))->getVerifiedSketch(/* silent =*/ true);
|
|
if (sketch) {
|
|
doCommand(Doc,"App.activeDocument().%s.MirrorPlane = (App.activeDocument().%s, [\"V_Axis\"])",
|
|
FeatName.c_str(), sketch->getNameInDocument());
|
|
direction = true;
|
|
}
|
|
}
|
|
if(!direction) {
|
|
auto body = static_cast<PartDesign::Body*>(Part::BodyBase::findBodyOf(features.front()));
|
|
if(body) {
|
|
doCommand(Doc,"App.activeDocument().%s.MirrorPlane = (App.activeDocument().%s, [\"\"])", FeatName.c_str(),
|
|
body->getOrigin()->getXY()->getNameInDocument());
|
|
}
|
|
}
|
|
|
|
finishTransformed(cmd, FeatName);
|
|
};
|
|
|
|
prepareTransformed(this, "Mirrored", worker);
|
|
}
|
|
|
|
bool CmdPartDesignMirrored::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_LinearPattern
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignLinearPattern);
|
|
|
|
CmdPartDesignLinearPattern::CmdPartDesignLinearPattern()
|
|
: Command("PartDesign_LinearPattern")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("LinearPattern");
|
|
sToolTipText = QT_TR_NOOP("Create a linear pattern feature");
|
|
sWhatsThis = "PartDesign_LinearPattern";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_LinearPattern";
|
|
}
|
|
|
|
void CmdPartDesignLinearPattern::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](std::string FeatName, std::vector<App::DocumentObject*> features) {
|
|
|
|
if (features.empty())
|
|
return;
|
|
|
|
bool direction = false;
|
|
if(features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) {
|
|
Part::Part2DObject *sketch = (static_cast<PartDesign::ProfileBased*>(features.front()))->getVerifiedSketch(/* silent =*/ true);
|
|
if (sketch) {
|
|
doCommand(Doc,"App.activeDocument().%s.Direction = (App.activeDocument().%s, [\"H_Axis\"])",
|
|
FeatName.c_str(), sketch->getNameInDocument());
|
|
direction = true;
|
|
}
|
|
}
|
|
if(!direction) {
|
|
auto body = static_cast<PartDesign::Body*>(Part::BodyBase::findBodyOf(features.front()));
|
|
if(body) {
|
|
doCommand(Doc,"App.activeDocument().%s.Direction = (App.activeDocument().%s, [\"\"])", FeatName.c_str(),
|
|
body->getOrigin()->getX()->getNameInDocument());
|
|
}
|
|
}
|
|
doCommand(Doc,"App.activeDocument().%s.Length = 100", FeatName.c_str());
|
|
doCommand(Doc,"App.activeDocument().%s.Occurrences = 2", FeatName.c_str());
|
|
|
|
finishTransformed(cmd, FeatName);
|
|
};
|
|
|
|
prepareTransformed(this, "LinearPattern", worker);
|
|
}
|
|
|
|
bool CmdPartDesignLinearPattern::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_PolarPattern
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignPolarPattern);
|
|
|
|
CmdPartDesignPolarPattern::CmdPartDesignPolarPattern()
|
|
: Command("PartDesign_PolarPattern")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("PolarPattern");
|
|
sToolTipText = QT_TR_NOOP("Create a polar pattern feature");
|
|
sWhatsThis = "PartDesign_PolarPattern";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_PolarPattern";
|
|
}
|
|
|
|
void CmdPartDesignPolarPattern::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](std::string FeatName, std::vector<App::DocumentObject*> features) {
|
|
|
|
if (features.empty())
|
|
return;
|
|
|
|
bool direction = false;
|
|
if(features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) {
|
|
Part::Part2DObject *sketch = (static_cast<PartDesign::ProfileBased*>(features.front()))->getVerifiedSketch(/* silent =*/ true);
|
|
if (sketch) {
|
|
doCommand(Doc,"App.activeDocument().%s.Axis = (App.activeDocument().%s, [\"N_Axis\"])",
|
|
FeatName.c_str(), sketch->getNameInDocument());
|
|
direction = true;
|
|
}
|
|
}
|
|
if(!direction) {
|
|
auto body = static_cast<PartDesign::Body*>(Part::BodyBase::findBodyOf(features.front()));
|
|
if(body) {
|
|
doCommand(Doc,"App.activeDocument().%s.Axis = (App.activeDocument().%s, [\"\"])", FeatName.c_str(),
|
|
body->getOrigin()->getZ()->getNameInDocument());
|
|
}
|
|
}
|
|
|
|
doCommand(Doc,"App.activeDocument().%s.Angle = 360", FeatName.c_str());
|
|
doCommand(Doc,"App.activeDocument().%s.Occurrences = 2", FeatName.c_str());
|
|
|
|
finishTransformed(cmd, FeatName);
|
|
};
|
|
|
|
prepareTransformed(this, "PolarPattern", worker);
|
|
}
|
|
|
|
bool CmdPartDesignPolarPattern::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Scaled
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignScaled);
|
|
|
|
CmdPartDesignScaled::CmdPartDesignScaled()
|
|
: Command("PartDesign_Scaled")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Scaled");
|
|
sToolTipText = QT_TR_NOOP("Create a scaled feature");
|
|
sWhatsThis = "PartDesign_Scaled";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Scaled";
|
|
}
|
|
|
|
void CmdPartDesignScaled::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd](std::string FeatName, std::vector<App::DocumentObject*> features) {
|
|
|
|
if (features.empty())
|
|
return;
|
|
|
|
doCommand(Doc,"App.activeDocument().%s.Factor = 2", FeatName.c_str());
|
|
doCommand(Doc,"App.activeDocument().%s.Occurrences = 2", FeatName.c_str());
|
|
|
|
finishTransformed(cmd, FeatName);
|
|
};
|
|
|
|
prepareTransformed(this, "Scaled", worker);
|
|
}
|
|
|
|
bool CmdPartDesignScaled::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_MultiTransform
|
|
//===========================================================================
|
|
DEF_STD_CMD_A(CmdPartDesignMultiTransform);
|
|
|
|
CmdPartDesignMultiTransform::CmdPartDesignMultiTransform()
|
|
: Command("PartDesign_MultiTransform")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Create MultiTransform");
|
|
sToolTipText = QT_TR_NOOP("Create a multitransform feature");
|
|
sWhatsThis = "PartDesign_MultiTransform";
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_MultiTransform";
|
|
}
|
|
|
|
void CmdPartDesignMultiTransform::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */false);
|
|
//if (!pcActiveBody) return;
|
|
|
|
std::vector<App::DocumentObject*> features;
|
|
|
|
// Check if a Transformed feature has been selected, convert it to MultiTransform
|
|
features = getSelection().getObjectsOfType(PartDesign::Transformed::getClassTypeId());
|
|
if (!features.empty()) {
|
|
// Throw out MultiTransform features, we don't want to nest them
|
|
for (std::vector<App::DocumentObject*>::iterator f = features.begin(); f != features.end(); ) {
|
|
if ((*f)->getTypeId().isDerivedFrom(PartDesign::MultiTransform::getClassTypeId()))
|
|
f = features.erase(f);
|
|
else
|
|
f++;
|
|
}
|
|
|
|
if (features.empty()) return;
|
|
// Note: If multiple Transformed features were selected, only the first one is used
|
|
PartDesign::Transformed* trFeat = static_cast<PartDesign::Transformed*>(features.front());
|
|
|
|
// Move the insert point back one feature
|
|
App::DocumentObject* oldTip = 0;
|
|
App::DocumentObject* prevFeature = 0;
|
|
if (pcActiveBody){
|
|
oldTip = pcActiveBody->Tip.getValue();
|
|
prevFeature = pcActiveBody->getPrevFeature(trFeat);
|
|
}
|
|
Gui::Selection().clearSelection();
|
|
if (prevFeature != NULL)
|
|
Gui::Selection().addSelection(prevFeature->getDocument()->getName(), prevFeature->getNameInDocument());
|
|
// TODO Review this (2015-09-05, Fat-Zer)
|
|
openCommand("Convert to MultiTransform feature");
|
|
doCommand(Gui, "FreeCADGui.runCommand('PartDesign_MoveTip')");
|
|
|
|
// Remove the Transformed feature from the Body
|
|
if(pcActiveBody)
|
|
doCommand(Doc, "App.activeDocument().%s.removeFeature(App.activeDocument().%s)",
|
|
pcActiveBody->getNameInDocument(), trFeat->getNameInDocument());
|
|
|
|
// Create a MultiTransform feature and move the Transformed feature inside it
|
|
std::string FeatName = getUniqueObjectName("MultiTransform");
|
|
doCommand(Doc, "App.activeDocument().addObject(\"PartDesign::MultiTransform\",\"%s\")", FeatName.c_str());
|
|
doCommand(Doc, "App.activeDocument().%s.Originals = App.activeDocument().%s.Originals", FeatName.c_str(), trFeat->getNameInDocument());
|
|
doCommand(Doc, "App.activeDocument().%s.Originals = []", trFeat->getNameInDocument());
|
|
doCommand(Doc, "App.activeDocument().%s.Transformations = [App.activeDocument().%s]", FeatName.c_str(), trFeat->getNameInDocument());
|
|
|
|
// Add the MultiTransform into the Body at the current insert point
|
|
finishFeature(this, FeatName);
|
|
|
|
// Restore the insert point
|
|
if (pcActiveBody && oldTip != trFeat) {
|
|
Gui::Selection().clearSelection();
|
|
Gui::Selection().addSelection(oldTip->getDocument()->getName(), oldTip->getNameInDocument());
|
|
Gui::Command::doCommand(Gui::Command::Gui,"FreeCADGui.runCommand('PartDesign_MoveTip')");
|
|
Gui::Selection().clearSelection();
|
|
} // otherwise the insert point remains at the new MultiTransform, which is fine
|
|
} else {
|
|
|
|
Gui::Command* cmd = this;
|
|
auto worker = [this, cmd, pcActiveBody](std::string FeatName, std::vector<App::DocumentObject*> features) {
|
|
|
|
if (features.empty())
|
|
return;
|
|
|
|
// Make sure the user isn't presented with an empty screen because no transformations are defined yet...
|
|
App::DocumentObject* prevSolid = pcActiveBody->Tip.getValue();
|
|
if (prevSolid != NULL) {
|
|
Part::Feature* feat = static_cast<Part::Feature*>(prevSolid);
|
|
doCommand(Doc,"App.activeDocument().%s.Shape = App.activeDocument().%s.Shape",
|
|
FeatName.c_str(), feat->getNameInDocument());
|
|
}
|
|
finishFeature(cmd, FeatName);
|
|
};
|
|
|
|
prepareTransformed(this, "MultiTransform", worker);
|
|
}
|
|
}
|
|
|
|
bool CmdPartDesignMultiTransform::isActive(void)
|
|
{
|
|
return hasActiveDocument();
|
|
}
|
|
|
|
//===========================================================================
|
|
// PartDesign_Boolean
|
|
//===========================================================================
|
|
|
|
/* Boolean commands =======================================================*/
|
|
DEF_STD_CMD_A(CmdPartDesignBoolean);
|
|
|
|
CmdPartDesignBoolean::CmdPartDesignBoolean()
|
|
:Command("PartDesign_Boolean")
|
|
{
|
|
sAppModule = "PartDesign";
|
|
sGroup = QT_TR_NOOP("PartDesign");
|
|
sMenuText = QT_TR_NOOP("Boolean operation");
|
|
sToolTipText = QT_TR_NOOP("Boolean operation with two or more bodies");
|
|
sWhatsThis = sToolTipText;
|
|
sStatusTip = sToolTipText;
|
|
sPixmap = "PartDesign_Boolean";
|
|
}
|
|
|
|
|
|
void CmdPartDesignBoolean::activated(int iMsg)
|
|
{
|
|
Q_UNUSED(iMsg);
|
|
PartDesign::Body *pcActiveBody = PartDesignGui::getBody(/*messageIfNot = */true);
|
|
if (!pcActiveBody) return;
|
|
|
|
Gui::SelectionFilter BodyFilter("SELECT PartDesign::Body COUNT 1..");
|
|
|
|
openCommand("Create Boolean");
|
|
std::string FeatName = getUniqueObjectName("Boolean");
|
|
doCommand(Doc,"App.activeDocument().addObject('PartDesign::Boolean','%s')",FeatName.c_str());
|
|
|
|
if (BodyFilter.match()) {
|
|
std::vector<App::DocumentObject*> bodies;
|
|
std::vector<std::vector<Gui::SelectionObject> >::iterator i = BodyFilter.Result.begin();
|
|
for (; i != BodyFilter.Result.end(); i++) {
|
|
for (std::vector<Gui::SelectionObject>::iterator j = i->begin(); j != i->end(); j++) {
|
|
if(j->getObject() != pcActiveBody)
|
|
bodies.push_back(j->getObject());
|
|
}
|
|
}
|
|
std::string bodyString = PartDesignGui::buildLinkListPythonStr(bodies);
|
|
doCommand(Doc,"App.activeDocument().%s.Bodies = %s",FeatName.c_str(),bodyString.c_str());
|
|
}
|
|
|
|
finishFeature(this, FeatName, nullptr, false);
|
|
}
|
|
|
|
bool CmdPartDesignBoolean::isActive(void)
|
|
{
|
|
if (getActiveGuiDocument())
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// Initialization
|
|
//===========================================================================
|
|
|
|
void CreatePartDesignCommands(void)
|
|
{
|
|
Gui::CommandManager &rcCmdMgr = Gui::Application::Instance->commandManager();
|
|
|
|
rcCmdMgr.addCommand(new CmdPartDesignShapeBinder());
|
|
rcCmdMgr.addCommand(new CmdPartDesignPlane());
|
|
rcCmdMgr.addCommand(new CmdPartDesignLine());
|
|
rcCmdMgr.addCommand(new CmdPartDesignPoint());
|
|
|
|
rcCmdMgr.addCommand(new CmdPartDesignNewSketch());
|
|
|
|
rcCmdMgr.addCommand(new CmdPartDesignPad());
|
|
rcCmdMgr.addCommand(new CmdPartDesignPocket());
|
|
rcCmdMgr.addCommand(new CmdPartDesignRevolution());
|
|
rcCmdMgr.addCommand(new CmdPartDesignGroove());
|
|
rcCmdMgr.addCommand(new CmdPartDesignAdditivePipe);
|
|
rcCmdMgr.addCommand(new CmdPartDesignSubtractivePipe);
|
|
rcCmdMgr.addCommand(new CmdPartDesignAdditiveLoft);
|
|
rcCmdMgr.addCommand(new CmdPartDesignSubtractiveLoft);
|
|
|
|
rcCmdMgr.addCommand(new CmdPartDesignFillet());
|
|
rcCmdMgr.addCommand(new CmdPartDesignDraft());
|
|
rcCmdMgr.addCommand(new CmdPartDesignChamfer());
|
|
rcCmdMgr.addCommand(new CmdPartDesignThickness());
|
|
|
|
rcCmdMgr.addCommand(new CmdPartDesignMirrored());
|
|
rcCmdMgr.addCommand(new CmdPartDesignLinearPattern());
|
|
rcCmdMgr.addCommand(new CmdPartDesignPolarPattern());
|
|
//rcCmdMgr.addCommand(new CmdPartDesignScaled());
|
|
rcCmdMgr.addCommand(new CmdPartDesignMultiTransform());
|
|
|
|
rcCmdMgr.addCommand(new CmdPartDesignBoolean());
|
|
}
|