diff --git a/src/Mod/Assembly/App/AssemblyLink.cpp b/src/Mod/Assembly/App/AssemblyLink.cpp index 48d3857d21..b39c815bc3 100644 --- a/src/Mod/Assembly/App/AssemblyLink.cpp +++ b/src/Mod/Assembly/App/AssemblyLink.cpp @@ -532,8 +532,6 @@ void AssemblyLink::synchronizeJoints() assemblyLinkJoints = getJoints(); - AssemblyObject::recomputeJointPlacements(assemblyLinkJoints); - for (auto* joint : assemblyLinkJoints) { joint->purgeTouched(); } @@ -546,87 +544,55 @@ void AssemblyLink::handleJointReference( const char* refName ) { - AssemblyObject* assembly = getLinkedAssembly(); - - auto prop1 = dynamic_cast(joint->getPropertyByName(refName)); - auto prop2 = dynamic_cast(lJoint->getPropertyByName(refName)); + auto prop1 = dynamic_cast(joint->getPropertyByName(refName)); + auto prop2 = dynamic_cast(lJoint->getPropertyByName(refName)); if (!prop1 || !prop2) { return; } - App::DocumentObject* obj1 = nullptr; - App::DocumentObject* obj2 = prop2->getValue(); - std::vector subs1 = prop1->getSubValues(); - std::vector subs2 = prop2->getSubValues(); - if (subs1.empty()) { + // 1. Get the external component prop1 is [ExternalPart, "Sub"] + App::DocumentObject* externalComponent = prop1->getValue(); + if (!externalComponent) { return; } - // Example : - // Obj1 = docA-Asm1 Subs1 = ["part1.body.pad.face0", "part1.body.pad.vertex1"] - // Obj1 = docA-Part Subs1 = ["Asm1.part1.body.pad.face0", "Asm1.part1.body.pad.vertex1"] // some - // user may put the assembly inside a part... should become : Obj2 = docB-Asm2 Subs2 = - // ["Asm1Link.part1.linkTobody.pad.face0", "Asm1Link.part1.linkTobody.pad.vertex1"] Obj2 = - // docB-Part Sub2 = ["Asm2.Asm1Link.part1.linkTobody.pad.face0", - // "Asm2.Asm1Link.part1.linkTobody.pad.vertex1"] - - std::string asmLink = getNameInDocument(); - for (auto& sub : subs1) { - // First let's remove 'Asm1' name and everything before if any. - sub = removeUpToName(sub, assembly->getNameInDocument()); - // Then we add the assembly link name. - sub = asmLink + "." + sub; - // Then the question is, is there more to prepend? Because the parent assembly may have some - // parents So we check assemblyLink parents and prepend necessary parents. - bool first = true; - std::vector inList = getInList(); - int limit = 0; - while (!inList.empty() && limit < 20) { - ++limit; - bool found = false; - for (auto* obj : inList) { - if (obj->isDerivedFrom()) { - found = true; - if (first) { - first = false; - } - else { - std::string obj1Name = obj1->getNameInDocument(); - sub = obj1Name + "." + sub; - } - obj1 = obj; - break; - } - } - if (found) { - inList = obj1->getInList(); - } - else { - inList = {}; - } - } - - // Lastly we need to replace the object name by its link name. - auto* obj = getObjFromRef(prop1); - auto* link = objLinkMap[obj]; - if (!obj || !link) { - return; - } - std::string objName = obj->getNameInDocument(); - std::string linkName = link->getNameInDocument(); - sub = replaceLastOccurrence(sub, objName, linkName); + // 2. Map to local link + auto it = objLinkMap.find(externalComponent); + if (it == objLinkMap.end()) { + Base::Console().warning( + "AssemblyLink: Could not map external component %s to a local link for joint %s\n", + externalComponent->getNameInDocument(), + joint->getNameInDocument() + ); + return; } - // Now obj1 and the subs1 are what should be in obj2 and subs2 if the joint did not changed - if (obj1 != obj2) { - prop2->setValue(obj1); + App::DocumentObject* localLink = it->second; + + // 3. Set the new reference + // The local joint now points to the local link [LocalLink, "Sub"] + if (prop2->getValue() != localLink) { + prop2->setValue(localLink); } + + // 4. Sync sub-elements + // The sub-elements (e.g. "Body.Face1") are relative to the component. + // Since the LocalLink points to the ExternalPart, the relative path is identical. + std::vector subs1 = prop1->getSubValues(); + std::vector subs2 = prop2->getSubValues(); + bool changed = false; - for (size_t i = 0; i < subs1.size(); ++i) { - if (i >= subs2.size() || subs1[i] != subs2[i]) { - changed = true; - break; + if (subs1.size() != subs2.size()) { + changed = true; + } + else { + for (size_t i = 0; i < subs1.size(); ++i) { + if (subs1[i] != subs2[i]) { + changed = true; + break; + } } } + if (changed) { prop2->setSubValues(std::move(subs1)); } diff --git a/src/Mod/Assembly/App/AssemblyObject.cpp b/src/Mod/Assembly/App/AssemblyObject.cpp index 6121457e31..42baf4469d 100644 --- a/src/Mod/Assembly/App/AssemblyObject.cpp +++ b/src/Mod/Assembly/App/AssemblyObject.cpp @@ -132,7 +132,7 @@ App::DocumentObjectExecReturn* AssemblyObject::execute() "User parameter:BaseApp/Preferences/Mod/Assembly" ); if (hGrp->GetBool("SolveOnRecompute", true)) { - solve(); + solve(false, false); // No need to update jcs since recompute updated them. } return ret; } @@ -488,6 +488,7 @@ bool AssemblyObject::validateNewPlacements() void AssemblyObject::postDrag() { mbdAssembly->runPostDrag(); // Do this after last drag + purgeTouched(); } void AssemblyObject::savePlacementsForUndo() @@ -607,48 +608,27 @@ void AssemblyObject::redrawJointPlacement(App::DocumentObject* joint) return; } - // Notify the joint object that the transform of the coin object changed. - auto* pPlc = dynamic_cast(joint->getPropertyByName("Placement1")); - if (pPlc) { - pPlc->setValue(pPlc->getValue()); - } - pPlc = dynamic_cast(joint->getPropertyByName("Placement2")); - if (pPlc) { - pPlc->setValue(pPlc->getValue()); - } - joint->purgeTouched(); -} - -void AssemblyObject::recomputeJointPlacements(std::vector joints) -{ - // The Placement1 and Placement2 of each joint needs to be updated as the parts moved. Base::PyGILStateLocker lock; - for (auto* joint : joints) { - if (!joint) { - continue; - } - App::PropertyPythonObject* proxy = joint - ? dynamic_cast(joint->getPropertyByName("Proxy")) - : nullptr; + App::PropertyPythonObject* proxy = joint + ? dynamic_cast(joint->getPropertyByName("Proxy")) + : nullptr; - if (!proxy) { - continue; - } + if (!proxy) { + return; + } - Py::Object jointPy = proxy->getValue(); + Py::Object jointPy = proxy->getValue(); - if (!jointPy.hasAttr("updateJCSPlacements")) { - continue; - } + if (!jointPy.hasAttr("redrawJointPlacements")) { + return; + } - Py::Object attr = jointPy.getAttr("updateJCSPlacements"); - if (attr.ptr() && attr.isCallable()) { - Py::Tuple args(1); - args.setItem(0, Py::asObject(joint->getPyObject())); - Py::Callable(attr).apply(args); - joint->purgeTouched(); - } + Py::Object attr = jointPy.getAttr("redrawJointPlacements"); + if (attr.ptr() && attr.isCallable()) { + Py::Tuple args(1); + args.setItem(0, Py::asObject(joint->getPyObject())); + Py::Callable(attr).apply(args); } } @@ -687,8 +667,8 @@ App::DocumentObject* AssemblyObject::getJointOfPartConnectingToGround( continue; } - App::DocumentObject* part1 = getMovingPartFromRef(this, joint, "Reference1"); - App::DocumentObject* part2 = getMovingPartFromRef(this, joint, "Reference2"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); if (!part1 || !part2) { continue; } @@ -764,8 +744,8 @@ std::vector AssemblyObject::getJoints(bool updateJCS, bool continue; } - auto* part1 = getMovingPartFromRef(this, joint, "Reference1"); - auto* part2 = getMovingPartFromRef(this, joint, "Reference2"); + auto* part1 = getMovingPartFromRef(joint, "Reference1"); + auto* part2 = getMovingPartFromRef(joint, "Reference2"); if (!part1 || !part2 || part1->getFullName() == part2->getFullName()) { // Remove incomplete joints. Left-over when the user deletes a part. // Remove incoherent joints (self-pointing joints) @@ -791,11 +771,6 @@ std::vector AssemblyObject::getJoints(bool updateJCS, bool } } - // Make sure the joints are up to date. - if (updateJCS) { - recomputeJointPlacements(joints); - } - return joints; } @@ -834,8 +809,8 @@ std::vector AssemblyObject::getJointsOfObj(App::DocumentOb std::vector jointsOf; for (auto joint : joints) { - App::DocumentObject* obj1 = getObjFromRef(joint, "Reference1"); - App::DocumentObject* obj2 = getObjFromRef(joint, "Reference2"); + App::DocumentObject* obj1 = getObjFromJointRef(joint, "Reference1"); + App::DocumentObject* obj2 = getObjFromJointRef(joint, "Reference2"); if (obj == obj1 || obj == obj2) { jointsOf.push_back(joint); } @@ -854,8 +829,8 @@ std::vector AssemblyObject::getJointsOfPart(App::DocumentO std::vector jointsOf; for (auto joint : joints) { - App::DocumentObject* part1 = getMovingPartFromRef(this, joint, "Reference1"); - App::DocumentObject* part2 = getMovingPartFromRef(this, joint, "Reference2"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); if (part == part1 || part == part2) { jointsOf.push_back(joint); } @@ -961,7 +936,7 @@ bool AssemblyObject::isJointConnectingPartToGround(App::DocumentObject* joint, c return false; } - App::DocumentObject* part = getMovingPartFromRef(this, joint, propname); + App::DocumentObject* part = getMovingPartFromRef(joint, propname); if (!part) { return false; } @@ -1055,8 +1030,8 @@ void AssemblyObject::removeUnconnectedJoints( joints.begin(), joints.end(), [&](App::DocumentObject* joint) { - App::DocumentObject* obj1 = getMovingPartFromRef(this, joint, "Reference1"); - App::DocumentObject* obj2 = getMovingPartFromRef(this, joint, "Reference2"); + App::DocumentObject* obj1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* obj2 = getMovingPartFromRef(joint, "Reference2"); return ( !isObjInSetOfObjRefs(obj1, connectedParts) || !isObjInSetOfObjRefs(obj2, connectedParts) @@ -1100,8 +1075,8 @@ std::vector AssemblyObject::getConnectedParts( continue; } - App::DocumentObject* obj1 = getMovingPartFromRef(this, joint, "Reference1"); - App::DocumentObject* obj2 = getMovingPartFromRef(this, joint, "Reference2"); + App::DocumentObject* obj1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* obj2 = getMovingPartFromRef(joint, "Reference2"); if (!obj1 || !obj2) { continue; @@ -1674,12 +1649,12 @@ std::string AssemblyObject::handleOneSideOfJoint( const char* propPlcName ) { - App::DocumentObject* part = getMovingPartFromRef(this, joint, propRefName); - App::DocumentObject* obj = getObjFromRef(joint, propRefName); + App::DocumentObject* part = getMovingPartFromRef(joint, propRefName); + App::DocumentObject* obj = getObjFromJointRef(joint, propRefName); if (!part || !obj) { Base::Console() - .warning("The property %s of Joint %s is bad.", propRefName, joint->getFullName()); + .warning("The property %s of Joint %s is bad.\n", propRefName, joint->getFullName()); return ""; } @@ -1735,15 +1710,15 @@ void AssemblyObject::getRackPinionMarkers( swapJCS(joint); // make sure that rack is first. } - App::DocumentObject* part1 = getMovingPartFromRef(this, joint, "Reference1"); - App::DocumentObject* obj1 = getObjFromRef(joint, "Reference1"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* obj1 = getObjFromJointRef(joint, "Reference1"); Base::Placement plc1 = getPlacementFromProp(joint, "Placement1"); - App::DocumentObject* obj2 = getObjFromRef(joint, "Reference2"); + App::DocumentObject* obj2 = getObjFromJointRef(joint, "Reference2"); Base::Placement plc2 = getPlacementFromProp(joint, "Placement2"); if (!part1 || !obj1) { - Base::Console().warning("Reference1 of Joint %s is bad.", joint->getFullName()); + Base::Console().warning("Reference1 of Joint %s is bad.\n", joint->getFullName()); return; } @@ -1809,21 +1784,21 @@ void AssemblyObject::getRackPinionMarkers( int AssemblyObject::slidingPartIndex(App::DocumentObject* joint) { - App::DocumentObject* part1 = getMovingPartFromRef(this, joint, "Reference1"); - App::DocumentObject* obj1 = getObjFromRef(joint, "Reference1"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* obj1 = getObjFromJointRef(joint, "Reference1"); boost::ignore_unused(obj1); Base::Placement plc1 = getPlacementFromProp(joint, "Placement1"); - App::DocumentObject* part2 = getMovingPartFromRef(this, joint, "Reference2"); - App::DocumentObject* obj2 = getObjFromRef(joint, "Reference2"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); + App::DocumentObject* obj2 = getObjFromJointRef(joint, "Reference2"); boost::ignore_unused(obj2); Base::Placement plc2 = getPlacementFromProp(joint, "Placement2"); int slidingFound = 0; for (auto* jt : getJoints(false, false)) { if (getJointType(jt) == JointType::Slider) { - App::DocumentObject* jpart1 = getMovingPartFromRef(this, jt, "Reference1"); - App::DocumentObject* jpart2 = getMovingPartFromRef(this, jt, "Reference2"); + App::DocumentObject* jpart1 = getMovingPartFromRef(jt, "Reference1"); + App::DocumentObject* jpart2 = getMovingPartFromRef(jt, "Reference2"); int found = 0; Base::Placement plcjt, plci; if (jpart1 == part1 || jpart1 == part2) { @@ -1857,8 +1832,8 @@ bool AssemblyObject::isMbDJointValid(App::DocumentObject* joint) // When dragging a part, we are bundling fixed parts together. // This may lead to a conflicting joint that is self referencing a MbD part. // The solver crash when fed such a bad joint. So we make sure it does not happen. - App::DocumentObject* part1 = getMovingPartFromRef(this, joint, "Reference1"); - App::DocumentObject* part2 = getMovingPartFromRef(this, joint, "Reference2"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); if (!part1 || !part2) { return false; } @@ -1898,8 +1873,8 @@ AssemblyObject::MbDPartData AssemblyObject::getMbDData(App::DocumentObject* part for (auto* joint : joints) { JointType jointType = getJointType(joint); if (jointType == JointType::Fixed) { - App::DocumentObject* part1 = getMovingPartFromRef(this, joint, "Reference1"); - App::DocumentObject* part2 = getMovingPartFromRef(this, joint, "Reference2"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); App::DocumentObject* partToAdd = currentPart == part1 ? part2 : part1; if (objectPartMap.find(partToAdd) != objectPartMap.end()) { @@ -2030,7 +2005,7 @@ App::DocumentObject* AssemblyObject::getUpstreamMovingPart( return part; } - part = getMovingPartFromRef(this, joint, name == "Reference1" ? "Reference2" : "Reference1"); + part = getMovingPartFromRef(joint, name == "Reference1" ? "Reference2" : "Reference1"); return getUpstreamMovingPart(part, joint, name); } diff --git a/src/Mod/Assembly/App/AssemblyObject.h b/src/Mod/Assembly/App/AssemblyObject.h index f1295e4a89..74bc8e64d5 100644 --- a/src/Mod/Assembly/App/AssemblyObject.h +++ b/src/Mod/Assembly/App/AssemblyObject.h @@ -108,7 +108,6 @@ public: Base::Placement getMbdPlacement(std::shared_ptr mbdPart); bool validateNewPlacements(); void setNewPlacements(); - static void recomputeJointPlacements(std::vector joints); static void redrawJointPlacements(std::vector joints); static void redrawJointPlacement(App::DocumentObject* joint); diff --git a/src/Mod/Assembly/App/AssemblyUtils.cpp b/src/Mod/Assembly/App/AssemblyUtils.cpp index 4fd7356b0e..913d4cfe39 100644 --- a/src/Mod/Assembly/App/AssemblyUtils.cpp +++ b/src/Mod/Assembly/App/AssemblyUtils.cpp @@ -520,14 +520,19 @@ App::DocumentObject* getObjFromProp(const App::DocumentObject* joint, const char return propObj->getValue(); } -App::DocumentObject* getObjFromRef(const App::DocumentObject* obj, const std::string& sub) +App::DocumentObject* getObjFromRef(App::DocumentObject* comp, const std::string& sub) { - if (!obj) { + if (!comp) { return nullptr; } - const auto* doc = obj->getDocument(); - const auto names = Base::Tools::splitSubName(sub); + const auto* doc = comp->getDocument(); + auto names = Base::Tools::splitSubName(sub); + names.insert(names.begin(), comp->getNameInDocument()); + + if (names.size() <= 2) { + return comp; + } // Lambda function to check if the typeId is a BodySubObject const auto isBodySubObject = [](App::DocumentObject* obj) -> bool { @@ -618,7 +623,7 @@ App::DocumentObject* getObjFromRef(const App::PropertyXLinkSub* prop) return nullptr; } - const App::DocumentObject* obj = prop->getValue(); + App::DocumentObject* obj = prop->getValue(); if (!obj) { return nullptr; } @@ -631,7 +636,7 @@ App::DocumentObject* getObjFromRef(const App::PropertyXLinkSub* prop) return getObjFromRef(obj, subs[0]); } -App::DocumentObject* getObjFromRef(const App::DocumentObject* joint, const char* pName) +App::DocumentObject* getObjFromJointRef(const App::DocumentObject* joint, const char* pName) { if (!joint) { return nullptr; @@ -647,13 +652,13 @@ App::DocumentObject* getLinkedObjFromRef(const App::DocumentObject* joint, const return nullptr; } - if (const auto* obj = getObjFromRef(joint, pObj)) { + if (const auto* obj = getObjFromJointRef(joint, pObj)) { return obj->getLinkedObject(true); } return nullptr; } -App::DocumentObject* getMovingPartFromRef( +App::DocumentObject* getMovingPartFromSel( const AssemblyObject* assemblyObject, App::DocumentObject* obj, const std::string& sub @@ -710,39 +715,23 @@ App::DocumentObject* getMovingPartFromRef( return nullptr; } -App::DocumentObject* getMovingPartFromRef( - const AssemblyObject* assemblyObject, - App::PropertyXLinkSub* prop -) +App::DocumentObject* getMovingPartFromRef(App::PropertyXLinkSub* prop) { if (!prop) { return nullptr; } - App::DocumentObject* obj = prop->getValue(); - if (!obj) { - return nullptr; - } - - const std::vector subs = prop->getSubValues(); - if (subs.empty()) { - return nullptr; - } - return getMovingPartFromRef(assemblyObject, obj, subs[0]); + return prop->getValue(); } -App::DocumentObject* getMovingPartFromRef( - const AssemblyObject* assemblyObject, - App::DocumentObject* joint, - const char* pName -) +App::DocumentObject* getMovingPartFromRef(App::DocumentObject* joint, const char* pName) { if (!joint) { return nullptr; } auto* prop = joint->getPropertyByName(pName); - return getMovingPartFromRef(assemblyObject, prop); + return getMovingPartFromRef(prop); } void syncPlacements(App::DocumentObject* src, App::DocumentObject* to) diff --git a/src/Mod/Assembly/App/AssemblyUtils.h b/src/Mod/Assembly/App/AssemblyUtils.h index 52799830cd..f4f73d3e25 100644 --- a/src/Mod/Assembly/App/AssemblyUtils.h +++ b/src/Mod/Assembly/App/AssemblyUtils.h @@ -165,27 +165,24 @@ AssemblyExport App::DocumentObject* getObjFromProp( const App::DocumentObject* joint, const char* propName ); -AssemblyExport App::DocumentObject* getObjFromRef(const App::DocumentObject* obj, const std::string& sub); +AssemblyExport App::DocumentObject* getObjFromRef(App::DocumentObject* obj, const std::string& sub); AssemblyExport App::DocumentObject* getObjFromRef(const App::PropertyXLinkSub* prop); -AssemblyExport App::DocumentObject* getObjFromRef(const App::DocumentObject* joint, const char* propName); +AssemblyExport App::DocumentObject* getObjFromJointRef( + const App::DocumentObject* joint, + const char* propName +); AssemblyExport App::DocumentObject* getLinkedObjFromRef( const App::DocumentObject* joint, const char* propName ); -AssemblyExport App::DocumentObject* getMovingPartFromRef( +// Get the moving part from a selection, which has the full path. +AssemblyExport App::DocumentObject* getMovingPartFromSel( const AssemblyObject* assemblyObject, App::DocumentObject* obj, const std::string& sub ); -AssemblyExport App::DocumentObject* getMovingPartFromRef( - const AssemblyObject* assemblyObject, - const App::PropertyXLinkSub* prop -); -AssemblyExport App::DocumentObject* getMovingPartFromRef( - const AssemblyObject* assemblyObject, - App::DocumentObject* joint, - const char* pName -); +AssemblyExport App::DocumentObject* getMovingPartFromRef(const App::PropertyXLinkSub* prop); +AssemblyExport App::DocumentObject* getMovingPartFromRef(App::DocumentObject* joint, const char* pName); AssemblyExport std::vector getSubAsList(const App::PropertyXLinkSub* prop); AssemblyExport std::vector getSubAsList( const App::DocumentObject* joint, diff --git a/src/Mod/Assembly/AssemblyTests/TestCore.py b/src/Mod/Assembly/AssemblyTests/TestCore.py index 1aae10038a..b68fefc0f1 100644 --- a/src/Mod/Assembly/AssemblyTests/TestCore.py +++ b/src/Mod/Assembly/AssemblyTests/TestCore.py @@ -206,8 +206,8 @@ class TestCore(unittest.TestCase): JointObject.Joint(joint, 0) refs = [ - [self.assembly, [box2.Name + ".Face6", box2.Name + ".Vertex7"]], - [self.assembly, [box.Name + ".Face6", box.Name + ".Vertex7"]], + [box2, ["Face6", "Vertex7"]], + [box, ["Face6", "Vertex7"]], ] joint.Proxy.setJointConnectors(joint, refs) diff --git a/src/Mod/Assembly/CommandCreateJoint.py b/src/Mod/Assembly/CommandCreateJoint.py index aeb3ed1144..4d32057217 100644 --- a/src/Mod/Assembly/CommandCreateJoint.py +++ b/src/Mod/Assembly/CommandCreateJoint.py @@ -416,6 +416,8 @@ def createGroundedJoint(obj): ) Gui.doCommand(commands) Gui.doCommandGui("JointObject.ViewProviderGroundedJoint(ground.ViewObject)") + + Gui.doCommand("UtilsAssembly.activeAssembly().Document.recompute()") return Gui.doCommandEval("ground") @@ -471,8 +473,11 @@ class CommandToggleGrounded: Gui.doCommand(commands) continue - ref = [sel.Object, [sub, sub]] - moving_part = UtilsAssembly.getMovingPart(assembly, ref) + moving_part, new_sub = UtilsAssembly.getComponentReference( + assembly, sel.Object, sub + ) + if not moving_part: + continue # Only objects within the assembly. if moving_part is None: diff --git a/src/Mod/Assembly/CommandCreateView.py b/src/Mod/Assembly/CommandCreateView.py index bf40fb3527..540c4334d1 100644 --- a/src/Mod/Assembly/CommandCreateView.py +++ b/src/Mod/Assembly/CommandCreateView.py @@ -562,7 +562,8 @@ class ExplodedViewSelGate: self.viewObj = viewObj def allow(self, doc, obj, sub): - if (obj.Name == self.assembly.Name and sub) or self.assembly.hasObject(obj, True): + comp, new_sub = UtilsAssembly.getComponentReference(self.assembly, obj, sub) + if comp: # Objects within the assembly. return True @@ -662,6 +663,7 @@ class TaskAssemblyCreateView(QtCore.QObject): def reject(self): self.deactivate() App.closeActiveTransaction(True) + App.activeDocument().recompute() return True def deactivate(self): @@ -709,9 +711,14 @@ class TaskAssemblyCreateView(QtCore.QObject): continue for sub_name in sel.SubElementNames: - ref = [sel.Object, [sub_name]] + moving_part, new_sub = UtilsAssembly.getComponentReference( + self.assembly, sel.Object, sub_name + ) + if not moving_part: + continue + + ref = [moving_part, [new_sub]] obj = UtilsAssembly.getObject(ref) - moving_part = UtilsAssembly.getMovingPart(self.assembly, ref) element_name = UtilsAssembly.getElementName(sub_name) # Only objects within the assembly, not the assembly and not elements. @@ -733,6 +740,7 @@ class TaskAssemblyCreateView(QtCore.QObject): ref[1][0] = UtilsAssembly.truncateSubAtFirst(ref[1][0], obj.Name) if not obj in self.selectedObjs and hasattr(obj, "Placement"): + ref = [sel.Object, [sub_name]] self.selectedRefs.append(ref) self.selectedObjs.append(obj) self.selectedObjsInitPlc.append(App.Placement(obj.Placement)) @@ -992,9 +1000,12 @@ class TaskAssemblyCreateView(QtCore.QObject): return else: - ref = [App.getDocument(doc_name).getObject(obj_name), [sub_name]] + rootObj = App.getDocument(doc_name).getObject(obj_name) + moving_part, new_sub = UtilsAssembly.getComponentReference( + self.assembly, rootObj, sub_name + ) + ref = [moving_part, [new_sub]] obj = UtilsAssembly.getObject(ref) - moving_part = UtilsAssembly.getMovingPart(self.assembly, ref) if obj is None or moving_part is None: return diff --git a/src/Mod/Assembly/CommandSolveAssembly.py b/src/Mod/Assembly/CommandSolveAssembly.py index 7ab1fe8b4a..3c2bdd5175 100644 --- a/src/Mod/Assembly/CommandSolveAssembly.py +++ b/src/Mod/Assembly/CommandSolveAssembly.py @@ -64,9 +64,8 @@ class CommandSolveAssembly: if not assembly: return - Gui.addModule("UtilsAssembly") App.setActiveTransaction("Solve assembly") - Gui.doCommand("UtilsAssembly.activeAssembly().solve()") + assembly.recompute(True) App.closeActiveTransaction() diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index 5056ad9201..dd85b6b6c4 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -199,10 +199,10 @@ bool ViewProviderAssembly::canDragObjectToTarget(App::DocumentObject* obj, App:: for (auto joint : allJoints) { // getLinkObjFromProp returns nullptr if the property doesn't exist. - App::DocumentObject* part1 = getMovingPartFromRef(assemblyPart, joint, "Reference1"); - App::DocumentObject* part2 = getMovingPartFromRef(assemblyPart, joint, "Reference2"); - App::DocumentObject* obj1 = getObjFromRef(joint, "Reference1"); - App::DocumentObject* obj2 = getObjFromRef(joint, "Reference2"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); + App::DocumentObject* obj1 = getObjFromJointRef(joint, "Reference1"); + App::DocumentObject* obj2 = getObjFromJointRef(joint, "Reference2"); App::DocumentObject* obj3 = getObjFromProp(joint, "ObjectToGround"); if (obj == obj1 || obj == obj2 || obj == part1 || obj == part2 || obj == obj3) { if (!prompted) { @@ -782,7 +782,7 @@ bool ViewProviderAssembly::getSelectedObjectsWithinAssembly(bool addPreselection App::DocumentObject* selRoot = Gui::Selection().getPreselection().Object.getObject(); std::string sub = Gui::Selection().getPreselection().pSubName; - App::DocumentObject* obj = getMovingPartFromRef(assemblyPart, selRoot, sub); + App::DocumentObject* obj = getMovingPartFromSel(assemblyPart, selRoot, sub); if (canDragObjectIn3d(obj)) { bool alreadyIn = false; @@ -850,7 +850,7 @@ void ViewProviderAssembly::collectMovableObjects( return; } - App::DocumentObject* part = getMovingPartFromRef(assemblyPart, selRoot, subNamePrefix); + App::DocumentObject* part = getMovingPartFromSel(assemblyPart, selRoot, subNamePrefix); if (onlySolids && assemblyPart->isPartConnected(part)) { return; // No dragger for connected parts. @@ -955,7 +955,7 @@ ViewProviderAssembly::DragMode ViewProviderAssembly::findDragMode() if (!ref) { return DragMode::Translation; } - auto* obj = getObjFromRef(movingJoint, pName.c_str()); + auto* obj = getObjFromJointRef(movingJoint, pName.c_str()); Base::Placement global_plc = App::GeoFeature::getGlobalPlacement(obj, ref); jcsGlobalPlc = global_plc * jcsPlc; @@ -1022,6 +1022,7 @@ void ViewProviderAssembly::tryInitMove(const SbVec2s& cursorPos, Gui::View3DInve else if (visible) { joint->Visibility.setValue(false); } + joint->purgeTouched(); } SbVec3f vec; @@ -1100,6 +1101,7 @@ void ViewProviderAssembly::endMove() bool visible = pair.first->Visibility.getValue(); if (visible != pair.second) { pair.first->Visibility.setValue(pair.second); + pair.first->purgeTouched(); } } @@ -1468,10 +1470,8 @@ void ViewProviderAssembly::isolateJointReferences(App::DocumentObject* joint, Is return; } - AssemblyObject* assembly = getObject(); - - App::DocumentObject* part1 = getMovingPartFromRef(assembly, joint, "Reference1"); - App::DocumentObject* part2 = getMovingPartFromRef(assembly, joint, "Reference2"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); if (!part1 || !part2) { return; } diff --git a/src/Mod/Assembly/JointObject.py b/src/Mod/Assembly/JointObject.py index b349961a7b..7bae57aab6 100644 --- a/src/Mod/Assembly/JointObject.py +++ b/src/Mod/Assembly/JointObject.py @@ -201,16 +201,11 @@ class Joint: self.migrationScript4(joint) self.migrationScript5(joint) self.migrationScript6(joint) + self.migrationScript7(joint) # First Joint Connector if not hasattr(joint, "Reference1"): - joint.addProperty( - "App::PropertyXLinkSubHidden", - "Reference1", - "Joint Connector 1", - QT_TRANSLATE_NOOP("App::Property", "The first reference of the joint"), - locked=True, - ) + self.addReference1Property(joint) if not hasattr(joint, "Placement1"): joint.addProperty( @@ -250,13 +245,7 @@ class Joint: # Second Joint Connector if not hasattr(joint, "Reference2"): - joint.addProperty( - "App::PropertyXLinkSubHidden", - "Reference2", - "Joint Connector 2", - QT_TRANSLATE_NOOP("App::Property", "The second reference of the joint"), - locked=True, - ) + self.addReference2Property(joint) if not hasattr(joint, "Placement2"): joint.addProperty( @@ -373,6 +362,24 @@ class Joint: joint.setPropertyStatus("LengthMin", "AllowNegativeValues") joint.setPropertyStatus("LengthMax", "AllowNegativeValues") + def addReference1Property(self, joint): + joint.addProperty( + "App::PropertyXLinkSub", + "Reference1", + "Joint Connector 1", + QT_TRANSLATE_NOOP("App::Property", "The first reference of the joint"), + locked=True, + ) + + def addReference2Property(self, joint): + joint.addProperty( + "App::PropertyXLinkSub", + "Reference2", + "Joint Connector 2", + QT_TRANSLATE_NOOP("App::Property", "The second reference of the joint"), + locked=True, + ) + def addAngleProperty(self, joint): joint.addProperty( "App::PropertyAngle", @@ -681,6 +688,54 @@ class Joint: self.addAngleMaxProperty(joint) joint.AngleMax = old_value + def migrationScript7(self, joint): + """ + Migrates PropertyXLinkSubHidden (Assembly-rooted) to PropertyXLinkSub (Component-rooted). + """ + + def migrate_prop(prop_name, add_method, assembly): + if not hasattr(joint, prop_name): + return + + # Only migrate if it's the old type + if joint.getTypeIdOfProperty(prop_name) != "App::PropertyXLinkSubHidden": + return + + old_ref = getattr(joint, prop_name) + + joint.setPropertyStatus(prop_name, "-LockDynamic") + joint.removeProperty(prop_name) + + add_method(joint) + + if old_ref: + root = old_ref[0] + old_subs = old_ref[1] + if old_subs: + comp, first_new_sub = UtilsAssembly.getComponentReference( + assembly, root, old_subs[0] + ) + + if comp: + new_subs = [first_new_sub] + + # We can deduce the prefix length from the difference + # between old and new of the first item. + prefix_len = len(old_subs[0]) - len(first_new_sub) + + for i in range(1, len(old_subs)): + sub = old_subs[i] + if len(sub) >= prefix_len: + new_subs.append(sub[prefix_len:]) + else: + new_subs.append(sub) + + setattr(joint, prop_name, [comp, new_subs]) + + assembly = self.getAssembly(joint) + migrate_prop("Reference1", self.addReference1Property, assembly) + migrate_prop("Reference2", self.addReference2Property, assembly) + def dumps(self): return None @@ -772,6 +827,8 @@ class Joint: ): raise Exception(errStr + "Reference2") + self.updateJCSPlacements(joint) + def setJointConnectors(self, joint, refs): # current selection is a vector of strings like "Assembly.Assembly1.Assembly2.Body.Pad.Edge16" including both what selection return as obj_name and obj_sub assembly = self.getAssembly(joint) @@ -812,6 +869,14 @@ class Joint: if not joint.Detach2: joint.Placement2 = self.findPlacement(joint, joint.Reference2, 1) + self.redrawJointPlacements(joint) + + def redrawJointPlacements(self, joint): + if joint.ViewObject: + proxy = joint.ViewObject.Proxy + if proxy: + proxy.redrawJointPlacements(joint) + """ So here we want to find a placement that corresponds to a local coordinate system that would be placed at the selected vertex. - obj is usually a App::Link to a PartDesign::Body, or primitive, fasteners. But can also be directly the object.1 @@ -850,8 +915,8 @@ class Joint: if reverse: sameDir = not sameDir - part1 = UtilsAssembly.getMovingPart(assembly, joint.Reference1) - part2 = UtilsAssembly.getMovingPart(assembly, joint.Reference2) + part1 = UtilsAssembly.getMovingPart(joint.Reference1) + part2 = UtilsAssembly.getMovingPart(joint.Reference2) if not part1 or not part2: return False @@ -915,7 +980,8 @@ class Joint: part.Placement = plc self.partsMovedByPresolved = {} - joint.Placement1 = joint.Placement1 # Make sure plc1 is redrawn + if joint.ViewObject: + joint.ViewObject.Proxy.redrawJointPlacements(joint) def preventParallel(self, joint): # Angle and perpendicular joints in the solver cannot handle the situation where both JCS are Parallel @@ -925,8 +991,8 @@ class Joint: assembly = self.getAssembly(joint) - part1 = UtilsAssembly.getMovingPart(assembly, joint.Reference1) - part2 = UtilsAssembly.getMovingPart(assembly, joint.Reference2) + part1 = UtilsAssembly.getMovingPart(joint.Reference1) + part2 = UtilsAssembly.getMovingPart(joint.Reference2) isAssembly = assembly.Type == "Assembly" if isAssembly: @@ -999,24 +1065,25 @@ class ViewProviderJoint: def updateData(self, joint, prop): """If a property of the handled feature has changed we have the chance to handle this here""" - # joint is the handled feature, prop is the name of the property that has changed - if prop == "Placement1": - if hasattr(joint, "Reference1") and joint.Reference1: - plc = joint.Placement1 - self.switch_JCS1.whichChild = coin.SO_SWITCH_ALL + if prop == "Placement1" and hasattr(joint, "Reference1"): + self.redrawJointPlacement(self.switch_JCS1, joint.Placement1, joint.Reference1) - self.switch_JCS1.set_marker_placement(plc, joint.Reference1) - else: - self.switch_JCS1.whichChild = coin.SO_SWITCH_NONE + if prop == "Placement2" and hasattr(joint, "Reference2"): + self.redrawJointPlacement(self.switch_JCS2, joint.Placement2, joint.Reference2) - if prop == "Placement2": - if hasattr(joint, "Reference2") and joint.Reference2: - plc = joint.Placement2 - self.switch_JCS2.whichChild = coin.SO_SWITCH_ALL + def redrawJointPlacements(self, joint): + if not hasattr(joint, "Reference1") or not hasattr(joint, "Reference2"): + return - self.switch_JCS2.set_marker_placement(plc, joint.Reference2) - else: - self.switch_JCS2.whichChild = coin.SO_SWITCH_NONE + self.redrawJointPlacement(self.switch_JCS1, joint.Placement1, joint.Reference1) + self.redrawJointPlacement(self.switch_JCS2, joint.Placement2, joint.Reference2) + + def redrawJointPlacement(self, jcs, plc, ref): + if ref: + jcs.whichChild = coin.SO_SWITCH_ALL + jcs.set_marker_placement(plc, ref) + else: + jcs.whichChild = coin.SO_SWITCH_NONE def showPreviewJCS(self, visible, placement=None, ref=None): if visible: @@ -1091,7 +1158,7 @@ class ViewProviderJoint: assembly = self.app_obj.Proxy.getAssembly(self.app_obj) # Assuming Reference1 corresponds to the first part link if hasattr(self.app_obj, "Reference1"): - part = UtilsAssembly.getMovingPart(assembly, self.app_obj.Reference1) + part = UtilsAssembly.getMovingPart(self.app_obj.Reference1) if part is not None and not assembly.isPartConnected(part): overlays[Gui.IconPosition.BottomLeft] = "Part_Detached" @@ -1531,7 +1598,6 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.deactivate() - solveIfAllowed(self.assembly) if self.activeType == "Assembly": self.joint.Visibility = self.visibilityBackup else: @@ -1540,14 +1606,15 @@ class TaskAssemblyCreateJoint(QtCore.QObject): cmds = UtilsAssembly.generatePropertySettings(self.joint) Gui.doCommand(cmds) + self.assembly.recompute(True) + App.closeActiveTransaction() return True def reject(self): self.deactivate() App.closeActiveTransaction(True) - if not self.creating: # update visibility only if we are editing the joint - self.joint.Visibility = self.visibilityBackup + self.assembly.recompute(True) return True def autoClosedOnTransactionChange(self): @@ -1596,8 +1663,15 @@ class TaskAssemblyCreateJoint(QtCore.QObject): for sub_name in sel.SubElementNames: # We add sub_name twice because the joints references have element name + vertex name # and in the case of initial selection, both are the same. - ref = [sel.Object, [sub_name, sub_name]] - moving_part = self.getMovingPart(ref) + + moving_part, new_sub = UtilsAssembly.getComponentReference( + self.assembly, sel.Object, sub_name + ) + if not moving_part: + break + + # Construct the reference using the Component as the root + ref = [moving_part, [new_sub, new_sub]] # Only objects within the assembly. if moving_part is None: @@ -1628,9 +1702,12 @@ class TaskAssemblyCreateJoint(QtCore.QObject): joint_group = UtilsAssembly.getJointGroup(self.assembly) self.joint = joint_group.newObject("App::FeaturePython", "Joint") self.joint.Label = self.jointName + joint_group.purgeTouched() + self.assembly.purgeTouched() Joint(self.joint, type_index) ViewProviderJoint(self.joint.ViewObject) + self.joint.purgeTouched() def onJointTypeChanged(self, index): self.jType = JointTypes[self.jForm.jointType.currentIndex()] @@ -1836,14 +1913,15 @@ class TaskAssemblyCreateJoint(QtCore.QObject): ref1 = self.joint.Reference1 ref2 = self.joint.Reference2 - self.refs.append(ref1) - self.refs.append(ref2) + if UtilsAssembly.isRefValid(ref1, 2): + self.refs.append(ref1) + sub1 = UtilsAssembly.addTipNameToSub(ref1) + Gui.Selection.addSelection(ref1[0].Document.Name, ref1[0].Name, sub1) - sub1 = UtilsAssembly.addTipNameToSub(ref1) - sub2 = UtilsAssembly.addTipNameToSub(ref2) - - Gui.Selection.addSelection(ref1[0].Document.Name, ref1[0].Name, sub1) - Gui.Selection.addSelection(ref2[0].Document.Name, ref2[0].Name, sub2) + if UtilsAssembly.isRefValid(ref2, 2): + self.refs.append(ref2) + sub2 = UtilsAssembly.addTipNameToSub(ref2) + Gui.Selection.addSelection(ref2[0].Document.Name, ref2[0].Name, sub2) self.jForm.angleSpinbox.setProperty("rawValue", self.joint.Angle.Value) self.jForm.distanceSpinbox.setProperty("rawValue", self.joint.Distance.Value) @@ -2025,7 +2103,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self._removeSelectedItems(selected_indexes) def getMovingPart(self, ref): - return UtilsAssembly.getMovingPart(self.assembly, ref) + return UtilsAssembly.getMovingPart(ref) # selectionObserver stuff def addSelection(self, doc_name, obj_name, sub_name, mousePos): @@ -2038,7 +2116,15 @@ class TaskAssemblyCreateJoint(QtCore.QObject): sub_name = UtilsAssembly.fixBodyExtraFeatureInSub(doc_name, sub_name) - ref = [rootObj, [sub_name]] + comp, new_sub = UtilsAssembly.getComponentReference(self.assembly, rootObj, sub_name) + if not comp: + # Selection was not valid (not inside assembly or logic failed) + Gui.Selection.removeSelection(doc_name, obj_name, sub_name) + return + + # Construct the reference using the Component as the root + ref = [comp, [new_sub]] + moving_part = self.getMovingPart(ref) # Check if the addition is acceptable (we are not doing this in selection gate to let user move objects) @@ -2085,12 +2171,16 @@ class TaskAssemblyCreateJoint(QtCore.QObject): sub_name = UtilsAssembly.fixBodyExtraFeatureInSub(doc_name, sub_name) + comp, new_sub = UtilsAssembly.getComponentReference(self.assembly, rootObj, sub_name) + if not comp: + return + for reference in self.refs[:]: ref_obj = reference[0] ref_element_name = reference[1][0] if len(reference[1]) > 0 else "" # match both object and processed element name for precise identification - if ref_obj == rootObj and ref_element_name == sub_name: + if ref_obj == comp and ref_element_name == new_sub: self.refs.remove(reference) break else: @@ -2103,7 +2193,13 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.presel_ref = None return - self.presel_ref = [App.getDocument(doc_name).getObject(obj_name), [sub_name]] + rootObj = App.getDocument(doc_name).getObject(obj_name) + + comp, new_sub = UtilsAssembly.getComponentReference(self.assembly, rootObj, sub_name) + if not comp: + return + + self.presel_ref = [comp, [new_sub]] def clearSelection(self, doc_name): self.refs.clear() diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index ca26472864..a893d25a22 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -128,7 +128,7 @@ def isLinkGroup(obj): def getObject(ref): - if len(ref) != 2: + if len(ref) != 2 or ref[0] is None: return None subs = ref[1] if len(subs) < 1: @@ -139,7 +139,10 @@ def getObject(ref): # or "LinkOrAssembly1.LinkOrPart1.LinkOrBody.pad.Edge16" # or "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBody.Local_CS.X" # We want either LinkOrBody or LinkOrBox or Local_CS. + # Note since the ref now holds the moving part, sub_name can now be just the element name. + # In this case the obj we need is the reference obj names = sub_name.split(".") + names.insert(0, ref[0].Name) if len(names) < 2: return None @@ -347,24 +350,6 @@ def getRootPath(obj, part): return None, "" -# get the placement of Obj relative to its moving Part -# Example : assembly.part1.part2.partn.body1 : placement of Obj relative to part1 -def getObjPlcRelativeToPart(assembly, ref): - # we need plc to be relative to the moving part - moving_part = getMovingPart(assembly, ref) - obj_global_plc = getGlobalPlacement(ref) - part_global_plc = getGlobalPlacement(ref, moving_part) - - return part_global_plc.inverse() * obj_global_plc - - -# Example : assembly.part1.part2.partn.body1 : jcsPlc is relative to body1 -# This function returns jcsPlc relative to part1 -def getJcsPlcRelativeToPart(assembly, jcsPlc, ref): - obj_relative_plc = getObjPlcRelativeToPart(assembly, ref) - return obj_relative_plc * jcsPlc - - # Return the jcs global placement def getJcsGlobalPlc(jcsPlc, ref): obj_global_plc = getGlobalPlacement(ref) @@ -382,6 +367,15 @@ def getGlobalPlacement(ref, targetObj=None): rootObj = ref[0] subName = ref[1][0] + # ref[0] is no longer the root object. Now it's the moving part. + # So to get the correct global placement in case user transformed the assembly, + # or if he has nested the assembly, we need to adjust it. + # Example : Part / Assembly / Cylinder / Face1 + # ref is [(Cylinder, 'Face1')] + parents = rootObj.Parents # [(, 'Assembly.Cylinder.')] + if parents is not None and len(parents) == 1 and len(parents[0]) == 2: + rootObj = parents[0][0] + subName = parents[0][1] + subName return rootObj.getPlacementOf(subName, targetObj) @@ -398,7 +392,7 @@ def getElementName(full_name): # We want either Edge16. parts = full_name.split(".") - if len(parts) < 2: + if len(parts) < 1: # At minimum "Box.edge16". It shouldn't be shorter return "" @@ -1214,59 +1208,79 @@ def getJointXYAngle(joint): return math.atan2(x_axis.y, x_axis.x) -def getMovingPart(assembly, ref): - # ref can be : - # [assembly, ['box.edge1', 'box.vertex2']] - # [Part, ['Assembly.box.edge1', 'Assembly.box.vertex2']] - # [assembly, ['Body.Pad.edge1', 'Body.Pad.vertex2']] - - if assembly is None or ref is None or len(ref) != 2: +def getMovingPart(ref): + if ref is None or len(ref) != 2: return None obj = ref[0] - subs = ref[1] - if subs is None or len(subs) < 1: - return None + return obj - sub = ref[1][0] # All subs should have the same object paths. - names = [obj.Name] + sub.split(".") - try: - index = names.index(assembly.Name) - # Get the sublist starting after the after the assembly (in case of Part1/Assembly/...) - names = names[index + 1 :] - except ValueError: - return None +def getComponentReference(assembly, root_obj, sub_string): + """ + Takes a full selection path (root + subnames) and normalizes it + to be rooted at the Assembly Component (Moving Part). + + Returns: (ComponentObject, RelativeSubString) or (None, "") + """ + if not assembly or not root_obj: + return None, "" doc = assembly.Document - if len(names) < 2: - App.Console.PrintError( - f"getMovingPart() in UtilsAssembly.py the object name {names} is too short. It should be at least similar to ['Box','edge16'], not shorter.\n" - ) - return None + # 1. Reconstruct full path + # e.g. ['Part', 'Assembly', 'Cylinder', 'Face1'] + names = [root_obj.Name] + sub_string.split(".") - for objName in names: - obj = doc.getObject(objName) + # 2. Find Assembly in path + try: + asm_idx = names.index(assembly.Name) + except ValueError: + return None, "" + # 3. Identify Component (first valid object after Assembly) + candidates = names[asm_idx + 1 :] + if not candidates: + return None, "" + + component = None + comp_idx = -1 + + for i, obj_name in enumerate(candidates): + obj = doc.getObject(obj_name) if not obj: continue - if obj.TypeId == "App::DocumentObjectGroup": - continue # we ignore groups. - - # We ignore dynamic sub-assemblies. - if obj.isDerivedFrom("Assembly::AssemblyLink") and obj.Rigid == False: + # Skips (Groups / Flexible Links / LinkGroups / non-geofeature objects) + if obj.isDerivedFrom("App::DocumentObjectGroup"): continue - - # If it is a LinkGroup then we skip it + if obj.isDerivedFrom("Assembly::AssemblyLink"): + if hasattr(obj, "Rigid") and not obj.Rigid: + continue if isLinkGroup(obj): continue - return obj + if isLink(obj): + linkedObj = obj.getLinkedObject() + if linkedObj and not linkedObj.isDerivedFrom("App::GeoFeature"): + continue + elif not obj.isDerivedFrom("App::GeoFeature"): + continue - return None + component = obj + comp_idx = asm_idx + 1 + i + break + + if not component: + return None, "" + + # 4. Construct new sub-string + # Everything after the component in the original names list + relative_parts = names[comp_idx + 1 :] + new_sub = ".".join(relative_parts) + + return component, new_sub def truncateSubAtFirst(sub, target):