From f51e1eeb223666936304fdec29aa3388c58108ea Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Mon, 1 Sep 2025 17:39:51 +0200 Subject: [PATCH] PartDesign: Enable selecting a sketch as base plane of another sketch (#23428) * PartDesign: Enable selecting a sketch as base plane of another sketch * to squash * Part: Attacher: enable attaching to empty objects such as empty Sketch or Body. * Update SketchWorkflow.cpp --- src/Mod/Part/App/Attacher.cpp | 35 +++++++++++++++++++---- src/Mod/PartDesign/Gui/SketchWorkflow.cpp | 35 +++++++++++++++++------ src/Mod/PartDesign/Gui/SketchWorkflow.h | 2 +- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/Mod/Part/App/Attacher.cpp b/src/Mod/Part/App/Attacher.cpp index f5f5beffae..01244c7b2e 100644 --- a/src/Mod/Part/App/Attacher.cpp +++ b/src/Mod/Part/App/Attacher.cpp @@ -842,9 +842,19 @@ void AttachEngine::readLinks(const std::vector& objs, auto shape = extractSubShape(objs[i], subs[i]); if (shape.isNull()) { - FC_THROWM(AttachEngineException, - "AttachEngine3D: null subshape " << objs[i]->getNameInDocument() << '.' - << subs[i]); + if (subs[i].length() == 0) { + storage.emplace_back(TopoShape()); + shapes[i] = &storage.back(); + types[i] = eRefType(rtPart | rtFlagHasPlacement); + continue; + } + else { + // This case should now be unreachable because extractSubShape would have thrown + // for a missing subname. But it's good defensive programming. + FC_THROWM(AttachEngineException, + "AttachEngine3D: null subshape " << objs[i]->getNameInDocument() << '.' + << subs[i]); + } } storage.emplace_back(shape); @@ -889,9 +899,22 @@ TopoShape AttachEngine::extractSubShape(App::DocumentObject* obj, const std::str for (;;) { if (shape.isNull()) { - FC_THROWM(AttachEngineException, - "AttachEngine3D: subshape not found " << obj->getNameInDocument() << '.' - << subname); + // Shape is null. Let's see if this is an acceptable null. + // (i.e., an empty object was selected, not a broken link to a sub-element). + if (subname.empty()) { + // The user selected the whole object, and it has no shape. + // This is the empty sketch or empty body case. + // Instead of throwing an error, we return a null TopoShape. + // The caller (readLinks) will then handle this null shape. + return TopoShape(); // Return a default-constructed (null) shape + } + else { + // The user specified a subname (e.g., "Edge1"), but it couldn't be found. + // This is a genuine error. + FC_THROWM(AttachEngineException, + "AttachEngine3D: subshape not found " << obj->getNameInDocument() + << '.' << subname); + } } if (shape.shapeType() != TopAbs_COMPOUND || shape.countSubShapes(TopAbs_SHAPE) != 1) { diff --git a/src/Mod/PartDesign/Gui/SketchWorkflow.cpp b/src/Mod/PartDesign/Gui/SketchWorkflow.cpp index 3b10347b58..9d482bbd43 100644 --- a/src/Mod/PartDesign/Gui/SketchWorkflow.cpp +++ b/src/Mod/PartDesign/Gui/SketchWorkflow.cpp @@ -197,17 +197,18 @@ class SketchPreselection { public: SketchPreselection(Gui::Document* guidocument, PartDesign::Body* activeBody, - std::tuple filter) + std::tuple 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(); + return faceFilter.match() || planeFilter.match() || sketchFilter.match(); } std::string getSupport() const @@ -231,11 +232,17 @@ public: selectedObject = validator.getObject(); supportString = validator.getSupport(); } - else { + 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); } @@ -250,7 +257,16 @@ public: FCMD_OBJ_CMD(activeBody, "newObject('Sketcher::SketchObject','" << FeatName << "')"); auto Feat = activeBody->getDocument()->getObject(FeatName.c_str()); FCMD_OBJ_CMD(Feat, "AttachmentSupport = " << supportString); - FCMD_OBJ_CMD(Feat, "MapMode = '" << Attacher::AttachEngine::getModeName(Attacher::mmFlatFace)<<"'"); + 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); } @@ -346,6 +362,7 @@ private: PartDesign::Body* activeBody; Gui::SelectionFilter faceFilter; Gui::SelectionFilter planeFilter; + Gui::SelectionFilter sketchFilter; std::string supportString; }; @@ -757,8 +774,8 @@ void SketchWorkflow::tryCreateSketch() return; } - auto faceOrPlaneFilter = getFaceAndPlaneFilter(); - SketchPreselection sketchOnFace{ guidocument, activeBody, faceOrPlaneFilter }; + auto filters = getFilters(); + SketchPreselection sketchOnFace {guidocument, activeBody, filters}; if (sketchOnFace.matches()) { // create Sketch on Face or Plane @@ -804,7 +821,7 @@ bool SketchWorkflow::shouldAbort(bool shouldMakeBody) const return !shouldMakeBody && !activeBody; } -std::tuple SketchWorkflow::getFaceAndPlaneFilter() const +std::tuple SketchWorkflow::getFilters() const { // Hint: // The behaviour of this command has changed with respect to a selected sketch: @@ -815,9 +832,11 @@ std::tuple SketchWorkflow::getFaceAn 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::2DObject COUNT 1", activeBody); if (PlaneFilter2.match()) { PlaneFilter = PlaneFilter2; } - return std::make_tuple(FaceFilter, PlaneFilter); + + return std::make_tuple(FaceFilter, PlaneFilter, SketchFilter); } diff --git a/src/Mod/PartDesign/Gui/SketchWorkflow.h b/src/Mod/PartDesign/Gui/SketchWorkflow.h index 4f116063c6..a57f9fc85d 100644 --- a/src/Mod/PartDesign/Gui/SketchWorkflow.h +++ b/src/Mod/PartDesign/Gui/SketchWorkflow.h @@ -50,7 +50,7 @@ private: void tryCreateSketch(); std::tuple shouldCreateBody(); bool shouldAbort(bool) const; - std::tuple getFaceAndPlaneFilter() const; + std::tuple getFilters() const; private: Gui::Document* guidocument;