/*************************************************************************** * 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 # include # include # include # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "TaskFeaturePick.h" #include "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(); 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.addObject(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.addObject(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(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(obj); const std::vector &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(PlaneFilter.Result[0][0].getObject()); supportString = std::string("(App.activeDocument().") + obj->getNameInDocument() + ", '')"; } if (!pcActiveBody->hasObject(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->addObject(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.addObject(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(); std::vector planes; std::vector 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 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->hasObject(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& features) -> bool { if(features.empty()) return false; return true; }; auto worker = [=](const std::vector& features) { App::Plane* plane = static_cast(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.addObject(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(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.addObject(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.insertObject(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& sketches, std::vector& status, std::vector::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::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->hasObject(*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 inList = (*s)->getInList(); std::vector::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(*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 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(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 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 status; std::vector::iterator firstFreeSketch; int freeSketches = validateSketches(sketches, status, firstFreeSketch); auto accepter = [=](const std::vector& features) -> bool { if(features.empty()) return false; return true; }; auto sketch_worker = [&](std::vector features) { base_worker(features.front(), ""); }; //if there is a sketch selected which is from another 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->addObject(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(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 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(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(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(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 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(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 & 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::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(selected.getObject()); std::vector SubNames = std::vector(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(selected.getObject()); std::vector SubNames = std::vector(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(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(selected.getObject()); std::vector SubNames = std::vector(selected.getSubNames()); size_t i = 0; // filter out the edges while(i < SubNames.size()) { std::string aSubName = static_cast(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)> func) { std::string FeatName = cmd->getUniqueObjectName(which.c_str()); auto accepter = [=](std::vector features) -> bool{ if(features.empty()) return false; return true; }; auto worker = [=](std::vector features) { std::stringstream str; str << "App.activeDocument()." << FeatName << ".Originals = ["; for (std::vector::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 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 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(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 features) { if (features.empty()) return; bool direction = false; if(features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { Part::Part2DObject *sketch = (static_cast(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(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 features) { if (features.empty()) return; bool direction = false; if(features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { Part::Part2DObject *sketch = (static_cast(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(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 features) { if (features.empty()) return; bool direction = false; if(features.front()->isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { Part::Part2DObject *sketch = (static_cast(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(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 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 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::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(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.removeObject(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 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(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 bodies; std::vector >::iterator i = BodyFilter.Result.begin(); for (; i != BodyFilter.Result.end(); i++) { for (std::vector::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()); }