Download src/Mod/PartDesign/Gui/SketchWorkflow.cpp from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 30.4 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/Gui/SketchWorkflow.cpp
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/PartDesign/Gui/SketchWorkflow.cpp
-
curl -L -o SketchWorkflow.cpp https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/PartDesign/Gui/SketchWorkflow.cpp
30.4 kB
| /************************************************************************** | |
| * Copyright (c) 2022 Werner Mayer <wmayer[at]users.sourceforge.net> * | |
| * * | |
| * 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 * | |
| * * | |
| ***************************************************************************/ | |
| using namespace PartDesignGui; | |
| namespace | |
| { | |
| struct RejectException | |
| { | |
| }; | |
| struct WrongSelectionException | |
| { | |
| }; | |
| struct WrongSupportException | |
| { | |
| }; | |
| struct SupportNotPlanarException | |
| { | |
| }; | |
| struct MissingPlanesException | |
| { | |
| }; | |
| class SupportFaceValidator | |
| { | |
| public: | |
| explicit SupportFaceValidator(Gui::SelectionObject faceSelection) | |
| : faceSelection(faceSelection) | |
| {} | |
| void handleSelectedBody(PartDesign::Body* activeBody) | |
| { | |
| App::DocumentObject* object = faceSelection.getObject(); | |
| std::vector<std::string> elements = faceSelection.getSubNames(); | |
| // In case the selected face belongs to the body then it means its | |
| // Display Mode Body is set to Tip. But the body face is not allowed | |
| // to be used as support because otherwise it would cause a cyclic | |
| // dependency. So, instead we use the tip object as reference. | |
| // https://forum.freecad.org/viewtopic.php?f=3&t=37448 | |
| if (object == activeBody) { | |
| App::DocumentObject* tip = activeBody->Tip.getValue(); | |
| if (tip && tip->isDerivedFrom<Part::Feature>() && elements.size() == 1) { | |
| Gui::SelectionChanges msg; | |
| msg.pDocName = faceSelection.getDocName(); | |
| msg.pObjectName = tip->getNameInDocument(); | |
| msg.pSubName = elements[0].c_str(); | |
| msg.pTypeName = tip->getTypeId().getName(); | |
| faceSelection = Gui::SelectionObject {msg}; | |
| // automatically switch to 'Through' mode | |
| setThroughModeOfBody(activeBody); | |
| } | |
| } | |
| } | |
| void throwIfInvalid() | |
| { | |
| App::DocumentObject* object = faceSelection.getObject(); | |
| std::vector<std::string> elements = faceSelection.getSubNames(); | |
| Part::Feature* partobject = dynamic_cast<Part::Feature*>(object); | |
| if (!partobject) { | |
| throw WrongSelectionException(); | |
| } | |
| if (elements.size() != 1) { | |
| throw WrongSelectionException(); | |
| } | |
| // get the selected sub shape (a Face) | |
| const Part::TopoShape& shape = partobject->Shape.getValue(); | |
| Part::TopoShape subshape(shape.getSubShape(elements[0].c_str())); | |
| if (subshape.isNull()) { | |
| throw WrongSupportException(); | |
| } | |
| if (!subshape.isPlanar(Attacher::AttachEnginePlane::planarPrecision())) { | |
| throw SupportNotPlanarException(); | |
| } | |
| } | |
| std::string getSupport() const | |
| { | |
| return faceSelection.getAsPropertyLinkSubString(); | |
| } | |
| App::DocumentObject* getObject() const | |
| { | |
| return faceSelection.getObject(); | |
| } | |
| private: | |
| void setThroughModeOfBody(PartDesign::Body* activeBody) | |
| { | |
| // automatically switch to 'Through' mode | |
| PartDesignGui::ViewProviderBody* vpBody = dynamic_cast<PartDesignGui::ViewProviderBody*>( | |
| Gui::Application::Instance->getViewProvider(activeBody) | |
| ); | |
| if (vpBody) { | |
| vpBody->DisplayModeBody.setValue("Through"); | |
| } | |
| } | |
| private: | |
| mutable Gui::SelectionObject faceSelection; | |
| }; | |
| class SupportPlaneValidator | |
| { | |
| public: | |
| explicit SupportPlaneValidator(Gui::SelectionObject faceSelection) | |
| : faceSelection(faceSelection) | |
| {} | |
| std::string getSupport() const | |
| { | |
| return faceSelection.getAsPropertyLinkSubString(); | |
| } | |
| App::DocumentObject* getObject() const | |
| { | |
| return faceSelection.getObject(); | |
| } | |
| private: | |
| mutable Gui::SelectionObject faceSelection; | |
| }; | |
| class SketchPreselection | |
| { | |
| public: | |
| SketchPreselection( | |
| Gui::Document* guidocument, | |
| PartDesign::Body* activeBody, | |
| std::tuple<Gui::SelectionFilter, Gui::SelectionFilter, Gui::SelectionFilter> filter | |
| ) | |
| : guidocument(guidocument) | |
| , activeBody(activeBody) | |
| , faceFilter(std::get<0>(filter)) | |
| , planeFilter(std::get<1>(filter)) | |
| , sketchFilter(std::get<2>(filter)) | |
| {} | |
| bool matches() | |
| { | |
| return faceFilter.match() || planeFilter.match() || sketchFilter.match(); | |
| } | |
| std::string getSupport() const | |
| { | |
| return supportString; | |
| } | |
| void createSupport() | |
| { | |
| createBodyOrThrow(); | |
| // get the selected object | |
| App::DocumentObject* selectedObject {}; | |
| if (faceFilter.match()) { | |
| Gui::SelectionObject faceSelObject = faceFilter.Result[0][0]; | |
| SupportFaceValidator validator {faceSelObject}; | |
| validator.handleSelectedBody(activeBody); | |
| validator.throwIfInvalid(); | |
| selectedObject = validator.getObject(); | |
| supportString = validator.getSupport(); | |
| } | |
| else if (planeFilter.match()) { | |
| SupportPlaneValidator validator(planeFilter.Result[0][0]); | |
| selectedObject = validator.getObject(); | |
| supportString = validator.getSupport(); | |
| } | |
| else { | |
| // For a sketch, the support is the object itself with no sub-element. | |
| Gui::SelectionObject sketchSelObject = sketchFilter.Result[0][0]; | |
| selectedObject = sketchSelObject.getObject(); | |
| supportString = sketchSelObject.getAsPropertyLinkSubString(); | |
| } | |
| handleIfSupportOutOfBody(selectedObject); | |
| } | |
| void createSketchOnSupport(const std::string& supportString) | |
| { | |
| // create Sketch on Face or Plane | |
| App::Document* appdocument = guidocument->getDocument(); | |
| std::string FeatName = appdocument->getUniqueObjectName("Sketch"); | |
| guidocument->openCommand(QT_TRANSLATE_NOOP("Command", "Sketch on Face")); | |
| FCMD_OBJ_CMD(activeBody, "newObject('Sketcher::SketchObject','" << FeatName << "')"); | |
| auto Feat = activeBody->getDocument()->getObject(FeatName.c_str()); | |
| FCMD_OBJ_CMD(Feat, "AttachmentSupport = " << supportString); | |
| if (sketchFilter.match()) { | |
| FCMD_OBJ_CMD( | |
| Feat, | |
| "MapMode = '" << Attacher::AttachEngine::getModeName(Attacher::mmObjectXY) << "'" | |
| ); | |
| } | |
| else { // For Face or Plane | |
| FCMD_OBJ_CMD( | |
| Feat, | |
| "MapMode = '" << Attacher::AttachEngine::getModeName(Attacher::mmFlatFace) << "'" | |
| ); | |
| } | |
| Gui::Command::updateActive(); | |
| PartDesignGui::setEdit(Feat, activeBody); | |
| } | |
| private: | |
| void createBodyOrThrow() | |
| { | |
| if (!activeBody) { | |
| activeBody = PartDesignGui::getBody(/* messageIfNot = */ true); | |
| if (activeBody) { | |
| tryAddNewBodyToActivePart(); | |
| } | |
| else { | |
| throw RejectException(); | |
| } | |
| } | |
| } | |
| void tryAddNewBodyToActivePart() | |
| { | |
| App::Part* activePart = PartDesignGui::getActivePart(); | |
| if (activePart) { | |
| activePart->addObject(activeBody); | |
| } | |
| } | |
| void handleIfSupportOutOfBody(App::DocumentObject* selectedObject) | |
| { | |
| if (!activeBody->hasObject(selectedObject)) { | |
| if (!selectedObject->isDerivedFrom(App::Plane::getClassTypeId())) { | |
| // TODO check here if the plane associated with right part/body (2015-09-01, Fat-Zer) | |
| // 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(Gui::getMainWindow()); | |
| PartDesignGui::Ui_DlgReference dlg; | |
| dlg.setupUi(&dia); | |
| dia.setModal(true); | |
| int result = dia.exec(); | |
| if (result == QDialog::Rejected) { | |
| throw RejectException(); | |
| } | |
| if (!dlg.radioXRef->isChecked()) { | |
| guidocument->openCommand(QT_TRANSLATE_NOOP("Command", "Make copy")); | |
| auto copy = makeCopy(selectedObject, dlg.radioIndependent->isChecked()); | |
| supportString = supportFromCopy(copy); | |
| guidocument->commitCommand(); | |
| } | |
| } | |
| } | |
| } | |
| App::DocumentObject* makeCopy(App::DocumentObject* selectedObject, bool independent) | |
| { | |
| std::string sub; | |
| if (faceFilter.match()) { | |
| sub = faceFilter.Result[0][0].getSubNames()[0]; | |
| } | |
| auto copy = PartDesignGui::TaskFeaturePick::makeCopy(selectedObject, sub, independent); | |
| addToBodyOrPart(copy); | |
| return copy; | |
| } | |
| std::string supportFromCopy(App::DocumentObject* copy) | |
| { | |
| std::string supportString; | |
| if (planeFilter.match()) { | |
| supportString = Gui::Command::getObjectCmd(copy, "(", ",'')"); | |
| } | |
| else { | |
| // it is ensured that only a single face is selected, hence it must always be Face1 of | |
| // the shapebinder | |
| supportString = Gui::Command::getObjectCmd(copy, "(", ",'Face1')"); | |
| } | |
| return supportString; | |
| } | |
| void addToBodyOrPart(App::DocumentObject* object) | |
| { | |
| auto activePart = PartDesignGui::getPartFor(activeBody, false); | |
| if (activeBody) { | |
| activeBody->addObject(object); | |
| } | |
| else if (activePart) { | |
| activePart->addObject(object); | |
| } | |
| } | |
| private: | |
| Gui::Document* guidocument; | |
| PartDesign::Body* activeBody; | |
| Gui::SelectionFilter faceFilter; | |
| Gui::SelectionFilter planeFilter; | |
| Gui::SelectionFilter sketchFilter; | |
| std::string supportString; | |
| }; | |
| class PlaneFinder | |
| { | |
| public: | |
| PlaneFinder(App::Document* appdocument, PartDesign::Body* activeBody) | |
| : appdocument(appdocument) | |
| , activeBody(activeBody) | |
| {} | |
| std::vector<App::DocumentObject*> getPlanes() const | |
| { | |
| return planes; | |
| } | |
| std::vector<PartDesignGui::TaskFeaturePick::featureStatus> getStatus() const | |
| { | |
| return status; | |
| } | |
| unsigned countValidPlanes() const | |
| { | |
| return validPlaneCount; | |
| } | |
| void findBasePlanes() | |
| { | |
| try { | |
| tryFindBasePlanes(); | |
| } | |
| catch (const Base::Exception& ex) { | |
| Base::Console().error("%s\n", ex.what()); | |
| } | |
| } | |
| void findDatumPlanes() | |
| { | |
| App::GeoFeatureGroupExtension* geoGroup = getGroupExtensionOfBody(); | |
| const std::vector<Base::Type> types | |
| = {PartDesign::Plane::getClassTypeId(), App::Plane::getClassTypeId()}; | |
| auto datumPlanes = appdocument->getObjectsOfType(types); | |
| for (auto plane : datumPlanes) { | |
| if (std::find(planes.begin(), planes.end(), plane) != planes.end()) { | |
| continue; // Skip if already in planes (for base planes) | |
| } | |
| planes.push_back(plane); | |
| // Check whether this plane belongs to the active body | |
| if (activeBody->hasObject(plane, true)) { | |
| if (!activeBody->isAfterInsertPoint(plane)) { | |
| validPlaneCount++; | |
| 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 { | |
| status.push_back(PartDesignGui::TaskFeaturePick::notInBody); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void findShapeBinderPlanes() | |
| { | |
| // Collect also shape binders consisting of a single planar face | |
| auto shapeBinders(appdocument->getObjectsOfType(PartDesign::ShapeBinder::getClassTypeId())); | |
| auto binders(appdocument->getObjectsOfType(PartDesign::SubShapeBinder::getClassTypeId())); | |
| shapeBinders.insert(shapeBinders.end(), binders.begin(), binders.end()); | |
| for (auto binder : shapeBinders) { | |
| // Check whether this plane belongs to the active body | |
| if (activeBody->hasObject(binder)) { | |
| Part::TopoShape shape = static_cast<Part::Feature*>(binder)->Shape.getShape(); | |
| if (shape.isPlanar()) { | |
| if (!activeBody->isAfterInsertPoint(binder)) { | |
| validPlaneCount++; | |
| planes.push_back(binder); | |
| status.push_back(PartDesignGui::TaskFeaturePick::validFeature); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| private: | |
| void tryFindBasePlanes() | |
| { | |
| auto* origin = activeBody->getOrigin(); | |
| for (auto plane : origin->planes()) { | |
| planes.push_back(plane); | |
| status.push_back(PartDesignGui::TaskFeaturePick::basePlane); | |
| validPlaneCount++; | |
| } | |
| } | |
| App::GeoFeatureGroupExtension* getGroupExtensionOfBody() const | |
| { | |
| App::GeoFeatureGroupExtension* geoGroup {nullptr}; | |
| if (activeBody) { | |
| auto group(App::GeoFeatureGroupExtension::getGroupOfObject(activeBody)); | |
| if (group) { | |
| geoGroup = group->getExtensionByType<App::GeoFeatureGroupExtension>(); | |
| } | |
| } | |
| return geoGroup; | |
| } | |
| private: | |
| App::Document* appdocument; | |
| PartDesign::Body* activeBody; | |
| unsigned validPlaneCount = 0; | |
| std::vector<App::DocumentObject*> planes; | |
| std::vector<PartDesignGui::TaskFeaturePick::featureStatus> status; | |
| }; | |
| class SketchRequestSelection | |
| { | |
| public: | |
| SketchRequestSelection(Gui::Document* guidocument, PartDesign::Body* activeBody) | |
| : guidocument(guidocument) | |
| , activeBody(activeBody) | |
| {} | |
| void findSupport() | |
| { | |
| try { | |
| // Start command early, so undo will undo any Body creation | |
| guidocument->openCommand(QT_TRANSLATE_NOOP("Command", "New Sketch")); | |
| tryFindSupport(); | |
| } | |
| catch (const RejectException&) { | |
| guidocument->abortCommand(); | |
| throw; | |
| } | |
| catch (const MissingPlanesException&) { | |
| guidocument->abortCommand(); | |
| throw; | |
| } | |
| } | |
| private: | |
| void tryFindSupport() | |
| { | |
| createBodyOrThrow(); | |
| bool useAttachment = App::GetApplication() | |
| .GetParameterGroupByPath( | |
| "User parameter:BaseApp/Preferences/Mod/PartDesign" | |
| ) | |
| ->GetBool("NewSketchUseAttachmentDialog", false); | |
| if (useAttachment) { | |
| createSketchAndShowAttachment(); | |
| } | |
| else { | |
| findAndSelectPlane(); | |
| } | |
| } | |
| void createBodyOrThrow() | |
| { | |
| if (!activeBody) { | |
| App::Document* appdocument = guidocument->getDocument(); | |
| activeBody = PartDesignGui::makeBody(appdocument); | |
| if (activeBody) { | |
| tryAddNewBodyToActivePart(); | |
| } | |
| else { | |
| throw RejectException(); | |
| } | |
| } | |
| } | |
| void tryAddNewBodyToActivePart() | |
| { | |
| App::Part* activePart = PartDesignGui::getActivePart(); | |
| if (activePart) { | |
| activePart->addObject(activeBody); | |
| } | |
| } | |
| void setOriginTemporaryVisibility() | |
| { | |
| auto* origin = activeBody->getOrigin(); | |
| auto* vpo = dynamic_cast<Gui::ViewProviderCoordinateSystem*>( | |
| Gui::Application::Instance->getViewProvider(origin) | |
| ); | |
| if (vpo) { | |
| vpo->setTemporaryVisibility(Gui::DatumElement::Planes | Gui::DatumElement::Axes); | |
| vpo->setPlaneLabelVisibility(true); | |
| } | |
| } | |
| void createSketchAndShowAttachment() | |
| { | |
| setOriginTemporaryVisibility(); | |
| // Create sketch | |
| App::Document* doc = activeBody->getDocument(); | |
| std::string FeatName = doc->getUniqueObjectName("Sketch"); | |
| FCMD_OBJ_CMD(activeBody, "newObject('Sketcher::SketchObject','" << FeatName << "')"); | |
| auto sketch = doc->getObject(FeatName.c_str()); | |
| PartDesign::Body* partDesignBody = activeBody; | |
| auto onAccept = [partDesignBody, sketch]() { | |
| resetOriginVisibility(partDesignBody); | |
| Gui::Selection().clearSelection(); | |
| PartDesignGui::setEdit(sketch, partDesignBody); | |
| }; | |
| auto onReject = [partDesignBody]() { | |
| resetOriginVisibility(partDesignBody); | |
| }; | |
| Gui::Selection().clearSelection(); | |
| // Open attachment dialog | |
| auto* vps = dynamic_cast<SketcherGui::ViewProviderSketch*>( | |
| Gui::Application::Instance->getViewProvider(sketch) | |
| ); | |
| vps->showAttachmentEditor(onAccept, onReject); | |
| } | |
| static void resetOriginVisibility(PartDesign::Body* partDesignBody) | |
| { | |
| auto* origin = partDesignBody->getOrigin(); | |
| auto* vpo = dynamic_cast<Gui::ViewProviderCoordinateSystem*>( | |
| Gui::Application::Instance->getViewProvider(origin) | |
| ); | |
| if (vpo) { | |
| vpo->resetTemporaryVisibility(); | |
| vpo->resetTemporarySize(); | |
| vpo->setPlaneLabelVisibility(false); | |
| } | |
| } | |
| void findAndSelectPlane() | |
| { | |
| App::Document* appdocument = guidocument->getDocument(); | |
| PlaneFinder planeFinder {appdocument, activeBody}; | |
| planeFinder.findBasePlanes(); | |
| planeFinder.findDatumPlanes(); | |
| planeFinder.findShapeBinderPlanes(); | |
| std::vector<App::DocumentObject*> planes = planeFinder.getPlanes(); | |
| std::vector<PartDesignGui::TaskFeaturePick::featureStatus> status = planeFinder.getStatus(); | |
| unsigned validPlaneCount = planeFinder.countValidPlanes(); | |
| for (auto& plane : planes) { | |
| auto* planeViewProvider | |
| = Gui::Application::Instance->getViewProvider<Gui::ViewProviderPlane>(plane); | |
| // skip updating planes from coordinate systems | |
| if (!planeViewProvider || !planeViewProvider->getRole().empty()) { | |
| continue; | |
| } | |
| planeViewProvider->setLabelVisibility(true); | |
| planeViewProvider->setTemporaryScale( | |
| Gui::ViewParams::instance()->getDatumTemporaryScaleFactor() | |
| ); | |
| } | |
| // | |
| // Lambda definitions | |
| // | |
| App::Document* documentOfBody = appdocument; | |
| PartDesign::Body* partDesignBody = activeBody; | |
| auto restorePlaneVisibility = [planes]() { | |
| for (auto& plane : planes) { | |
| auto* planeViewProvider | |
| = Gui::Application::Instance->getViewProvider<Gui::ViewProviderPlane>(plane); | |
| if (!planeViewProvider) { | |
| continue; | |
| } | |
| planeViewProvider->resetTemporarySize(); | |
| planeViewProvider->setLabelVisibility(false); | |
| } | |
| }; | |
| // Determines if user made a valid selection in dialog | |
| auto acceptFunction = | |
| [restorePlaneVisibility](const std::vector<App::DocumentObject*>& features) -> bool { | |
| restorePlaneVisibility(); | |
| return !features.empty(); | |
| }; | |
| // Called by dialog when user hits "OK" and accepter returns true | |
| auto processFunction = [documentOfBody, | |
| partDesignBody](const std::vector<App::DocumentObject*>& features) { | |
| SketchRequestSelection::createSketch(documentOfBody, partDesignBody, features); | |
| }; | |
| // Called by dialog for "Cancel", or "OK" if accepter returns false | |
| std::string docname = documentOfBody->getName(); | |
| auto rejectFunction = [docname, restorePlaneVisibility]() { | |
| restorePlaneVisibility(); | |
| Gui::Document* document = Gui::Application::Instance->getDocument(docname.c_str()); | |
| if (document) { | |
| document->abortCommand(); | |
| } | |
| }; | |
| // | |
| // End of lambda definitions | |
| // | |
| if (validPlaneCount == 0) { | |
| throw MissingPlanesException(); | |
| } | |
| else if (validPlaneCount == 1) { | |
| processFunction(planes); | |
| } | |
| else if (validPlaneCount > 1) { | |
| checkForShownDialog(); | |
| Gui::Selection().clearSelection(); | |
| // Show dialog and let user pick plane | |
| Gui::Control().showDialog(new PartDesignGui::TaskDlgFeaturePick( | |
| planes, | |
| status, | |
| acceptFunction, | |
| processFunction, | |
| true, | |
| rejectFunction | |
| )); | |
| } | |
| } | |
| void checkForShownDialog() | |
| { | |
| Gui::TaskView::TaskDialog* dlg = Gui::Control().activeDialog(); | |
| PartDesignGui::TaskDlgFeaturePick* pickDlg | |
| = qobject_cast<PartDesignGui::TaskDlgFeaturePick*>(dlg); | |
| if (dlg && !pickDlg) { | |
| QMessageBox msgBox(Gui::getMainWindow()); | |
| msgBox.setText(QObject::tr("A dialog is already open in the task panel")); | |
| msgBox.setInformativeText(QObject::tr("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 { | |
| throw RejectException(); | |
| } | |
| } | |
| if (dlg) { | |
| Gui::Control().closeDialog(); | |
| } | |
| } | |
| static void createSketch( | |
| App::Document* documentOfBody, | |
| PartDesign::Body* partDesignBody, | |
| const std::vector<App::DocumentObject*>& features | |
| ) | |
| { | |
| // may happen when the user switched to an empty document while the | |
| // dialog is open | |
| if (features.empty()) { | |
| return; | |
| } | |
| std::string FeatName = documentOfBody->getUniqueObjectName("Sketch"); | |
| auto* plane = static_cast<App::Plane*>(features.front()); | |
| auto* lcs = plane->getLCS(); | |
| std::string supportString; | |
| if (lcs) { | |
| supportString = Gui::Command::getObjectCmd(lcs, "(") + ",['" | |
| + plane->getNameInDocument() + "'])"; | |
| } | |
| else { | |
| supportString = Gui::Command::getObjectCmd(plane, "(", ",[''])"); | |
| } | |
| App::Document* doc = partDesignBody->getDocument(); | |
| if (!doc->hasPendingTransaction()) { | |
| doc->openTransaction(QT_TRANSLATE_NOOP("Command", "New Sketch")); | |
| } | |
| FCMD_OBJ_CMD(partDesignBody, "newObject('Sketcher::SketchObject','" << FeatName << "')"); | |
| auto Feat = doc->getObject(FeatName.c_str()); | |
| FCMD_OBJ_CMD(Feat, "AttachmentSupport = " << supportString); | |
| FCMD_OBJ_CMD( | |
| Feat, | |
| "MapMode = '" << Attacher::AttachEngine::getModeName(Attacher::mmFlatFace) << "'" | |
| ); | |
| Gui::Command::updateActive(); // Make sure the AttachmentSupport's Placement property is updated | |
| PartDesignGui::setEdit(Feat, partDesignBody); | |
| } | |
| private: | |
| Gui::Document* guidocument; | |
| PartDesign::Body* activeBody; | |
| }; | |
| } // namespace | |
| SketchWorkflow::SketchWorkflow(Gui::Document* document) | |
| : guidocument(document) | |
| { | |
| appdocument = guidocument->getDocument(); | |
| } | |
| void SketchWorkflow::createSketch() | |
| { | |
| try { | |
| tryCreateSketch(); | |
| } | |
| catch (const RejectException&) { | |
| } | |
| catch (const WrongSelectionException&) { | |
| QMessageBox::warning( | |
| Gui::getMainWindow(), | |
| QObject::tr("Several sub-elements selected"), | |
| QObject::tr("Select a single face as support for a sketch!") | |
| ); | |
| } | |
| catch (const WrongSupportException&) { | |
| QMessageBox::warning( | |
| Gui::getMainWindow(), | |
| QObject::tr("No support face selected"), | |
| QObject::tr("Select a face as support for a sketch!") | |
| ); | |
| } | |
| catch (const SupportNotPlanarException&) { | |
| QMessageBox::warning( | |
| Gui::getMainWindow(), | |
| QObject::tr("No planar support"), | |
| QObject::tr("Need a planar face as support for a sketch!") | |
| ); | |
| } | |
| catch (const MissingPlanesException&) { | |
| QMessageBox::warning( | |
| Gui::getMainWindow(), | |
| QObject::tr("No valid planes in this document"), | |
| QObject::tr("Create a plane first or select a face to sketch on") | |
| ); | |
| } | |
| } | |
| void SketchWorkflow::tryCreateSketch() | |
| { | |
| auto result = shouldCreateBody(); | |
| auto shouldMakeBody = std::get<0>(result); | |
| activeBody = std::get<1>(result); | |
| if (shouldAbort(shouldMakeBody)) { | |
| return; | |
| } | |
| auto filters = getFilters(); | |
| SketchPreselection sketchOnFace {guidocument, activeBody, filters}; | |
| if (sketchOnFace.matches()) { | |
| // create Sketch on Face or Plane | |
| sketchOnFace.createSupport(); | |
| sketchOnFace.createSketchOnSupport(sketchOnFace.getSupport()); | |
| } | |
| else { | |
| SketchRequestSelection requestSelection {guidocument, activeBody}; | |
| requestSelection.findSupport(); | |
| } | |
| } | |
| std::tuple<bool, PartDesign::Body*> SketchWorkflow::shouldCreateBody() | |
| { | |
| auto shouldMakeBody {false}; | |
| // We need either an active Body, or for there to be no Body | |
| // objects (in which case, just make one) to make a new sketch. | |
| // If we are inside a link, we need to use its placement. | |
| App::DocumentObject* topParent; | |
| PartDesign::Body* pdBody | |
| = PartDesignGui::getBody(/* messageIfNot = */ false, true, true, &topParent); | |
| if (pdBody && topParent->isLink()) { | |
| auto* xLink = dynamic_cast<App::Link*>(topParent); | |
| pdBody->Placement.setValue(xLink->Placement.getValue()); | |
| } | |
| if (!pdBody) { | |
| if (appdocument->countObjectsOfType<PartDesign::Body>() == 0) { | |
| shouldMakeBody = true; | |
| } | |
| else { | |
| PartDesignGui::DlgActiveBody dia(Gui::getMainWindow(), appdocument); | |
| if (dia.exec() == QDialog::Accepted) { | |
| pdBody = dia.getActiveBody(); | |
| } | |
| } | |
| } | |
| return std::make_tuple(shouldMakeBody, pdBody); | |
| } | |
| bool SketchWorkflow::shouldAbort(bool shouldMakeBody) const | |
| { | |
| return !shouldMakeBody && !activeBody; | |
| } | |
| std::tuple<Gui::SelectionFilter, Gui::SelectionFilter, Gui::SelectionFilter> SketchWorkflow::getFilters() const | |
| { | |
| // Hint: | |
| // The behaviour of this command has changed with respect to a selected sketch: | |
| // It doesn't try any more to edit a selected sketch but always tries to create | |
| // a new sketch. | |
| // See https://forum.freecad.org/viewtopic.php?f=3&t=44070 | |
| Gui::SelectionFilter FaceFilter("SELECT Part::Feature SUBELEMENT Face COUNT 1"); | |
| Gui::SelectionFilter PlaneFilter("SELECT App::Plane COUNT 1", activeBody); | |
| Gui::SelectionFilter PlaneFilter2("SELECT PartDesign::Plane COUNT 1", activeBody); | |
| Gui::SelectionFilter SketchFilter("SELECT Part::Part2DObject COUNT 1", activeBody); | |
| if (PlaneFilter2.match()) { | |
| PlaneFilter = PlaneFilter2; | |
| } | |
| return std::make_tuple(FaceFilter, PlaneFilter, SketchFilter); | |
| } | |