From a6efacdf24a69b48795fc1e0ca112a8405beeb02 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Wed, 9 Oct 2024 16:30:17 +0200 Subject: [PATCH] TaskAttacher: Make sure hierarchy is respected when adding references. --- src/Mod/Part/App/Attacher.cpp | 78 +++++++++++-------- src/Mod/Part/Gui/TaskAttacher.cpp | 120 ++++++++++++++++++++++++++++-- src/Mod/Part/Gui/TaskAttacher.h | 2 + 3 files changed, 163 insertions(+), 37 deletions(-) diff --git a/src/Mod/Part/App/Attacher.cpp b/src/Mod/Part/App/Attacher.cpp index b452fd90f4..2dd69ed34f 100644 --- a/src/Mod/Part/App/Attacher.cpp +++ b/src/Mod/Part/App/Attacher.cpp @@ -27,6 +27,7 @@ # include # include # include +# include # include # include # include @@ -820,7 +821,7 @@ GProp_GProps AttachEngine::getInertialPropsOfShape(const std::vector &objs, - const std::vector &sub, + const std::vector &subs, std::vector &geofs, std::vector &shapes, std::vector &storage, @@ -831,50 +832,56 @@ void AttachEngine::readLinks(const std::vector &objs, shapes.resize(objs.size()); types.resize(objs.size()); for (std::size_t i = 0; i < objs.size(); i++) { - if (!objs[i]->getTypeId().isDerivedFrom(App::GeoFeature::getClassTypeId())) { + std::string fullSub = subs[i]; + const char* element = Data::findElementName(fullSub.c_str()); + App::DocumentObject* obj = objs[i]->getSubObject(subs[i].c_str()); + + auto* geof = dynamic_cast(obj); + if (!geof) { FC_THROWM(AttachEngineException, "AttachEngine3D: attached to a non App::GeoFeature '" - << objs[i]->getNameInDocument() << "'"); + << obj->getNameInDocument() << "'"); } - auto* geof = dynamic_cast(objs[i]); geofs[i] = geof; - Part::TopoShape shape; + TopoDS_Shape myShape; + Base::Placement plc = App::GeoFeature::getGlobalPlacement(obj, objs[i], fullSub); if (geof->isDerivedFrom(App::Plane::getClassTypeId())) { // obtain Z axis and origin of placement Base::Vector3d norm; - geof->Placement.getValue().getRotation().multVec(Base::Vector3d(0.0, 0.0, 1.0), norm); + plc.getRotation().multVec(Base::Vector3d(0.0, 0.0, 1.0), norm); Base::Vector3d org; - geof->Placement.getValue().multVec(Base::Vector3d(), org); + plc.multVec(Base::Vector3d(), org); // make shape - an local-XY plane infinite face gp_Pln plane = gp_Pln(gp_Pnt(org.x, org.y, org.z), gp_Dir(norm.x, norm.y, norm.z)); - TopoDS_Shape myShape = BRepBuilderAPI_MakeFace(plane).Shape(); + myShape = BRepBuilderAPI_MakeFace(plane).Shape(); myShape.Infinite(true); - storage.emplace_back(myShape); - shapes[i] = &(storage[storage.size() - 1]); } else if (geof->isDerivedFrom(App::Line::getClassTypeId())) { // obtain X axis and origin of placement - // note an inconsistency: App::Line is along local X, PartDesign::DatumLine is along - // local Z. Base::Vector3d dir; - geof->Placement.getValue().getRotation().multVec(Base::Vector3d(1.0, 0.0, 0.0), dir); + plc.getRotation().multVec(Base::Vector3d(0.0, 0.0, 1.0), dir); Base::Vector3d org; - geof->Placement.getValue().multVec(Base::Vector3d(), org); + plc.multVec(Base::Vector3d(), org); // make shape - an infinite line along local X axis gp_Lin line = gp_Lin(gp_Pnt(org.x, org.y, org.z), gp_Dir(dir.x, dir.y, dir.z)); - TopoDS_Shape myShape = BRepBuilderAPI_MakeEdge(line).Shape(); + myShape = BRepBuilderAPI_MakeEdge(line).Shape(); myShape.Infinite(true); - storage.emplace_back(myShape); - shapes[i] = &(storage[storage.size() - 1]); + } + else if (geof->isDerivedFrom(App::Point::getClassTypeId())) { + Base::Vector3d org; + plc.multVec(Base::Vector3d(), org); + + gp_Pnt pnt = gp_Pnt(org.x, org.y, org.z); + myShape = BRepBuilderAPI_MakeVertex(pnt).Shape(); } else { try { - shape = Part::Feature::getTopoShape(geof, sub[i].c_str(), true); + Part::TopoShape shape = Part::Feature::getTopoShape(geof, element, true); for (;;) { if (shape.isNull()) { FC_THROWM(AttachEngineException, "AttachEngine3D: subshape not found " - << objs[i]->getNameInDocument() << '.' << sub[i]); + << objs[i]->getNameInDocument() << '.' << subs[i]); } if (shape.shapeType() != TopAbs_COMPOUND || shape.countSubShapes(TopAbs_SHAPE) != 1) { @@ -883,32 +890,34 @@ void AttachEngine::readLinks(const std::vector &objs, // auto extract the single sub-shape from a compound shape = shape.getSubTopoShape(TopAbs_SHAPE, 1); } - storage.emplace_back(shape.getShape()); + shape.setPlacement(plc); + myShape = shape.getShape(); } catch (Standard_Failure& e) { FC_THROWM(AttachEngineException, "AttachEngine3D: subshape not found " << objs[i]->getNameInDocument() - << '.' << sub[i] << std::endl + << '.' << subs[i] << std::endl << e.GetMessageString()); } catch (Base::CADKernelError& e) { FC_THROWM(AttachEngineException, "AttachEngine3D: subshape not found " << objs[i]->getNameInDocument() - << '.' << sub[i] << std::endl + << '.' << subs[i] << std::endl << e.what()); } - if (storage.back().IsNull()) { + if (myShape.IsNull()) { FC_THROWM(AttachEngineException, "AttachEngine3D: null subshape " << objs[i]->getNameInDocument() << '.' - << sub[i]); + << subs[i]); } - shapes[i] = &(storage.back()); } + storage.emplace_back(myShape); + shapes[i] = &(storage.back()); // FIXME: unpack single-child compounds here? Compounds are not used so far, so it should be // considered later, when the need arises. types[i] = getShapeType(*(shapes[i])); - if (sub[i].length() == 0) { + if (subs[i].length() == 0) { types[i] = eRefType(types[i] | rtFlagHasPlacement); } } @@ -977,6 +986,7 @@ Base::Placement AttachEngine::calculateAttachedPlacement(const Base::Placement& for (auto obj : objs) { ++i; auto& sub = subnames[i]; + obj = obj->getSubObject(sub.c_str()); auto& shadow = shadowSubs[i]; if (shadow.empty() || !Data::hasMissingElement(sub.c_str())) { continue; @@ -1176,9 +1186,13 @@ AttachEngine3D::_calculateAttachedPlacement(const std::vectorPlacement.getValue(); - refOrg = gp_Pnt(Place.getPosition().x, Place.getPosition().y, Place.getPosition().z); + Base::Placement Place = App::GeoFeature::getGlobalPlacement(parts[0], objs[0], subs[0]); + Base::Console().Warning("parts[0] = %s\n", parts[0]->getNameInDocument()); + Base::Console().Warning("objs[0] = %s\n", objs[0]->getNameInDocument()); + Base::Console().Warning("subs[0] = %s\n", subs[0]); + Base::Console().Warning("Place = (%f, %f, %f)\n", Place.getPosition().x, Place.getPosition().y, Place.getPosition().z); + Base::Vector3d vec = Place.getPosition(); + gp_Pnt refOrg = gp_Pnt(vec.x, vec.y, vec.z); // origin of linked object // variables to derive the actual placement. // They are to be set, depending on the mode: @@ -2120,9 +2134,9 @@ AttachEngineLine::_calculateAttachedPlacement(const std::vectorPlacement.getValue(); - refOrg = gp_Pnt(Place.getPosition().x, Place.getPosition().y, Place.getPosition().z); + Base::Placement Place = App::GeoFeature::getGlobalPlacement(parts[0], objs[0], subs[0]); + Base::Vector3d vec = Place.getPosition(); + gp_Pnt refOrg = gp_Pnt(vec.x, vec.y, vec.z); // origin of linked object // variables to derive the actual placement. // They are to be set, depending on the mode: diff --git a/src/Mod/Part/Gui/TaskAttacher.cpp b/src/Mod/Part/Gui/TaskAttacher.cpp index 1b57ea6daf..547ff84d25 100644 --- a/src/Mod/Part/Gui/TaskAttacher.cpp +++ b/src/Mod/Part/Gui/TaskAttacher.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -112,7 +113,7 @@ void TaskAttacher::makeRefStrings(std::vector& refstrings, std::vector< TaskAttacher::TaskAttacher(Gui::ViewProviderDocumentObject* ViewProvider, QWidget* parent, QString picture, QString text, TaskAttacher::VisibilityFunction visFunc) : TaskBox(Gui::BitmapFactory().pixmap(picture.toLatin1()), text, true, parent) - , SelectionObserver(ViewProvider) + , SelectionObserver(ViewProvider, true, Gui::ResolveMode::NoResolve) , ViewProvider(ViewProvider) , ui(new Ui_TaskAttacher) , visibilityFunc(visFunc) @@ -365,6 +366,112 @@ QLineEdit* TaskAttacher::getLine(unsigned idx) } } +void TaskAttacher::processSelection(App::DocumentObject*& rootObj, std::string& sub) +{ + // The reference that we store must take into account the hierarchy of geoFeatures. For example: + // - Part + // - - Cube + // - Sketch + // if sketch is attached to Cube.Face1 then it must store Part:Cube.Face3 as Sketch is outside of Part. + // - Part + // - - Cube + // - - Sketch + // In this example if must store Cube:Face3 because Sketch is inside Part, sibling of Cube. + // So placement of Part is already taken into account. + // - Part1 + // - - Part2 + // - - - Cube + // - - Sketch + // In this example it must store Part2:Cube.Face3 since Part1 is already taken into account. + // - Part1 + // - - Part2 + // - - - Cube + // - - Part3 + // - - - Sketch + // In this example it's not possible because Sketch has Part3 placement. So it should be rejected + // So we need to take the selection object and subname, and process them to get the correct obj/sub based + // on attached and attaching objects positions. + + std::vector names = Base::Tools::splitSubName(sub); + if (!rootObj || names.size() < 2) { + return; + } + names.insert(names.begin(), rootObj->getNameInDocument()); + + App::Document* doc = rootObj->getDocument(); + App::DocumentObject* attachingObj = ViewProvider->getObject(); // Attaching object + App::DocumentObject* subObj = rootObj->getSubObject(sub.c_str()); // Object being attached. + if (!subObj || subObj == rootObj) { + // Case of root object. We don't need to modify it. + return; + } + if (subObj == attachingObj) { + //prevent self-referencing + rootObj = nullptr; + return; + } + + // Check if attachingObj is a root object. if so we keep the full path. + auto* group = App::GeoFeatureGroupExtension::getGroupOfObject(attachingObj); + if (!group) { + if (attachingObj->getDocument() != rootObj->getDocument()) { + // If it's not in same document then it's not a good selection + rootObj = nullptr; + } + // if it's same document we keep the rootObj and sub unchanged. + return; + } + + for (size_t i = 0; i < names.size(); ++i) { + App::DocumentObject* obj = doc->getObject(names[i].c_str()); + if (!obj) { + Base::Console().TranslatedUserError("TaskAttacher", + "Unsuitable selection: '%s' cannot be attached to '%s' from within it's group '%s'.\n", + attachingObj->getFullLabel(), subObj->getFullLabel(), group->getFullLabel()); + rootObj = nullptr; + return; + } + + // In case the attaching object is in a link to a part. + // For instance : + // - Part1 + // - - LinkToPart2 + // - - - Cube + // - - - Sketch + obj = obj->getLinkedObject(); + + if (obj == group) { + ++i; + obj = doc->getObject(names[i].c_str()); + if (!obj) { + return; + } + + rootObj = obj; + + // Rebuild 'sub' starting from the next element after the current 'name' + sub = ""; + for (size_t j = i + 1; j < names.size(); ++j) { + sub += names[j]; + if (j != names.size() - 1) { + sub += "."; // Add a period between elements + } + } + return; + } + } + + // if we reach this point it means that attaching object's group is outside of + // the scope of the attached object. For instance: + // - Part1 + // - - Part2 + // - - - Cube + // - - Part3 + // - - - Sketch + // In this case the selection is not acceptable. + rootObj = nullptr; +} + void TaskAttacher::onSelectionChanged(const Gui::SelectionChanges& msg) { if (!ViewProvider) { @@ -377,14 +484,17 @@ void TaskAttacher::onSelectionChanged(const Gui::SelectionChanges& msg) } // Note: The validity checking has already been done in ReferenceSelection.cpp - Part::AttachExtension* pcAttach = ViewProvider->getObject()->getExtensionByType(); + App::DocumentObject* obj = ViewProvider->getObject(); + Part::AttachExtension* pcAttach = obj->getExtensionByType(); std::vector refs = pcAttach->AttachmentSupport.getValues(); std::vector refnames = pcAttach->AttachmentSupport.getSubValues(); - App::DocumentObject* selObj = ViewProvider->getObject()->getDocument()->getObject(msg.pObjectName); - if (!selObj || selObj == ViewProvider->getObject())//prevent self-referencing - return; + App::DocumentObject* selObj = obj->getDocument()->getObject(msg.pObjectName); std::string subname = msg.pSubName; + processSelection(selObj, subname); + if (!selObj) { + return; + } // Remove subname for planes and datum features if (selObj->isDerivedFrom() || selObj->isDerivedFrom()) { diff --git a/src/Mod/Part/Gui/TaskAttacher.h b/src/Mod/Part/Gui/TaskAttacher.h index 8cb5192c00..c8cb3fce9f 100644 --- a/src/Mod/Part/Gui/TaskAttacher.h +++ b/src/Mod/Part/Gui/TaskAttacher.h @@ -113,6 +113,8 @@ private: void updateRefButton(int idx); void updateAttachmentOffsetUI(); + void processSelection(App::DocumentObject*& obj, std::string& sub); + /** * @brief updateListOfModes Fills the mode list with modes that apply to * current set of references. Maintains selection when possible.