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
This commit is contained in:
PaddleStroke
2025-09-01 17:39:51 +02:00
committed by GitHub
parent 7d38cc45ec
commit f51e1eeb22
3 changed files with 57 additions and 15 deletions

View File

@@ -842,9 +842,19 @@ void AttachEngine::readLinks(const std::vector<App::DocumentObject*>& 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) {

View File

@@ -197,17 +197,18 @@ class SketchPreselection
{
public:
SketchPreselection(Gui::Document* guidocument, PartDesign::Body* activeBody,
std::tuple<Gui::SelectionFilter, Gui::SelectionFilter> filter)
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();
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<Gui::SelectionFilter, Gui::SelectionFilter> SketchWorkflow::getFaceAndPlaneFilter() const
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:
@@ -815,9 +832,11 @@ std::tuple<Gui::SelectionFilter, Gui::SelectionFilter> 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);
}

View File

@@ -50,7 +50,7 @@ private:
void tryCreateSketch();
std::tuple<bool, PartDesign::Body*> shouldCreateBody();
bool shouldAbort(bool) const;
std::tuple<Gui::SelectionFilter, Gui::SelectionFilter> getFaceAndPlaneFilter() const;
std::tuple<Gui::SelectionFilter, Gui::SelectionFilter, Gui::SelectionFilter> getFilters() const;
private:
Gui::Document* guidocument;