Files
create/src/Mod/PartDesign/Gui/SketchWorkflow.cpp
Markus Reitböck 88c6f176bb PartDesign: use CMake to generate precompiled headers on all platforms
"Professional CMake" book suggest the following:

"Targets should build successfully with or without compiler support for precompiled headers. It
 should be considered an optimization, not a requirement. In particular, do not explicitly include a
 precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically
 generated precompile header on the compiler command line instead. This is more portable across
 the major compilers and is likely to be easier to maintain. It will also avoid warnings being
 generated from certain code checking tools like iwyu (include what you use)."

Therefore, removed the "#include <PreCompiled.h>" from sources, also
there is no need for the "#ifdef _PreComp_" anymore
2025-09-23 22:39:36 +02:00

869 lines
29 KiB
C++

/**************************************************************************
* 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 *
* *
***************************************************************************/
# include <TopoDS.hxx>
# include <TopoDS_Face.hxx>
# include <boost/signals2.hpp>
# include <map>
# include <string>
# include <vector>
# include <QMessageBox>
#include "SketchWorkflow.h"
#include "DlgActiveBody.h"
#include "TaskFeaturePick.h"
#include "Utils.h"
#include "ViewProviderBody.h"
#include "WorkflowManager.h"
#include "ui_DlgReference.h"
#include <Mod/PartDesign/App/Body.h>
#include <Mod/PartDesign/App/DatumPlane.h>
#include <Mod/PartDesign/App/ShapeBinder.h>
#include <Mod/Part/App/Attacher.h>
#include <Mod/Part/App/TopoShape.h>
#include <Mod/Sketcher/Gui/ViewProviderSketch.h>
#include <App/Document.h>
#include <App/Link.h>
#include <App/Origin.h>
#include <App/Datums.h>
#include <App/Part.h>
#include <Gui/Application.h>
#include <Gui/Command.h>
#include <Gui/Control.h>
#include <Gui/Document.h>
#include <Gui/MainWindow.h>
#include <Gui/ViewParams.h>
#include <Gui/ViewProviderPlane.h>
#include <Gui/Selection/SelectionFilter.h>
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;
};
}
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);
}