From 8d3e3acd11067922403bbce82a436dcf68c2acf1 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Fri, 12 Jul 2024 08:36:14 +0200 Subject: [PATCH] Assembly: Property change from Object/Part to Reference --- src/Mod/Assembly/App/AssemblyObject.cpp | 587 ++++++++++------- src/Mod/Assembly/App/AssemblyObject.h | 52 +- src/Mod/Assembly/AssemblyTests/TestCore.py | 38 +- src/Mod/Assembly/CommandCreateJoint.py | 19 +- src/Mod/Assembly/CommandCreateView.py | 270 ++++---- src/Mod/Assembly/Gui/ViewProviderAssembly.cpp | 255 +++----- src/Mod/Assembly/Gui/ViewProviderAssembly.h | 38 +- src/Mod/Assembly/JointObject.py | 614 ++++++++---------- src/Mod/Assembly/UtilsAssembly.py | 401 +++++++----- 9 files changed, 1217 insertions(+), 1057 deletions(-) diff --git a/src/Mod/Assembly/App/AssemblyObject.cpp b/src/Mod/Assembly/App/AssemblyObject.cpp index adb19272d3..b6bd2fe491 100644 --- a/src/Mod/Assembly/App/AssemblyObject.cpp +++ b/src/Mod/Assembly/App/AssemblyObject.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include #include @@ -434,18 +435,18 @@ App::DocumentObject* AssemblyObject::getJointOfPartConnectingToGround(App::Docum if (!joint) { continue; } - App::DocumentObject* part1 = getObjFromProp(joint, "Part1"); - App::DocumentObject* part2 = getObjFromProp(joint, "Part2"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); if (!part1 || !part2) { continue; } - if (part == part1 && isJointConnectingPartToGround(joint, "Part1")) { - name = "Part1"; + if (part == part1 && isJointConnectingPartToGround(joint, "Reference1")) { + name = "Reference1"; return joint; } - if (part == part2 && isJointConnectingPartToGround(joint, "Part2")) { - name = "Part2"; + if (part == part2 && isJointConnectingPartToGround(joint, "Reference2")) { + name = "Reference2"; return joint; } } @@ -507,8 +508,8 @@ std::vector AssemblyObject::getJoints(bool updateJCS, bool continue; } - auto* part1 = getObjFromProp(joint, "Part1"); - auto* part2 = getObjFromProp(joint, "Part2"); + 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) @@ -571,8 +572,8 @@ std::vector AssemblyObject::getJointsOfObj(App::DocumentOb std::vector jointsOf; for (auto joint : joints) { - App::DocumentObject* obj1 = getObjFromProp(joint, "object1"); - App::DocumentObject* obj2 = getObjFromProp(joint, "Object2"); + App::DocumentObject* obj1 = getObjFromRef(joint, "Reference1"); + App::DocumentObject* obj2 = getObjFromRef(joint, "Reference2"); if (obj == obj1 || obj == obj2) { jointsOf.push_back(obj); } @@ -587,8 +588,8 @@ std::vector AssemblyObject::getJointsOfPart(App::DocumentO std::vector jointsOf; for (auto joint : joints) { - App::DocumentObject* part1 = getObjFromProp(joint, "Part1"); - App::DocumentObject* part2 = getObjFromProp(joint, "Part2"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); if (part == part1 || part == part2) { jointsOf.push_back(joint); } @@ -674,11 +675,7 @@ bool AssemblyObject::isJointConnectingPartToGround(App::DocumentObject* joint, c return false; } - auto* propPart = dynamic_cast(joint->getPropertyByName(propname)); - if (!propPart) { - return false; - } - App::DocumentObject* part = propPart->getValue(); + App::DocumentObject* part = getMovingPartFromRef(joint, propname); // Check if the part is grounded. bool isGrounded = isPartGrounded(part); @@ -727,14 +724,26 @@ bool AssemblyObject::isJointTypeConnecting(App::DocumentObject* joint) && jointType != JointType::Gears && jointType != JointType::Belt; } + +bool AssemblyObject::isObjInSetOfObjRefPairs(App::DocumentObject* obj, + const std::set& set) +{ + for (const auto& pair : set) { + if (pair.first == obj) { + return true; + } + } + return false; +} + void AssemblyObject::removeUnconnectedJoints(std::vector& joints, std::vector groundedObjs) { - std::set connectedParts; + std::set connectedParts; // Initialize connectedParts with groundedObjs for (auto* groundedObj : groundedObjs) { - connectedParts.insert(groundedObj); + connectedParts.insert({groundedObj, nullptr}); } // Perform a traversal from each grounded object @@ -747,11 +756,11 @@ void AssemblyObject::removeUnconnectedJoints(std::vector& std::remove_if( joints.begin(), joints.end(), - [&connectedParts](App::DocumentObject* joint) { - App::DocumentObject* obj1 = getObjFromProp(joint, "Part1"); - App::DocumentObject* obj2 = getObjFromProp(joint, "Part2"); - if ((connectedParts.find(obj1) == connectedParts.end()) - || (connectedParts.find(obj2) == connectedParts.end())) { + [&](App::DocumentObject* joint) { + App::DocumentObject* obj1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* obj2 = getMovingPartFromRef(joint, "Reference2"); + if (!isObjInSetOfObjRefPairs(obj1, connectedParts) + || !isObjInSetOfObjRefPairs(obj2, connectedParts)) { Base::Console().Warning( "%s is unconnected to a grounded part so it is ignored.\n", joint->getFullName()); @@ -763,36 +772,47 @@ void AssemblyObject::removeUnconnectedJoints(std::vector& } void AssemblyObject::traverseAndMarkConnectedParts(App::DocumentObject* currentObj, - std::set& connectedParts, + std::set& connectedParts, const std::vector& joints) { // getConnectedParts returns the objs connected to the currentObj by any joint auto connectedObjs = getConnectedParts(currentObj, joints); - for (auto* nextObj : connectedObjs) { - if (connectedParts.find(nextObj) == connectedParts.end()) { - connectedParts.insert(nextObj); - traverseAndMarkConnectedParts(nextObj, connectedParts, joints); + for (auto nextObjRef : connectedObjs) { + if (!isObjInSetOfObjRefPairs(nextObjRef.first, connectedParts)) { + // Create a new ObjRefPair with the nextObj and a nullptr for PropertyXLinkSub* + connectedParts.insert(nextObjRef); + traverseAndMarkConnectedParts(nextObjRef.first, connectedParts, joints); } } } -std::vector +std::vector AssemblyObject::getConnectedParts(App::DocumentObject* part, const std::vector& joints) { - std::vector connectedParts; + std::vector connectedParts; for (auto joint : joints) { if (!isJointTypeConnecting(joint)) { continue; } - App::DocumentObject* obj1 = getObjFromProp(joint, "Part1"); - App::DocumentObject* obj2 = getObjFromProp(joint, "Part2"); + App::DocumentObject* obj1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* obj2 = getMovingPartFromRef(joint, "Reference2"); if (obj1 == part) { - connectedParts.push_back(obj2); + auto* ref = + dynamic_cast(joint->getPropertyByName("Reference2")); + if (!ref) { + continue; + } + connectedParts.push_back({obj2, ref}); } else if (obj2 == part) { - connectedParts.push_back(obj1); + auto* ref = + dynamic_cast(joint->getPropertyByName("Reference1")); + if (!ref) { + continue; + } + connectedParts.push_back({obj1, ref}); } } return connectedParts; @@ -816,11 +836,11 @@ bool AssemblyObject::isPartConnected(App::DocumentObject* obj) std::vector groundedObjs = getGroundedParts(); std::vector joints = getJoints(false); - std::set connectedParts; + std::set connectedParts; // Initialize connectedParts with groundedObjs for (auto* groundedObj : groundedObjs) { - connectedParts.insert(groundedObj); + connectedParts.insert({groundedObj, nullptr}); } // Perform a traversal from each grounded object @@ -828,8 +848,8 @@ bool AssemblyObject::isPartConnected(App::DocumentObject* obj) traverseAndMarkConnectedParts(groundedObj, connectedParts, joints); } - for (auto part : connectedParts) { - if (obj == part) { + for (auto objRef : connectedParts) { + if (obj == objRef.first) { return true; } } @@ -928,10 +948,10 @@ std::shared_ptr AssemblyObject::makeMbdJointDistance(App::DocumentObj { DistanceType type = getDistanceType(joint); - const char* elt1 = getElementFromProp(joint, "Object1"); - const char* elt2 = getElementFromProp(joint, "Object2"); - auto* obj1 = getLinkedObjFromProp(joint, "Object1"); - auto* obj2 = getLinkedObjFromProp(joint, "Object2"); + const char* elt1 = getElementFromProp(joint, "Reference1"); + const char* elt2 = getElementFromProp(joint, "Reference2"); + auto* obj1 = getLinkedObjFromRef(joint, "Reference1"); + auto* obj2 = getLinkedObjFromRef(joint, "Reference2"); if (type == DistanceType::PointPoint) { // Point to point distance, or ball joint if distance=0. @@ -1130,8 +1150,8 @@ AssemblyObject::makeMbdJoint(App::DocumentObject* joint) getRackPinionMarkers(joint, fullMarkerNameI, fullMarkerNameJ); } else { - fullMarkerNameI = handleOneSideOfJoint(joint, "Object1", "Part1", "Placement1"); - fullMarkerNameJ = handleOneSideOfJoint(joint, "Object2", "Part2", "Placement2"); + fullMarkerNameI = handleOneSideOfJoint(joint, "Reference1", "Placement1"); + fullMarkerNameJ = handleOneSideOfJoint(joint, "Reference2", "Placement2"); } if (fullMarkerNameI == "" || fullMarkerNameJ == "") { return {}; @@ -1246,16 +1266,15 @@ AssemblyObject::makeMbdJoint(App::DocumentObject* joint) } std::string AssemblyObject::handleOneSideOfJoint(App::DocumentObject* joint, - const char* propObjName, - const char* propPartName, + const char* propRefName, const char* propPlcName) { - App::DocumentObject* part = getObjFromProp(joint, propPartName); - App::DocumentObject* obj = getObjFromProp(joint, propObjName); + App::DocumentObject* part = getMovingPartFromRef(joint, propRefName); + App::DocumentObject* obj = getObjFromRef(joint, propRefName); if (!part || !obj) { - Base::Console().Warning("The property %s of Joint %s is empty.", - obj ? propPartName : propObjName, + Base::Console().Warning("The property %s of Joint %s is bad.", + propRefName, joint->getFullName()); return ""; } @@ -1269,10 +1288,15 @@ std::string AssemblyObject::handleOneSideOfJoint(App::DocumentObject* joint, // Make plc relative to the containing part // plc = objPlc * plc; // this would not work for nested parts. - Base::Placement obj_global_plc = getGlobalPlacement(obj, part); + auto* ref = dynamic_cast(joint->getPropertyByName(propRefName)); + if (!ref) { + return ""; + } + + Base::Placement obj_global_plc = getGlobalPlacement(obj, ref); plc = obj_global_plc * plc; - Base::Placement part_global_plc = getGlobalPlacement(part); + Base::Placement part_global_plc = getGlobalPlacement(part, ref); plc = part_global_plc.inverse() * plc; } @@ -1302,29 +1326,31 @@ void AssemblyObject::getRackPinionMarkers(App::DocumentObject* joint, swapJCS(joint); // make sure that rack is first. } - App::DocumentObject* part1 = getObjFromProp(joint, "Part1"); - App::DocumentObject* obj1 = getObjFromProp(joint, "Object1"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* obj1 = getObjFromRef(joint, "Reference1"); Base::Placement plc1 = getPlacementFromProp(joint, "Placement1"); - App::DocumentObject* part2 = getObjFromProp(joint, "Part2"); - App::DocumentObject* obj2 = getObjFromProp(joint, "Object2"); + App::DocumentObject* obj2 = getObjFromRef(joint, "Reference2"); Base::Placement plc2 = getPlacementFromProp(joint, "Placement2"); if (!part1 || !obj1) { - Base::Console().Warning("The property %s of Joint %s is empty.", - obj1 ? "Part1" : "Object1", - joint->getFullName()); + Base::Console().Warning("Reference1 of Joint %s is bad.", joint->getFullName()); return; } // For the pinion nothing special needed : - markerNameJ = handleOneSideOfJoint(joint, "Object2", "Part2", "Placement2"); + markerNameJ = handleOneSideOfJoint(joint, "Reference2", "Placement2"); // For the rack we need to change the placement : // make the pinion plc relative to the rack placement. - Base::Placement pinion_global_plc = getGlobalPlacement(obj2, part2); + auto* ref1 = dynamic_cast(joint->getPropertyByName("Reference1")); + auto* ref2 = dynamic_cast(joint->getPropertyByName("Reference2")); + if (!ref1 || !ref2) { + return; + } + Base::Placement pinion_global_plc = getGlobalPlacement(obj2, ref2); plc2 = pinion_global_plc * plc2; - Base::Placement rack_global_plc = getGlobalPlacement(obj1, part1); + Base::Placement rack_global_plc = getGlobalPlacement(obj1, ref1); plc2 = rack_global_plc.inverse() * plc2; // The rot of the rack placement should be the same as the pinion, but with X axis along the @@ -1356,7 +1382,7 @@ void AssemblyObject::getRackPinionMarkers(App::DocumentObject* joint, if (obj1->getNameInDocument() != part1->getNameInDocument()) { plc1 = rack_global_plc * plc1; - Base::Placement part_global_plc = getGlobalPlacement(part1); + Base::Placement part_global_plc = getGlobalPlacement(part1, ref1); plc1 = part_global_plc.inverse() * plc1; } @@ -1370,21 +1396,21 @@ void AssemblyObject::getRackPinionMarkers(App::DocumentObject* joint, int AssemblyObject::slidingPartIndex(App::DocumentObject* joint) { - App::DocumentObject* part1 = getObjFromProp(joint, "Part1"); - App::DocumentObject* obj1 = getObjFromProp(joint, "Object1"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* obj1 = getObjFromRef(joint, "Reference1"); boost::ignore_unused(obj1); Base::Placement plc1 = getPlacementFromProp(joint, "Placement1"); - App::DocumentObject* part2 = getObjFromProp(joint, "Part2"); - App::DocumentObject* obj2 = getObjFromProp(joint, "Object2"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); + App::DocumentObject* obj2 = getObjFromRef(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 = getObjFromProp(jt, "Part1"); - App::DocumentObject* jpart2 = getObjFromProp(jt, "Part2"); + App::DocumentObject* jpart1 = getMovingPartFromRef(jt, "Reference1"); + App::DocumentObject* jpart2 = getMovingPartFromRef(jt, "Reference2"); int found = 0; Base::Placement plcjt, plci; if (jpart1 == part1 || jpart1 == part2) { @@ -1489,8 +1515,8 @@ std::shared_ptr AssemblyObject::makeMbdMarker(std::string& name, Bas return mbdMarker; } -std::vector AssemblyObject::getDownstreamParts(App::DocumentObject* part, - App::DocumentObject* joint) +std::vector> +AssemblyObject::getDownstreamParts(App::DocumentObject* part, App::DocumentObject* joint) { // First we deactivate the joint bool state = getJointActivated(joint); @@ -1498,12 +1524,13 @@ std::vector AssemblyObject::getDownstreamParts(App::Docume std::vector joints = getJoints(false); - std::set connectedParts = {part}; + std::set> connectedParts = { + {part, nullptr}}; traverseAndMarkConnectedParts(part, connectedParts, joints); - std::vector downstreamParts; + std::vector> downstreamParts; for (auto parti : connectedParts) { - if (!isPartConnected(parti) && (parti != part)) { + if (!isPartConnected(parti.first) && (parti.first != part)) { downstreamParts.push_back(parti); } } @@ -1527,8 +1554,8 @@ std::vector AssemblyObject::getDownstreamParts(App::Docume auto it = std::remove(jointsOfPart.begin(), jointsOfPart.end(), connectingJoint); jointsOfPart.erase(it, jointsOfPart.end()); for (auto joint : jointsOfPart) { - App::DocumentObject* part1 = getObjFromProp(joint, "Part1"); - App::DocumentObject* part2 = getObjFromProp(joint, "Part2"); + App::DocumentObject* part1 = getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = getMovingPartFromRef(joint, "Reference2"); bool firstIsDown = part->getFullName() == part2->getFullName(); App::DocumentObject* downstreamPart = firstIsDown ? part1 : part2; @@ -1574,30 +1601,30 @@ std::vector AssemblyObject::getUpstreamParts(App::Document std::string name; App::DocumentObject* connectingJoint = getJointOfPartConnectingToGround(part, name); App::DocumentObject* upPart = - getObjFromProp(connectingJoint, name == "Part1" ? "Part2" : "Part1"); + getMovingPartFromRef(connectingJoint, name == "Reference1" ? "Reference2" : "Reference1"); std::vector upstreamParts = getUpstreamParts(upPart, limit); upstreamParts.push_back(part); return upstreamParts; } -App::DocumentObject* AssemblyObject::getUpstreamMovingPart(App::DocumentObject* part) +App::DocumentObject* AssemblyObject::getUpstreamMovingPart(App::DocumentObject* part, + App::DocumentObject*& joint, + std::string& name) { if (isPartGrounded(part)) { return nullptr; } - std::string name; - App::DocumentObject* connectingJoint = getJointOfPartConnectingToGround(part, name); - JointType jointType = getJointType(connectingJoint); + joint = getJointOfPartConnectingToGround(part, name); + JointType jointType = getJointType(joint); if (jointType != JointType::Fixed) { return part; } - App::DocumentObject* upPart = - getObjFromProp(connectingJoint, name == "Part1" ? "Part2" : "Part1"); + part = getMovingPartFromRef(joint, name == "Reference1" ? "Reference2" : "Reference1"); - return getUpstreamMovingPart(upPart); + return getUpstreamMovingPart(part, joint, name); } double AssemblyObject::getObjMass(App::DocumentObject* obj) @@ -1659,32 +1686,23 @@ void AssemblyObject::updateGroundedJointsPlacements() void AssemblyObject::swapJCS(App::DocumentObject* joint) { - auto propPlacement1 = - dynamic_cast(joint->getPropertyByName("Placement1")); - auto propPlacement2 = - dynamic_cast(joint->getPropertyByName("Placement2")); - if (propPlacement1 && propPlacement2) { - auto temp = propPlacement1->getValue(); - propPlacement1->setValue(propPlacement2->getValue()); - propPlacement2->setValue(temp); + auto pPlc1 = dynamic_cast(joint->getPropertyByName("Placement1")); + auto pPlc2 = dynamic_cast(joint->getPropertyByName("Placement2")); + if (pPlc1 && pPlc2) { + auto temp = pPlc1->getValue(); + pPlc1->setValue(pPlc2->getValue()); + pPlc2->setValue(temp); } - auto propObject1 = dynamic_cast(joint->getPropertyByName("Object1")); - auto propObject2 = dynamic_cast(joint->getPropertyByName("Object2")); - if (propObject1 && propObject2) { - auto temp = propObject1->getValue(); - auto subs1 = propObject1->getSubValues(); - auto subs2 = propObject2->getSubValues(); - propObject1->setValue(propObject2->getValue()); - propObject1->setSubValues(std::move(subs2)); - propObject2->setValue(temp); - propObject2->setSubValues(std::move(subs1)); - } - auto propPart1 = dynamic_cast(joint->getPropertyByName("Part1")); - auto propPart2 = dynamic_cast(joint->getPropertyByName("Part2")); - if (propPart1 && propPart2) { - auto temp = propPart1->getValue(); - propPart1->setValue(propPart2->getValue()); - propPart2->setValue(temp); + auto pRef1 = dynamic_cast(joint->getPropertyByName("Reference1")); + auto pRef2 = dynamic_cast(joint->getPropertyByName("Reference2")); + if (pRef1 && pRef2) { + auto temp = pRef1->getValue(); + auto subs1 = pRef1->getSubValues(); + auto subs2 = pRef2->getSubValues(); + pRef1->setValue(pRef2->getValue()); + pRef1->setSubValues(std::move(subs2)); + pRef2->setValue(temp); + pRef2->setSubValues(std::move(subs1)); } } @@ -1761,12 +1779,12 @@ double AssemblyObject::getEdgeRadius(App::DocumentObject* obj, const char* elt) DistanceType AssemblyObject::getDistanceType(App::DocumentObject* joint) { - std::string type1 = getElementTypeFromProp(joint, "Object1"); - std::string type2 = getElementTypeFromProp(joint, "Object2"); - std::string elt1 = getElementFromProp(joint, "Object1"); - std::string elt2 = getElementFromProp(joint, "Object2"); - auto* obj1 = getLinkedObjFromProp(joint, "Object1"); - auto* obj2 = getLinkedObjFromProp(joint, "Object2"); + std::string type1 = getElementTypeFromProp(joint, "Reference1"); + std::string type2 = getElementTypeFromProp(joint, "Reference2"); + std::string elt1 = getElementFromProp(joint, "Reference1"); + std::string elt2 = getElementFromProp(joint, "Reference2"); + auto* obj1 = getLinkedObjFromRef(joint, "Reference1"); + auto* obj2 = getLinkedObjFromRef(joint, "Reference2"); if (type1 == "Vertex" && type2 == "Vertex") { return DistanceType::PointPoint; @@ -2002,115 +2020,53 @@ Base::Placement AssemblyObject::getPlacementFromProp(App::DocumentObject* obj, c return plc; } -bool AssemblyObject::getTargetPlacementRelativeTo(Base::Placement& foundPlc, - App::DocumentObject* targetObj, - App::DocumentObject* part, - App::DocumentObject* container, - bool inContainerBranch, - bool ignorePlacement) -{ - inContainerBranch = inContainerBranch || (!ignorePlacement && part == container); - - if (targetObj == part && inContainerBranch && !ignorePlacement) { - foundPlc = getPlacementFromProp(targetObj, "Placement"); - return true; - } - - if (part->isDerivedFrom(App::DocumentObjectGroup::getClassTypeId())) { - for (auto& obj : part->getOutList()) { - bool found = getTargetPlacementRelativeTo(foundPlc, - targetObj, - obj, - container, - inContainerBranch, - ignorePlacement); - if (found) { - return true; - } - } - } - else if (part->isDerivedFrom(Assembly::AssemblyObject::getClassTypeId()) - || part->isDerivedFrom(App::Part::getClassTypeId()) - || part->isDerivedFrom(PartDesign::Body::getClassTypeId())) { - for (auto& obj : part->getOutList()) { - bool found = getTargetPlacementRelativeTo(foundPlc, - targetObj, - obj, - container, - inContainerBranch); - if (!found) { - continue; - } - - if (!ignorePlacement) { - foundPlc = getPlacementFromProp(part, "Placement") * foundPlc; - } - - return true; - } - } - else if (auto link = dynamic_cast(part)) { - auto linked_obj = link->getLinkedObject(); - - if (dynamic_cast(linked_obj) || dynamic_cast(linked_obj)) { - for (auto& obj : linked_obj->getOutList()) { - bool found = getTargetPlacementRelativeTo(foundPlc, - targetObj, - obj, - container, - inContainerBranch); - if (!found) { - continue; - } - - foundPlc = getPlacementFromProp(link, "Placement") * foundPlc; - return true; - } - } - - bool found = getTargetPlacementRelativeTo(foundPlc, - targetObj, - linked_obj, - container, - inContainerBranch, - true); - - if (found) { - if (!ignorePlacement) { - foundPlc = getPlacementFromProp(link, "Placement") * foundPlc; - } - - return true; - } - } - - return false; -} Base::Placement AssemblyObject::getGlobalPlacement(App::DocumentObject* targetObj, - App::DocumentObject* container) + App::DocumentObject* rootObj, + const std::string& sub) { - bool inContainerBranch = (container == nullptr); - auto rootObjects = App::GetApplication().getActiveDocument()->getRootObjectsIgnoreLinks(); - for (auto& part : rootObjects) { - Base::Placement foundPlc; - bool found = - getTargetPlacementRelativeTo(foundPlc, targetObj, part, container, inContainerBranch); - if (found) { - return foundPlc; + if (!targetObj || !rootObj || sub == "") { + return Base::Placement(); + } + std::vector names = splitSubName(sub); + + App::Document* doc = rootObj->getDocument(); + Base::Placement plc = getPlacementFromProp(rootObj, "Placement"); + + for (auto& name : names) { + App::DocumentObject* obj = doc->getObject(name.c_str()); + if (!obj) { + return Base::Placement(); + } + + plc = plc * getPlacementFromProp(obj, "Placement"); + + if (obj == targetObj) { + return plc; + } + if (obj->isDerivedFrom()) { + // Update doc in case its an external link. + doc = obj->getLinkedObject()->getDocument(); } } + // If targetObj has not been found there's a problem return Base::Placement(); } -Base::Placement AssemblyObject::getGlobalPlacement(App::DocumentObject* joint, - const char* targetObj, - const char* container) +Base::Placement AssemblyObject::getGlobalPlacement(App::DocumentObject* targetObj, + App::PropertyXLinkSub* prop) { - App::DocumentObject* obj = getObjFromProp(joint, targetObj); - App::DocumentObject* part = getObjFromProp(joint, container); - return getGlobalPlacement(obj, part); + if (!targetObj || !prop) { + return Base::Placement(); + } + + std::vector subs = prop->getSubValues(); + if (subs.empty()) { + return Base::Placement(); + } + + return getGlobalPlacement(targetObj, prop->getValue(), subs[0]); } double AssemblyObject::getJointDistance(App::DocumentObject* joint) @@ -2149,19 +2105,57 @@ JointType AssemblyObject::getJointType(App::DocumentObject* joint) return jointType; } -const char* AssemblyObject::getElementFromProp(App::DocumentObject* obj, const char* propName) +std::vector AssemblyObject::getSubAsList(App::PropertyXLinkSub* prop) { - auto* prop = dynamic_cast(obj->getPropertyByName(propName)); if (!prop) { - return ""; + return {}; } - auto subs = prop->getSubValues(); + std::vector subs = prop->getSubValues(); if (subs.empty()) { + return {}; + } + + return splitSubName(subs[0]); +} + +std::vector AssemblyObject::getSubAsList(App::DocumentObject* obj, const char* pName) +{ + auto* prop = dynamic_cast(obj->getPropertyByName(pName)); + + return getSubAsList(prop); +} + +std::vector AssemblyObject::splitSubName(const std::string& sub) +{ + // Turns 'Part.Part001.Body.Pad.Edge1' + // Into ['Part', 'Part001','Body','Pad','Edge1'] + std::vector subNames; + std::string subName; + std::istringstream subNameStream(sub); + while (std::getline(subNameStream, subName, '.')) { + subNames.push_back(subName); + } + + // Check if the last character of the input string is the delimiter. + // If so, add an empty string to the subNames vector. + // Because the last subname is the element name and can be empty. + if (!sub.empty() && sub.back() == '.') { + subNames.push_back(""); // Append empty string for trailing dot. + } + + return subNames; +} + +const char* AssemblyObject::getElementFromProp(App::DocumentObject* obj, const char* pName) +{ + std::vector names = getSubAsList(obj, pName); + + if (names.empty()) { return ""; } - return subs[0].c_str(); + return names.back().c_str(); } std::string AssemblyObject::getElementTypeFromProp(App::DocumentObject* obj, const char* propName) @@ -2185,10 +2179,157 @@ App::DocumentObject* AssemblyObject::getObjFromProp(App::DocumentObject* joint, return propObj->getValue(); } -App::DocumentObject* AssemblyObject::getLinkedObjFromProp(App::DocumentObject* joint, - const char* pObj) +App::DocumentObject* AssemblyObject::getObjFromRef(App::PropertyXLinkSub* prop) { - auto* obj = getObjFromProp(joint, pObj); + if (!prop) { + return nullptr; + } + + App::Document* doc = prop->getValue()->getDocument(); + std::vector names = getSubAsList(prop); + + // Lambda function to check if the typeId is a BodySubObject + auto isBodySubObject = [](App::DocumentObject* obj) -> bool { + // PartDesign::Point + Line + Plane + CoordinateSystem + // getViewProviderName instead of isDerivedFrom to avoid dependency on sketcher + return (strcmp(obj->getViewProviderName(), "SketcherGui::ViewProviderSketch") == 0 + || obj->isDerivedFrom()); + }; + + // Helper function to handle PartDesign::Body objects + auto handlePartDesignBody = [&](App::DocumentObject* obj, + std::vector::iterator it) -> App::DocumentObject* { + auto nextIt = std::next(it); + if (nextIt != names.end()) { + for (auto* obji : obj->getOutList()) { + if (*nextIt == obji->getNameInDocument()) { + if (isBodySubObject(obji)) { + return obji; + } + } + } + } + return obj; + }; + + + for (auto it = names.begin(); it != names.end(); ++it) { + App::DocumentObject* obj = doc->getObject(it->c_str()); + if (!obj) { + return nullptr; + } + + // The last but one name should be the selected + if (std::next(it) == std::prev(names.end())) { + return obj; + } + + if (obj->isDerivedFrom()) { + continue; + } + else if (obj->isDerivedFrom()) { + return handlePartDesignBody(obj, it); + } + else if (obj->isDerivedFrom()) { + // Primitive, fastener, gear, etc. + return obj; + } + else if (obj->isDerivedFrom()) { + App::DocumentObject* linked_obj = obj->getLinkedObject(); + if (linked_obj->isDerivedFrom()) { + auto* retObj = handlePartDesignBody(linked_obj, it); + return retObj == linked_obj ? obj : retObj; + } + else if (linked_obj->isDerivedFrom()) { + return obj; + } + else { + doc = linked_obj->getDocument(); + continue; + } + } + } + + return nullptr; +} + +App::DocumentObject* AssemblyObject::getObjFromRef(App::DocumentObject* joint, const char* pName) +{ + auto* prop = dynamic_cast(joint->getPropertyByName(pName)); + + return getObjFromRef(prop); +} + +App::DocumentObject* AssemblyObject::getMovingPartFromRef(App::DocumentObject* obj, + std::string& sub) +{ + if (!obj) { + return nullptr; + } + + App::Document* doc = obj->getDocument(); + + std::vector names = splitSubName(sub); + names.insert(names.begin(), obj->getNameInDocument()); + + bool assemblyPassed = false; + + for (const auto& objName : names) { + obj = doc->getObject(objName.c_str()); + if (!obj) { + continue; + } + + if (obj->isDerivedFrom()) { // update the document if necessary for next object + doc = obj->getLinkedObject()->getDocument(); + } + + if (obj == this) { + // We make sure we pass the assembly for cases like part.assembly.part.body + assemblyPassed = true; + continue; + } + if (!assemblyPassed) { + continue; + } + + return obj; + } + + return nullptr; +} + +App::DocumentObject* AssemblyObject::getMovingPartFromRef(App::PropertyXLinkSub* prop) +{ + if (!prop) { + return nullptr; + } + + App::DocumentObject* obj = prop->getValue(); + if (!obj) { + return nullptr; + } + + std::vector subs = prop->getSubValues(); + if (subs.empty()) { + return nullptr; + } + + return getMovingPartFromRef(obj, subs[0]); +} + +App::DocumentObject* AssemblyObject::getMovingPartFromRef(App::DocumentObject* joint, + const char* pName) +{ + auto* prop = dynamic_cast(joint->getPropertyByName(pName)); + + return getMovingPartFromRef(prop); +} + +App::DocumentObject* AssemblyObject::getLinkedObjFromRef(App::DocumentObject* joint, + const char* pObj) +{ + auto* obj = getObjFromRef(joint, pObj); if (obj) { return obj->getLinkedObject(true); } diff --git a/src/Mod/Assembly/App/AssemblyObject.h b/src/Mod/Assembly/App/AssemblyObject.h index fd6f376877..65198b2b09 100644 --- a/src/Mod/Assembly/App/AssemblyObject.h +++ b/src/Mod/Assembly/App/AssemblyObject.h @@ -44,6 +44,11 @@ class ASMTMarker; class ASMTPart; } // namespace MbD +namespace App +{ +class PropertyXLinkSub; +} // namespace App + namespace Base { class Placement; @@ -57,6 +62,8 @@ namespace Assembly class JointGroup; class ViewGroup; +using ObjRefPair = std::pair; + // This enum has to be the same as the one in JointObject.py enum class JointType { @@ -123,7 +130,6 @@ enum class DistanceType Other, }; - class AssemblyExport AssemblyObject: public App::Part { PROPERTY_HEADER_WITH_OVERRIDE(Assembly::AssemblyObject); @@ -173,8 +179,7 @@ public: JointType jointType); std::shared_ptr makeMbdJointDistance(App::DocumentObject* joint); std::string handleOneSideOfJoint(App::DocumentObject* joint, - const char* propObjLinkName, - const char* propPartName, + const char* propRefName, const char* propPlcName); void getRackPinionMarkers(App::DocumentObject* joint, std::string& markerNameI, @@ -197,20 +202,23 @@ public: bool isJointConnectingPartToGround(App::DocumentObject* joint, const char* partPropName); bool isJointTypeConnecting(App::DocumentObject* joint); + bool isObjInSetOfObjRefPairs(App::DocumentObject* obj, const std::set& pairs); void removeUnconnectedJoints(std::vector& joints, std::vector groundedObjs); void traverseAndMarkConnectedParts(App::DocumentObject* currentPart, - std::set& connectedParts, + std::set& connectedParts, const std::vector& joints); - std::vector - getConnectedParts(App::DocumentObject* part, const std::vector& joints); + std::vector getConnectedParts(App::DocumentObject* part, + const std::vector& joints); bool isPartGrounded(App::DocumentObject* part); bool isPartConnected(App::DocumentObject* part); - std::vector getDownstreamParts(App::DocumentObject* part, - App::DocumentObject* joint); + std::vector getDownstreamParts(App::DocumentObject* part, + App::DocumentObject* joint); std::vector getUpstreamParts(App::DocumentObject* part, int limit = 0); - App::DocumentObject* getUpstreamMovingPart(App::DocumentObject* part); + App::DocumentObject* getUpstreamMovingPart(App::DocumentObject* part, + App::DocumentObject*& joint, + std::string& name); double getObjMass(App::DocumentObject* obj); void setObjMasses(std::vector> objectMasses); @@ -253,19 +261,23 @@ public: static const char* getElementFromProp(App::DocumentObject* obj, const char* propName); static std::string getElementTypeFromProp(App::DocumentObject* obj, const char* propName); static App::DocumentObject* getObjFromProp(App::DocumentObject* joint, const char* propName); - static App::DocumentObject* getLinkedObjFromProp(App::DocumentObject* joint, const char* pObj); + static App::DocumentObject* getObjFromRef(App::PropertyXLinkSub* prop); + static App::DocumentObject* getObjFromRef(App::DocumentObject* joint, const char* propName); + App::DocumentObject* getMovingPartFromRef(App::DocumentObject* obj, std::string& sub); + App::DocumentObject* getMovingPartFromRef(App::PropertyXLinkSub* prop); + App::DocumentObject* getMovingPartFromRef(App::DocumentObject* joint, const char* propName); + static App::DocumentObject* getLinkedObjFromRef(App::DocumentObject* joint, + const char* propName); + static std::vector getSubAsList(App::PropertyXLinkSub* prop); + static std::vector getSubAsList(App::DocumentObject* joint, const char* propName); + static std::vector splitSubName(const std::string& subName); static Base::Placement getPlacementFromProp(App::DocumentObject* obj, const char* propName); - static bool getTargetPlacementRelativeTo(Base::Placement& foundPlc, - App::DocumentObject* targetObj, - App::DocumentObject* part, - App::DocumentObject* container, - bool inContainerBranch, - bool ignorePlacement = false); + static Base::Placement getGlobalPlacement(App::DocumentObject* targetObj, - App::DocumentObject* container = nullptr); - static Base::Placement getGlobalPlacement(App::DocumentObject* joint, - const char* targetObj, - const char* container = ""); + App::DocumentObject* rootObj, + const std::string& sub); + static Base::Placement getGlobalPlacement(App::DocumentObject* targetObj, + App::PropertyXLinkSub* prop); }; // using AssemblyObjectPython = App::FeaturePythonT; diff --git a/src/Mod/Assembly/AssemblyTests/TestCore.py b/src/Mod/Assembly/AssemblyTests/TestCore.py index 1682606380..1aae10038a 100644 --- a/src/Mod/Assembly/AssemblyTests/TestCore.py +++ b/src/Mod/Assembly/AssemblyTests/TestCore.py @@ -149,29 +149,34 @@ class TestCore(unittest.TestCase): box.Placement = App.Placement(App.Vector(10, 20, 30), App.Rotation(15, 25, 35)) # Step 0 : box with placement. No element selected - plc = joint.Proxy.findPlacement(joint, box, box, "", "") + ref = [self.assembly, [box.Name + ".", box.Name + "."]] + plc = joint.Proxy.findPlacement(joint, ref) targetPlc = App.Placement(App.Vector(), App.Rotation()) self.assertTrue(plc.isSame(targetPlc, 1e-6), "'{}' failed - Step 0".format(operation)) # Step 1 : box with placement. Face + Vertex - plc = joint.Proxy.findPlacement(joint, box, box, "Face6", "Vertex7") + ref = [self.assembly, [box.Name + ".Face6", box.Name + ".Vertex7"]] + plc = joint.Proxy.findPlacement(joint, ref) targetPlc = App.Placement(App.Vector(L, W, H), App.Rotation()) self.assertTrue(plc.isSame(targetPlc, 1e-6), "'{}' failed - Step 1".format(operation)) # Step 2 : box with placement. Edge + Vertex - plc = joint.Proxy.findPlacement(joint, box, box, "Edge8", "Vertex8") + ref = [self.assembly, [box.Name + ".Edge8", box.Name + ".Vertex8"]] + plc = joint.Proxy.findPlacement(joint, ref) targetPlc = App.Placement(App.Vector(L, W, 0), App.Rotation(0, -90, 270)) self.assertTrue(plc.isSame(targetPlc, 1e-6), "'{}' failed - Step 2".format(operation)) # Step 3 : box with placement. Vertex - plc = joint.Proxy.findPlacement(joint, box, box, "Vertex3", "Vertex3") + ref = [self.assembly, [box.Name + ".Vertex3", box.Name + ".Vertex3"]] + plc = joint.Proxy.findPlacement(joint, ref) targetPlc = App.Placement(App.Vector(0, W, H), App.Rotation()) _msg(" plc '{}'".format(plc)) _msg(" targetPlc '{}'".format(targetPlc)) self.assertTrue(plc.isSame(targetPlc, 1e-6), "'{}' failed - Step 3".format(operation)) # Step 4 : box with placement. Face - plc = joint.Proxy.findPlacement(joint, box, box, "Face2", "Face2") + ref = [self.assembly, [box.Name + ".Face2", box.Name + ".Face2"]] + plc = joint.Proxy.findPlacement(joint, ref) targetPlc = App.Placement(App.Vector(L, W / 2, H / 2), App.Rotation(0, -90, 180)) _msg(" plc '{}'".format(plc)) _msg(" targetPlc '{}'".format(targetPlc)) @@ -200,24 +205,11 @@ class TestCore(unittest.TestCase): joint = self.jointgroup.newObject("App::FeaturePython", "testJoint") JointObject.Joint(joint, 0) - current_selection = [] - current_selection.append( - { - "object": box2, - "part": box2, - "element_name": "Face6", - "vertex_name": "Vertex7", - } - ) - current_selection.append( - { - "object": box, - "part": box, - "element_name": "Face6", - "vertex_name": "Vertex7", - } - ) + refs = [ + [self.assembly, [box2.Name + ".Face6", box2.Name + ".Vertex7"]], + [self.assembly, [box.Name + ".Face6", box.Name + ".Vertex7"]], + ] - joint.Proxy.setJointConnectors(joint, current_selection) + joint.Proxy.setJointConnectors(joint, refs) self.assertTrue(box.Placement.isSame(box2.Placement, 1e-6), "'{}'".format(operation)) diff --git a/src/Mod/Assembly/CommandCreateJoint.py b/src/Mod/Assembly/CommandCreateJoint.py index 410d8bb224..8ce6505897 100644 --- a/src/Mod/Assembly/CommandCreateJoint.py +++ b/src/Mod/Assembly/CommandCreateJoint.py @@ -529,22 +529,17 @@ class CommandToggleGrounded: # If you select 2 solids (bodies for example) within an assembly. # There'll be a single sel but 2 SubElementNames. for sub in sel.SubElementNames: - # Only objects within the assembly. - objs_names, element_name = UtilsAssembly.getObjsNamesAndElement(sel.ObjectName, sub) - if assembly.Name not in objs_names: - continue + ref = [sel.Object, [sub, sub]] + moving_part = UtilsAssembly.getMovingPart(assembly, ref) - full_element_name = UtilsAssembly.getFullElementName(sel.ObjectName, sub) - obj = UtilsAssembly.getObject(full_element_name) - part_containing_obj = UtilsAssembly.getContainingPart(full_element_name, obj) + # Only objects within the assembly. + if moving_part is None: + continue # Check if part is grounded and if so delete the joint. ungrounded = False for joint in joint_group.Group: - if ( - hasattr(joint, "ObjectToGround") - and joint.ObjectToGround == part_containing_obj - ): + if hasattr(joint, "ObjectToGround") and joint.ObjectToGround == moving_part: doc = App.ActiveDocument doc.removeObject(joint.Name) doc.recompute() @@ -554,7 +549,7 @@ class CommandToggleGrounded: continue # Create groundedJoint. - createGroundedJoint(part_containing_obj) + createGroundedJoint(moving_part) App.closeActiveTransaction() diff --git a/src/Mod/Assembly/CommandCreateView.py b/src/Mod/Assembly/CommandCreateView.py index 4a43f48844..b83841bc13 100644 --- a/src/Mod/Assembly/CommandCreateView.py +++ b/src/Mod/Assembly/CommandCreateView.py @@ -227,47 +227,80 @@ class ExplodedViewStep: def __init__(self, evStep, type_index=0): evStep.Proxy = self - # we cannot use "App::PropertyLinkList" for objs because they can be external - evStep.addProperty( - "App::PropertyStringList", - "ObjNames", - "Exploded Move", - QT_TRANSLATE_NOOP("App::Property", "The object moved by the move"), - ) + self.createProperties(evStep) - evStep.addProperty( - "App::PropertyLinkList", - "Parts", - "Exploded Move", - QT_TRANSLATE_NOOP("App::Property", "The containing parts of objects moved by the move"), - ) - - evStep.addProperty( - "App::PropertyPlacement", - "MovementTransform", - "Exploded Move", - QT_TRANSLATE_NOOP( - "App::Property", - "This is the movement of the move. The end placement is the result of the start placement * this placement.", - ), - ) - - evStep.addProperty( - "App::PropertyEnumeration", - "MoveType", - "Exploded Move", - QT_TRANSLATE_NOOP("App::Property", "The type of the move"), - ) evStep.MoveType = ExplodedViewStepTypes # sets the list evStep.MoveType = ExplodedViewStepTypes[type_index] # set the initial value + def onDocumentRestored(self, evStep): + self.createProperties(evStep) + + def createProperties(self, evStep): + self.migrationScript(evStep) + + if not hasattr(evStep, "References"): + evStep.addProperty( + "App::PropertyXLinkSubHidden", + "References", + "Exploded Move", + QT_TRANSLATE_NOOP("App::Property", "The objects moved by the move"), + ) + + if not hasattr(evStep, "MovementTransform"): + evStep.addProperty( + "App::PropertyPlacement", + "MovementTransform", + "Exploded Move", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the movement of the move. The end placement is the result of the start placement * this placement.", + ), + ) + + if not hasattr(evStep, "MoveType"): + evStep.addProperty( + "App::PropertyEnumeration", + "MoveType", + "Exploded Move", + QT_TRANSLATE_NOOP("App::Property", "The type of the move"), + ) + + def migrationScript(self, evStep): + if hasattr(evStep, "Parts"): + objNames = evStep.ObjNames + parts = evStep.Parts + + evStep.removeProperty("ObjNames") + evStep.removeProperty("Parts") + + evStep.addProperty( + "App::PropertyXLinkSubHidden", + "References", + "Exploded Move", + QT_TRANSLATE_NOOP("App::Property", "The objects moved by the move"), + ) + + rootObj = None + paths = [] + + for objName, part in zip(objNames, parts): + # now we need to get the 'selection-root-obj' and the global path + obj = UtilsAssembly.getObjectInPart(objName, part) + rootObj, path = UtilsAssembly.getRootPath(obj, part) + if rootObj is None: + continue + paths.append(path) + # Note: all the parts should have the same rootObj. + + evStep.References = [rootObj, paths] + def dumps(self): return None def loads(self, state): return None - def onChanged(self, joint, prop): + def onChanged(self, evStep, prop): """Do something when a property has changed""" pass @@ -277,20 +310,23 @@ class ExplodedViewStep: pass def applyStep(self, move, com=App.Vector(), size=100): + if not UtilsAssembly.isRefValid(move.References, 1): + return + positions = [] if move.MoveType == "Radial": distance = move.MovementTransform.Base.Length factor = 4 * distance / size - for objName, part in zip(move.ObjNames, move.Parts): - if not objName: - continue - obj = UtilsAssembly.getObjectInPart(objName, part) + subs = move.References[1] + for sub in subs: + ref = [move.References[0], [sub]] + obj = UtilsAssembly.getObject(ref) if not obj: continue if move.ViewObject: - startPos = UtilsAssembly.getCenterOfBoundingBox([obj], [part]) + startPos = UtilsAssembly.getCenterOfBoundingBox([obj], [ref]) if move.MoveType == "Radial": objCom, objSize = UtilsAssembly.getComAndSize(obj) @@ -300,7 +336,7 @@ class ExplodedViewStep: obj.Placement = move.MovementTransform * obj.Placement if move.ViewObject: - endPos = UtilsAssembly.getCenterOfBoundingBox([obj], [part]) + endPos = UtilsAssembly.getCenterOfBoundingBox([obj], [ref]) positions.append([startPos, endPos]) if move.ViewObject: @@ -308,14 +344,6 @@ class ExplodedViewStep: return positions - def getMovingobjects(self, move): - movingObjs = [] - for objName, part in zip(move.ObjNames, move.Parts): - obj = UtilsAssembly.getObjectInPart(objName, part) - if obj is not None: - movingObjs.append(obj) - return movingObjs - class ViewProviderExplodedViewStep: def __init__(self, vobj): @@ -485,7 +513,7 @@ class TaskAssemblyCreateView(QtCore.QObject): self.selectingFeature = False self.form.LabelAlignDragger.setVisible(False) - self.preselection_dict = None + self.presel_ref = None self.blockSetDragger = False self.blockDraggerMove = True @@ -532,8 +560,8 @@ class TaskAssemblyCreateView(QtCore.QObject): return self.dismissCurrentStep() + self.selectedRefs = [] self.selectedObjs = [] - self.selectedParts = [] # containing parts self.selectedObjsInitPlc = [] selection = Gui.Selection.getSelectionEx("*", 0) if not selection: @@ -549,39 +577,33 @@ class TaskAssemblyCreateView(QtCore.QObject): continue for sub_name in sel.SubElementNames: - # Only objects within the assembly. - objs_names, element_name = UtilsAssembly.getObjsNamesAndElement( - sel.ObjectName, sub_name - ) - if self.assembly.Name not in objs_names: + ref = [sel.Object, [sub_name]] + 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. + if obj is None or moving_part is None or obj == self.assembly or element_name != "": Gui.Selection.removeSelection(sel.Object, sub_name) continue - obj_name = sel.ObjectName - full_obj_name = UtilsAssembly.getFullObjName(obj_name, sub_name) - full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) - selected_object = UtilsAssembly.getObject(full_element_name) - if selected_object is None: - continue - element_name = UtilsAssembly.getElementName(full_element_name) - part = UtilsAssembly.getContainingPart( - full_element_name, selected_object, self.assembly - ) + partAsSolid = self.form.CheckBox_PartsAsSingleSolid.isChecked() + if partAsSolid: + obj = moving_part - if selected_object == self.assembly or element_name != "": - # do not accept selection of assembly itself or elements - Gui.Selection.removeSelection(sel.Object, sub_name) - continue + # truncate the sub name at obj.Name + if partAsSolid: + # We handle both cases separately because with external files there + # can be several times the same name. For containing part we are sure it's + # the first instance, for the object we are sure it's the last. + ref[1][0] = UtilsAssembly.truncateSubAtLast(ref[1][0], obj.Name) + else: + ref[1][0] = UtilsAssembly.truncateSubAtFirst(ref[1][0], obj.Name) - if self.form.CheckBox_PartsAsSingleSolid.isChecked(): - selected_object = part - - if not selected_object in self.selectedObjs and hasattr( - selected_object, "Placement" - ): - self.selectedObjs.append(selected_object) - self.selectedParts.append(part) - self.selectedObjsInitPlc.append(App.Placement(selected_object.Placement)) + if not obj in self.selectedObjs and hasattr(obj, "Placement"): + self.selectedRefs.append(ref) + self.selectedObjs.append(obj) + self.selectedObjsInitPlc.append(App.Placement(obj.Placement)) if len(self.selectedObjs) != 0: self.enableDragger(True) @@ -658,11 +680,11 @@ class TaskAssemblyCreateView(QtCore.QObject): if self.alignMode == "Custom": self.initialDraggerPlc = App.Placement(self.assembly.ViewObject.DraggerPlacement) else: - plc = UtilsAssembly.getGlobalPlacement(self.selectedObjs[0], self.selectedParts[0]) + plc = UtilsAssembly.getGlobalPlacement(self.selectedRefs[0], self.selectedObjs[0]) self.initialDraggerPlc = App.Placement(plc) if self.alignMode == "Center": self.initialDraggerPlc.Base = UtilsAssembly.getCenterOfBoundingBox( - self.selectedObjs, self.selectedParts + self.selectedObjs, self.selectedRefs ) def setDraggerObjectPlc(self): @@ -684,19 +706,20 @@ class TaskAssemblyCreateView(QtCore.QObject): ExplodedViewStep(self.currentStep, moveType_index) ViewProviderExplodedViewStep(self.currentStep.ViewObject) + self.currentStep.MovementTransform = App.Placement() + + # Note: the rootObj of all our refs must be the same since all the + # objects are within assembly. So we put all the sub in a single ref. + listOfSubs = [] + for ref in self.selectedRefs: + listOfSubs.append(ref[1][0]) + self.currentStep.References = [self.selectedRefs[0][0], listOfSubs] + # Note: self.viewObj.Moves.append(self.currentStep) does not work listOfMoves = self.viewObj.Moves listOfMoves.append(self.currentStep) self.viewObj.Moves = listOfMoves - objNames = [] - for obj in self.selectedObjs: - objNames.append(obj.Name) - - self.currentStep.MovementTransform = App.Placement() - self.currentStep.ObjNames = objNames - self.currentStep.Parts = self.selectedParts - def dismissCurrentStep(self): if self.currentStep is None: return @@ -728,13 +751,13 @@ class TaskAssemblyCreateView(QtCore.QObject): self.currentStep.Proxy.applyStep(self.currentStep, self.com, self.size) def draggerFinished(self, event): - if self.currentStep.MoveType == "Radial": - self.currentStep = None + isRadial = self.currentStep.MoveType == "Radial" + self.currentStep = None + + if isRadial: Gui.Selection.clearSelection() return - self.currentStep = None - # Reset the initial placements self.findDraggerInitialPlc() @@ -748,29 +771,23 @@ class TaskAssemblyCreateView(QtCore.QObject): view = Gui.activeDocument().activeView() cursor_info = view.getObjectInfo(view.getCursorPos()) - if not cursor_info or not self.preselection_dict: + if not cursor_info or not self.presel_ref: self.assembly.ViewObject.DraggerVisibility = False return - newPos = App.Vector(cursor_info["x"], cursor_info["y"], cursor_info["z"]) - self.preselection_dict["mouse_pos"] = newPos + ref = self.presel_ref + element_name = UtilsAssembly.getElementName(ref[1][0]) - if self.preselection_dict["element_name"] == "": - self.preselection_dict["vertex_name"] = "" + if element_name == "": + vertex_name = "" else: - self.preselection_dict["vertex_name"] = UtilsAssembly.findElementClosestVertex( - self.preselection_dict - ) + newPos = App.Vector(cursor_info["x"], cursor_info["y"], cursor_info["z"]) + vertex_name = UtilsAssembly.findElementClosestVertex(self.assembly, ref, newPos) - obj = self.preselection_dict["object"] - part = self.preselection_dict["part"] - plc = UtilsAssembly.findPlacement( - obj, - part, - self.preselection_dict["element_name"], - self.preselection_dict["vertex_name"], - ) - global_plc = UtilsAssembly.getGlobalPlacement(obj, part) + ref = UtilsAssembly.addVertexToReference(ref, vertex_name) + + plc = UtilsAssembly.findPlacement(ref) + global_plc = UtilsAssembly.getGlobalPlacement(ref) plc = global_plc * plc self.blockDraggerMove = True @@ -827,22 +844,23 @@ class TaskAssemblyCreateView(QtCore.QObject): return else: - full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) - selected_object = UtilsAssembly.getObject(full_element_name) - if selected_object is None: + ref = [App.getDocument(doc_name).getObject(obj_name), [sub_name]] + obj = UtilsAssembly.getObject(ref) + moving_part = UtilsAssembly.getMovingPart(self.assembly, ref) + + if obj is None or moving_part is None: return - element_name = UtilsAssembly.getElementName(full_element_name) - part = UtilsAssembly.getContainingPart( - full_element_name, selected_object, self.assembly - ) + if self.form.CheckBox_PartsAsSingleSolid.isChecked(): + part = moving_part + else: + part = obj - if not self.form.CheckBox_PartsAsSingleSolid.isChecked(): - part = selected_object + element_name = UtilsAssembly.getElementName(sub_name) if element_name != "": # When selecting, we do not want to select an element, but only the containing part. - Gui.Selection.removeSelection(selected_object, element_name) + Gui.Selection.removeSelection(doc_name, obj_name, sub_name) if Gui.Selection.isSelected(part, ""): Gui.Selection.removeSelection(part, "") else: @@ -857,31 +875,17 @@ class TaskAssemblyCreateView(QtCore.QObject): self.findDraggerInitialPlc() return - full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) - element_name = UtilsAssembly.getElementName(full_element_name) + element_name = UtilsAssembly.getElementName(sub_name) if element_name == "": self.setDragger() pass def setPreselection(self, doc_name, obj_name, sub_name): if not self.selectingFeature or not sub_name: - self.preselection_dict = None + self.presel_ref = None return - full_obj_name = UtilsAssembly.getFullObjName(obj_name, sub_name) - full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) - selected_object = UtilsAssembly.getObject(full_element_name) - element_name = UtilsAssembly.getElementName(full_element_name) - part = UtilsAssembly.getContainingPart(full_element_name, selected_object, self.assembly) - - self.preselection_dict = { - "object": selected_object, - "part": part, - "sub_name": sub_name, - "element_name": element_name, - "full_element_name": full_element_name, - "full_obj_name": full_obj_name, - } + self.presel_ref = [App.getDocument(doc_name).getObject(obj_name), [sub_name]] def clearSelection(self, doc_name): self.form.stepList.clearSelection() diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index 37cf6f69ee..534db31b05 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -164,10 +164,10 @@ bool ViewProviderAssembly::canDragObjectToTarget(App::DocumentObject* obj, for (auto joint : allJoints) { // getLinkObjFromProp returns nullptr if the property doesn't exist. - App::DocumentObject* obj1 = AssemblyObject::getObjFromProp(joint, "Object1"); - App::DocumentObject* obj2 = AssemblyObject::getObjFromProp(joint, "Object2"); - App::DocumentObject* part1 = AssemblyObject::getObjFromProp(joint, "Part1"); - App::DocumentObject* part2 = AssemblyObject::getObjFromProp(joint, "Part2"); + App::DocumentObject* obj1 = AssemblyObject::getObjFromRef(joint, "Reference1"); + App::DocumentObject* obj2 = AssemblyObject::getObjFromRef(joint, "Reference2"); + App::DocumentObject* part1 = assemblyPart->getMovingPartFromRef(joint, "Reference1"); + App::DocumentObject* part2 = assemblyPart->getMovingPartFromRef(joint, "Reference2"); App::DocumentObject* obj3 = AssemblyObject::getObjFromProp(joint, "ObjectToGround"); if (obj == obj1 || obj == obj2 || obj == part1 || obj == part2 || obj == obj3) { if (!prompted) { @@ -376,12 +376,12 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent } - for (auto& pair : docsToMove) { - App::DocumentObject* obj = pair.first; + for (auto& objToMove : docsToMove) { + App::DocumentObject* obj = objToMove.obj; auto* propPlacement = dynamic_cast(obj->getPropertyByName("Placement")); if (propPlacement) { - Base::Placement plc = pair.second; + Base::Placement plc = objToMove.plc; // Base::Console().Warning("newPos %f %f %f\n", newPos.x, newPos.y, newPos.z); if (dragMode == DragMode::RotationOnPlane) { @@ -585,7 +585,7 @@ bool ViewProviderAssembly::getSelectedObjectsWithinAssembly(bool addPreselection std::vector objsSubNames = selObj.getSubNames(); for (auto& subNamesStr : objsSubNames) { - std::vector subNames = parseSubNames(subNamesStr); + std::vector subNames = AssemblyObject::splitSubName(subNamesStr); if (subNames.empty()) { continue; } @@ -593,7 +593,8 @@ bool ViewProviderAssembly::getSelectedObjectsWithinAssembly(bool addPreselection continue; } - App::DocumentObject* obj = getObjectFromSubNames(subNames); + App::DocumentObject* selRoot = selObj.getObject(); + App::DocumentObject* obj = assemblyPart->getMovingPartFromRef(selRoot, subNamesStr); if (!canDragObjectIn3d(obj)) { continue; @@ -601,7 +602,10 @@ bool ViewProviderAssembly::getSelectedObjectsWithinAssembly(bool addPreselection auto* pPlc = dynamic_cast(obj->getPropertyByName("Placement")); - docsToMove.emplace_back(obj, pPlc->getValue()); + + MovingObject movingObj(obj, pPlc->getValue(), selRoot, subNamesStr); + + docsToMove.emplace_back(movingObj); } } } @@ -613,15 +617,15 @@ bool ViewProviderAssembly::getSelectedObjectsWithinAssembly(bool addPreselection // Base::Console().Warning("Gui::Selection().getPreselection().pSubName %s\n", // Gui::Selection().getPreselection().pSubName); - std::string subNamesStr = Gui::Selection().getPreselection().pSubName; - std::vector subNames = parseSubNames(subNamesStr); + App::DocumentObject* selRoot = Gui::Selection().getPreselection().Object.getObject(); + std::string sub = Gui::Selection().getPreselection().pSubName; - App::DocumentObject* obj = getObjectFromSubNames(subNames); + App::DocumentObject* obj = assemblyPart->getMovingPartFromRef(selRoot, sub); if (canDragObjectIn3d(obj)) { bool alreadyIn = false; - for (auto& pair : docsToMove) { - App::DocumentObject* obji = pair.first; + for (auto& movingObj : docsToMove) { + App::DocumentObject* obji = movingObj.obj; if (obji == obj) { alreadyIn = true; break; @@ -635,7 +639,9 @@ bool ViewProviderAssembly::getSelectedObjectsWithinAssembly(bool addPreselection Gui::Selection().clearSelection(); docsToMove.clear(); } - docsToMove.emplace_back(obj, pPlc->getValue()); + MovingObject movingObj(obj, pPlc->getValue(), selRoot, sub); + + docsToMove.emplace_back(movingObj); } } } @@ -643,88 +649,12 @@ bool ViewProviderAssembly::getSelectedObjectsWithinAssembly(bool addPreselection return !docsToMove.empty(); } -std::vector ViewProviderAssembly::parseSubNames(std::string& subNamesStr) -{ - std::vector subNames; - std::string subName; - std::istringstream subNameStream(subNamesStr); - while (std::getline(subNameStream, subName, '.')) { - subNames.push_back(subName); - } - - // Check if the last character of the input string is the delimiter. - // If so, add an empty string to the subNames vector. - // Because the last subname is the element name and can be empty. - if (!subNamesStr.empty() && subNamesStr.back() == '.') { - subNames.push_back(""); // Append empty string for trailing dot. - } - - return subNames; -} - -App::DocumentObject* ViewProviderAssembly::getObjectFromSubNames(std::vector& subNames) -{ - App::Document* appDoc = getObject()->getDocument(); - - std::string objName; - if (subNames.size() < 2) { - return nullptr; - } - else if (subNames.size() == 2) { - // If two subnames then it can't be a body and the object we want is the first one - // For example we want box in "box.face1" - // "assembly.part.box.face1" - // "p.fcstd.assembly.LinkToPart.box.face1" - // "p2.fcstd.Part.box." - return appDoc->getObject(subNames[0].c_str()); - } - - // From here subnames is at least 3 and can be more. There are several cases to consider : - // bodyOrLink.pad.face1 -> bodyOrLink should be the moving entity - // partOrLink.bodyOrLink.pad.face1 -> partOrLink should be the moving entity - // partOrLink.box.face1 -> partOrLink should be the moving entity - // partOrLink1...ParOrLinkn.bodyOrLink.pad.face1 -> partOrLink1 should be the moving entity - // assembly1.partOrLink1...ParOrLinkn.bodyOrLink.pad.face1 -> partOrLink1 should be the moving - // entity assembly1.boxOrLink1.face1 -> boxOrLink1 should be the moving entity - - for (auto objName : subNames) { - App::DocumentObject* obj = appDoc->getObject(objName.c_str()); - if (!obj) { - continue; - } - - if (obj->getTypeId().isDerivedFrom(AssemblyObject::getClassTypeId())) { - continue; - } - else if (obj->getTypeId().isDerivedFrom(App::Part::getClassTypeId()) - || obj->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { - return obj; - } - else if (obj->getTypeId().isDerivedFrom(App::Link::getClassTypeId())) { - App::Link* link = dynamic_cast(obj); - - App::DocumentObject* linkedObj = link->getLinkedObject(true); - if (!linkedObj) { - continue; - } - - if (linkedObj->getTypeId().isDerivedFrom(App::Part::getClassTypeId()) - || linkedObj->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) { - return obj; - } - } - } - - return nullptr; -} - ViewProviderAssembly::DragMode ViewProviderAssembly::findDragMode() { if (docsToMove.size() == 1) { auto* assemblyPart = static_cast(getObject()); - std::string partPropName; - movingJoint = - assemblyPart->getJointOfPartConnectingToGround(docsToMove[0].first, partPropName); + std::string pName; + movingJoint = assemblyPart->getJointOfPartConnectingToGround(docsToMove[0].obj, pName); if (!movingJoint) { return DragMode::Translation; @@ -735,45 +665,69 @@ ViewProviderAssembly::DragMode ViewProviderAssembly::findDragMode() // If fixed joint we need to find the upstream joint to find move mode. // For example : Gnd -(revolute)- A -(fixed)- B : if user try to move B, then we should // actually move A - App::DocumentObject* upstreamPart = - assemblyPart->getUpstreamMovingPart(docsToMove[0].first); - docsToMove.clear(); - if (!upstreamPart) { - return DragMode::None; - } - - auto* propPlacement = - dynamic_cast(upstreamPart->getPropertyByName("Placement")); - if (propPlacement) { - docsToMove.emplace_back(upstreamPart, propPlacement->getValue()); - } - - movingJoint = - assemblyPart->getJointOfPartConnectingToGround(docsToMove[0].first, partPropName); + auto* upPart = + assemblyPart->getUpstreamMovingPart(docsToMove[0].obj, movingJoint, pName); if (!movingJoint) { return DragMode::Translation; } + docsToMove.clear(); + if (!upPart) { + return DragMode::None; + } + + auto* pPlc = + dynamic_cast(upPart->getPropertyByName("Placement")); + if (pPlc) { + auto* ref = dynamic_cast( + movingJoint->getPropertyByName(pName.c_str())); + + App::DocumentObject* selRoot = ref->getValue(); + if (!selRoot) { + return DragMode::None; + } + std::vector subs = ref->getSubValues(); + if (subs.empty()) { + return DragMode::None; + } + + docsToMove.emplace_back(upPart, pPlc->getValue(), selRoot, subs[0]); + } + jointType = AssemblyObject::getJointType(movingJoint); } - const char* plcPropName = (partPropName == "Part1") ? "Placement1" : "Placement2"; - const char* objPropName = (partPropName == "Part1") ? "Object1" : "Object2"; + const char* plcPropName = (pName == "Reference1") ? "Placement1" : "Placement2"; // jcsPlc is relative to the Object jcsPlc = AssemblyObject::getPlacementFromProp(movingJoint, plcPropName); // Make jcsGlobalPlc relative to the origin of the doc - Base::Placement global_plc = - AssemblyObject::getGlobalPlacement(movingJoint, objPropName, partPropName.c_str()); + auto* ref = + dynamic_cast(movingJoint->getPropertyByName(pName.c_str())); + if (!ref) { + return DragMode::Translation; + } + auto* obj = assemblyPart->getObjFromRef(movingJoint, pName.c_str()); + Base::Placement global_plc = AssemblyObject::getGlobalPlacement(obj, ref); jcsGlobalPlc = global_plc * jcsPlc; // Add downstream parts so that they move together - auto downstreamParts = assemblyPart->getDownstreamParts(docsToMove[0].first, movingJoint); - for (auto part : downstreamParts) { - auto* propPlacement = - dynamic_cast(part->getPropertyByName("Placement")); - if (propPlacement) { - docsToMove.emplace_back(part, propPlacement->getValue()); + auto downstreamParts = assemblyPart->getDownstreamParts(docsToMove[0].obj, movingJoint); + for (auto partRef : downstreamParts) { + auto* pPlc = dynamic_cast( + partRef.first->getPropertyByName("Placement")); + if (pPlc) { + App::DocumentObject* selRoot = partRef.second->getValue(); + if (!selRoot) { + return DragMode::None; + } + std::vector subs = partRef.second->getSubValues(); + if (subs.empty()) { + return DragMode::None; + } + + + docsToMove.emplace_back(partRef.first, pPlc->getValue(), selRoot, subs[0]); } } @@ -859,15 +813,15 @@ void ViewProviderAssembly::initMove(const SbVec2s& cursorPos, Gui::View3DInvento bool solveOnMove = hGrp->GetBool("SolveOnMove", true); if (solveOnMove) { objectMasses.clear(); - for (auto& pair : docsToMove) { - objectMasses.push_back({pair.first, 10.0}); + for (auto& movingObj : docsToMove) { + objectMasses.push_back({movingObj.obj, 10.0}); } auto* assemblyPart = static_cast(getObject()); assemblyPart->setObjMasses(objectMasses); std::vector dragParts; - for (auto& pair : docsToMove) { - dragParts.push_back(pair.first); + for (auto& movingObj : docsToMove) { + dragParts.push_back(movingObj.obj); } assemblyPart->preDrag(dragParts); } @@ -910,13 +864,17 @@ void ViewProviderAssembly::initMoveDragger() setDraggerVisibility(true); // find the placement for the dragger. - App::DocumentObject* obj = docsToMove[0].first; - draggerInitPlc = AssemblyObject::getGlobalPlacement(obj, obj); + App::DocumentObject* part = docsToMove[0].obj; + + draggerInitPlc = + AssemblyObject::getGlobalPlacement(part, docsToMove[0].rootObj, docsToMove[0].sub); std::vector listOfObjs; - for (auto& pair : docsToMove) { - listOfObjs.push_back(pair.first); + std::vector listOfRefs; + for (auto& movingObj : docsToMove) { + listOfObjs.push_back(movingObj.obj); + listOfRefs.push_back(movingObj.ref); } - Base::Vector3d pos = getCenterOfBoundingBox(listOfObjs, listOfObjs); + Base::Vector3d pos = getCenterOfBoundingBox(docsToMove); draggerInitPlc.setPosition(pos); setDraggerPlacement(draggerInitPlc); @@ -939,12 +897,12 @@ void ViewProviderAssembly::draggerMotionCallback(void* data, SoDragger* d) Base::Placement draggerPlc = sudoThis->getDraggerPlacement(); Base::Placement movePlc = draggerPlc * sudoThis->draggerInitPlc.inverse(); - for (auto& pair : sudoThis->docsToMove) { - App::DocumentObject* obj = pair.first; + for (auto& movingObj : sudoThis->docsToMove) { + App::DocumentObject* obj = movingObj.obj; - auto* propPlc = dynamic_cast(obj->getPropertyByName("Placement")); - if (propPlc) { - propPlc->setValue(movePlc * pair.second); + auto* pPlc = dynamic_cast(obj->getPropertyByName("Placement")); + if (pPlc) { + pPlc->setValue(movePlc * movingObj.plc); } } } @@ -1078,14 +1036,14 @@ PyObject* ViewProviderAssembly::getPyObject() // UTILS Base::Vector3d -ViewProviderAssembly::getCenterOfBoundingBox(const std::vector& objs, - const std::vector& parts) +ViewProviderAssembly::getCenterOfBoundingBox(const std::vector& movingObjs) { int count = 0; - Base::Vector3d center; + Base::Vector3d center; // feujhzef - for (size_t i = 0; i < objs.size(); ++i) { - Gui::ViewProvider* viewProvider = Gui::Application::Instance->getViewProvider(objs[i]); + for (auto& movingObj : movingObjs) { + Gui::ViewProvider* viewProvider = + Gui::Application::Instance->getViewProvider(movingObj.obj); if (!viewProvider) { continue; } @@ -1097,17 +1055,16 @@ ViewProviderAssembly::getCenterOfBoundingBox(const std::vector& subNames); - std::vector parseSubNames(std::string& subNamesStr); /// Get the python wrapper for that ViewProvider PyObject* getPyObject() override; @@ -164,8 +193,7 @@ public: Base::Placement getDraggerPlacement(); Gui::SoFCCSysDragger* getDragger(); - static Base::Vector3d getCenterOfBoundingBox(const std::vector& objs, - const std::vector& parts); + static Base::Vector3d getCenterOfBoundingBox(const std::vector& movingObjs); DragMode dragMode; bool canStartDragging; @@ -189,7 +217,7 @@ public: App::DocumentObject* movingJoint; std::vector> objectMasses; - std::vector> docsToMove; + std::vector docsToMove; Gui::SoFCCSysDragger* asmDragger = nullptr; SoSwitch* asmDraggerSwitch = nullptr; diff --git a/src/Mod/Assembly/JointObject.py b/src/Mod/Assembly/JointObject.py index cf9033fbed..e79ec3112a 100644 --- a/src/Mod/Assembly/JointObject.py +++ b/src/Mod/Assembly/JointObject.py @@ -171,15 +171,11 @@ def get_active_view(gui_doc): # The joint object consists of 2 JCS (joint coordinate systems) and a Joint Type. -# A JCS is a placement that is computed (unless it is detached) from : -# - An Object: this can be any Part::Feature solid. Or a PartDesign Body. Or a App::Link to those. -# - A Part DocumentObject : This is the lowest level containing part. It can be either the Object itself if it -# stands alone. Or a App::Part. Or a App::Link to a App::Part. -# For example : -# Assembly.Assembly1.Part1.Part2.Box : Object is Box, part is 'Part1' -# Assembly.Assembly1.LinkToPart1.Part2.Box : Object is Box, part is 'LinkToPart1' +# A JCS is a placement that is computed (unless it is detached) from references (PropertyXLinkSubHidden) that links to : +# - An object: this can be any Part::Feature solid. Or a PartDesign Body. Or a App::Link to those. # - An element name: This can be either a face, an edge, a vertex or empty. Empty means that the Object placement will be used # - A vertex name: For faces and edges, we need to specify which vertex of said face/edge to use +# Both element names hold the full path to the object. # From these a placement is computed. It is relative to the Object. class Joint: def __init__(self, joint, type_index): @@ -203,22 +199,15 @@ class Joint: def createProperties(self, joint): self.migrationScript(joint) + self.migrationScript2(joint) # First Joint Connector - if not hasattr(joint, "Object1"): + if not hasattr(joint, "Reference1"): joint.addProperty( - "App::PropertyXLinkSub", - "Object1", + "App::PropertyXLinkSubHidden", + "Reference1", "Joint Connector 1", - QT_TRANSLATE_NOOP("App::Property", "The first object of the joint"), - ) - - if not hasattr(joint, "Part1"): - joint.addProperty( - "App::PropertyLink", - "Part1", - "Joint Connector 1", - QT_TRANSLATE_NOOP("App::Property", "The first part of the joint"), + QT_TRANSLATE_NOOP("App::Property", "The first reference of the joint"), ) if not hasattr(joint, "Placement1"): @@ -228,7 +217,7 @@ class Joint: "Joint Connector 1", QT_TRANSLATE_NOOP( "App::Property", - "This is the local coordinate system within object1 that will be used for the joint.", + "This is the local coordinate system within Reference1's object that will be used for the joint.", ), ) @@ -244,20 +233,12 @@ class Joint: ) # Second Joint Connector - if not hasattr(joint, "Object2"): + if not hasattr(joint, "Reference2"): joint.addProperty( - "App::PropertyXLinkSub", - "Object2", + "App::PropertyXLinkSubHidden", + "Reference2", "Joint Connector 2", - QT_TRANSLATE_NOOP("App::Property", "The second object of the joint"), - ) - - if not hasattr(joint, "Part2"): - joint.addProperty( - "App::PropertyLink", - "Part2", - "Joint Connector 2", - QT_TRANSLATE_NOOP("App::Property", "The second part of the joint"), + QT_TRANSLATE_NOOP("App::Property", "The second reference of the joint"), ) if not hasattr(joint, "Placement2"): @@ -267,7 +248,7 @@ class Joint: "Joint Connector 2", QT_TRANSLATE_NOOP( "App::Property", - "This is the local coordinate system within object2 that will be used for the joint.", + "This is the local coordinate system within Reference2's object that will be used for the joint.", ), ) @@ -470,6 +451,97 @@ class Joint: joint.Object2 = [obj2, [el2, vtx2]] + def migrationScript2(self, joint): + if hasattr(joint, "Object1"): + obj = joint.Object1[0] + part = joint.Part1 + elt = joint.Object1[1][0] + vtx = joint.Object1[1][1] + + joint.removeProperty("Object1") + joint.removeProperty("Part1") + + # now we need to get the 'selection-root-obj' and the global path + rootObj, path = UtilsAssembly.getRootPath(obj, part) + obj = rootObj + elt = path + elt + vtx = path + vtx + + joint.addProperty( + "App::PropertyXLinkSubHidden", + "Reference1", + "Joint Connector 1", + QT_TRANSLATE_NOOP("App::Property", "The first reference of the joint"), + ) + + joint.Reference1 = [obj, [elt, vtx]] + + if hasattr(joint, "Object2"): + obj = joint.Object2[0] + part = joint.Part2 + elt = joint.Object2[1][0] + vtx = joint.Object2[1][1] + + joint.removeProperty("Object2") + joint.removeProperty("Part2") + + rootObj, path = UtilsAssembly.getRootPath(obj, part) + obj = rootObj + elt = path + elt + vtx = path + vtx + + joint.addProperty( + "App::PropertyXLinkSubHidden", + "Reference2", + "Joint Connector 2", + QT_TRANSLATE_NOOP("App::Property", "The second reference of the joint"), + ) + + joint.Reference2 = [obj, [elt, vtx]] + + def getSubnameForSelection(self, obj, part, elName): + # We need the subname starting from the part. + # Example for : Assembly.Part1.LinkToPart2.Part3.Body.Tip.Face1 + # part is Part1 and obj is Body + # we should get : LinkToPart2.Part3.Body.Tip.Face1 + + if obj is None or part is None: + return elName + + if obj.TypeId == "PartDesign::Body": + elName = obj.Tip.Name + "." + elName + elif obj.TypeId == "App::Link": + linked_obj = obj.getLinkedObject() + if linked_obj.TypeId == "PartDesign::Body": + elName = linked_obj.Tip.Name + "." + elName + + if obj != part and obj in part.OutListRecursive: + bSub = "" + currentObj = part + + limit = 0 + while limit < 1000: + limit = limit + 1 + + if currentObj != part: + if bSub != "": + bSub = bSub + "." + bSub = bSub + currentObj.Name + + if currentObj == obj: + break + + if currentObj.TypeId == "App::Link": + currentObj = currentObj.getLinkedObject() + + for obji in currentObj.OutList: + if obji == obj or obj in obji.OutListRecursive: + currentObj = obji + break + + elName = bSub + "." + elName + return elName + def dumps(self): return None @@ -495,10 +567,11 @@ class Joint: return if prop == "Rotation" or prop == "Offset": - self.updateJCSPlacements(joint) - if joint.Object1 is None or joint.Object2 is None: + if joint.Reference1 is None or joint.Reference2 is None: return + self.updateJCSPlacements(joint) + presolved = self.preSolve(joint, False) isAssembly = self.getAssembly(joint).Type == "Assembly" @@ -508,45 +581,34 @@ class Joint: self.updateJCSPlacements(joint) if prop == "Distance" and (joint.JointType == "Distance" or joint.JointType == "Angle"): - if joint.Part1 and joint.Part2: - if joint.JointType == "Angle" and joint.Distance != 0.0: - self.preventParallel(joint) - solveIfAllowed(self.getAssembly(joint)) + if joint.Reference1 is None or joint.Reference2 is None: + return + + if joint.JointType == "Angle" and joint.Distance != 0.0: + self.preventParallel(joint) + solveIfAllowed(self.getAssembly(joint)) def execute(self, fp): """Do something when doing a recomputation, this method is mandatory""" # App.Console.PrintMessage("Recompute Python Box feature\n") pass - def setJointConnectors(self, joint, current_selection): + 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) isAssembly = assembly.Type == "Assembly" - if len(current_selection) >= 1: - joint.Object1 = [ - current_selection[0]["object"], - [current_selection[0]["element_name"], current_selection[0]["vertex_name"]], - ] - joint.Part1 = current_selection[0]["part"] - joint.Placement1 = self.findPlacement( - joint, joint.Object1[0], joint.Part1, joint.Object1[1][0], joint.Object1[1][1] - ) + if len(refs) >= 1: + joint.Reference1 = refs[0] + joint.Placement1 = self.findPlacement(joint, joint.Reference1, 0) else: - joint.Object1 = None - joint.Part1 = None + joint.Reference1 = None joint.Placement1 = App.Placement() self.partMovedByPresolved = None - if len(current_selection) >= 2: - joint.Object2 = [ - current_selection[1]["object"], - [current_selection[1]["element_name"], current_selection[1]["vertex_name"]], - ] - joint.Part2 = current_selection[1]["part"] - joint.Placement2 = self.findPlacement( - joint, joint.Object2[0], joint.Part2, joint.Object2[1][0], joint.Object2[1][1], True - ) + if len(refs) >= 2: + joint.Reference2 = refs[1] + joint.Placement2 = self.findPlacement(joint, joint.Reference2, 1) if joint.JointType in JointUsingPreSolve: self.preSolve(joint) elif joint.JointType in JointParallelForbidden: @@ -558,8 +620,7 @@ class Joint: self.updateJCSPlacements(joint) else: - joint.Object2 = None - joint.Part2 = None + joint.Reference2 = None joint.Placement2 = App.Placement() if isAssembly: assembly.undoSolve() @@ -567,14 +628,10 @@ class Joint: def updateJCSPlacements(self, joint): if not joint.Detach1: - joint.Placement1 = self.findPlacement( - joint, joint.Object1[0], joint.Part1, joint.Object1[1][0], joint.Object1[1][1] - ) + joint.Placement1 = self.findPlacement(joint, joint.Reference1, 0) if not joint.Detach2: - joint.Placement2 = self.findPlacement( - joint, joint.Object2[0], joint.Part2, joint.Object2[1][0], joint.Object2[1][1], True - ) + joint.Placement2 = self.findPlacement(joint, joint.Reference2, 1) """ So here we want to find a placement that corresponds to a local coordinate system that would be placed at the selected vertex. @@ -586,15 +643,12 @@ class Joint: - if elt is a cylindrical face, vtx can also be the center of the arcs of the cylindrical face. """ - def findPlacement(self, joint, obj, part, elt, vtx, isSecond=False): - if not obj or not part: - return App.Placement() - + def findPlacement(self, joint, ref, index=0): ignoreVertex = joint.JointType == "Distance" - plc = UtilsAssembly.findPlacement(obj, part, elt, vtx, ignoreVertex) + plc = UtilsAssembly.findPlacement(ref, ignoreVertex) # We apply rotation / reverse / offset it necessary, but only to the second JCS. - if isSecond: + if index == 1: if joint.Offset.Length != 0.0: plc = UtilsAssembly.applyOffsetToPlacement(plc, joint.Offset) if joint.Rotation != 0.0: @@ -604,28 +658,28 @@ class Joint: def flipOnePart(self, joint): assembly = self.getAssembly(joint) - part2ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Part2") - part1Grounded = assembly.isPartGrounded(joint.Part1) - part2Grounded = assembly.isPartGrounded(joint.Part2) + part2ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Reference2") + + part1 = UtilsAssembly.getMovingPart(assembly, joint.Reference1) + part2 = UtilsAssembly.getMovingPart(assembly, joint.Reference2) + + part1Grounded = assembly.isPartGrounded(part1) + part2Grounded = assembly.isPartGrounded(part2) if part2ConnectedByJoint and not part2Grounded: jcsPlc = UtilsAssembly.getJcsPlcRelativeToPart( - joint.Placement2, joint.Object2[0], joint.Part2 - ) - globalJcsPlc = UtilsAssembly.getJcsGlobalPlc( - joint.Placement2, joint.Object2[0], joint.Part2 + assembly, joint.Placement2, joint.Reference2 ) + globalJcsPlc = UtilsAssembly.getJcsGlobalPlc(joint.Placement2, joint.Reference2) jcsPlc = UtilsAssembly.flipPlacement(jcsPlc) - joint.Part2.Placement = globalJcsPlc * jcsPlc.inverse() + part2.Placement = globalJcsPlc * jcsPlc.inverse() elif not part1Grounded: jcsPlc = UtilsAssembly.getJcsPlcRelativeToPart( - joint.Placement1, joint.Object1[0], joint.Part1 - ) - globalJcsPlc = UtilsAssembly.getJcsGlobalPlc( - joint.Placement1, joint.Object1[0], joint.Part1 + assembly, joint.Placement1, joint.Reference1 ) + globalJcsPlc = UtilsAssembly.getJcsGlobalPlc(joint.Placement1, joint.Reference1) jcsPlc = UtilsAssembly.flipPlacement(jcsPlc) - joint.Part1.Placement = globalJcsPlc * jcsPlc.inverse() + part1.Placement = globalJcsPlc * jcsPlc.inverse() solveIfAllowed(self.getAssembly(joint)) @@ -634,13 +688,18 @@ class Joint: # we actually don't want to match perfectly the JCS, it is best to match them # in the current closest direction, ie either matched or flipped. + sameDir = self.areJcsSameDir(joint) assembly = self.getAssembly(joint) + + part1 = UtilsAssembly.getMovingPart(assembly, joint.Reference1) + part2 = UtilsAssembly.getMovingPart(assembly, joint.Reference2) + isAssembly = assembly.Type == "Assembly" if isAssembly: joint.Activated = False - part1Connected = assembly.isPartConnected(joint.Part1) - part2Connected = assembly.isPartConnected(joint.Part2) + part1Connected = assembly.isPartConnected(part1) + part2Connected = assembly.isPartConnected(part2) joint.Activated = True else: part1Connected = False @@ -648,34 +707,30 @@ class Joint: if not part2Connected: if savePlc: - self.partMovedByPresolved = joint.Part2 - self.presolveBackupPlc = joint.Part2.Placement + self.partMovedByPresolved = part2 + self.presolveBackupPlc = part2.Placement - globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc( - joint.Placement1, joint.Object1[0], joint.Part1 - ) + globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc(joint.Placement1, joint.Reference1) jcsPlc2 = UtilsAssembly.getJcsPlcRelativeToPart( - joint.Placement2, joint.Object2[0], joint.Part2 + assembly, joint.Placement2, joint.Reference2 ) if not sameDir: jcsPlc2 = UtilsAssembly.flipPlacement(jcsPlc2) - joint.Part2.Placement = globalJcsPlc1 * jcsPlc2.inverse() + part2.Placement = globalJcsPlc1 * jcsPlc2.inverse() return True elif not part1Connected: if savePlc: - self.partMovedByPresolved = joint.Part1 - self.presolveBackupPlc = joint.Part1.Placement + self.partMovedByPresolved = part1 + self.presolveBackupPlc = part1.Placement - globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc( - joint.Placement2, joint.Object2[0], joint.Part2 - ) + globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc(joint.Placement2, joint.Reference2) jcsPlc1 = UtilsAssembly.getJcsPlcRelativeToPart( - joint.Placement1, joint.Object1[0], joint.Part1 + assembly, joint.Placement1, joint.Reference1 ) if not sameDir: jcsPlc1 = UtilsAssembly.flipPlacement(jcsPlc1) - joint.Part1.Placement = globalJcsPlc2 * jcsPlc1.inverse() + part1.Placement = globalJcsPlc2 * jcsPlc1.inverse() return True return False @@ -693,47 +748,43 @@ class Joint: return assembly = self.getAssembly(joint) + + part1 = UtilsAssembly.getMovingPart(assembly, joint.Reference1) + part2 = UtilsAssembly.getMovingPart(assembly, joint.Reference2) + isAssembly = assembly.Type == "Assembly" if isAssembly: - part1ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Part1") - part2ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Part2") + part1ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Reference1") + part2ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Reference2") else: part1ConnectedByJoint = False part2ConnectedByJoint = True if part2ConnectedByJoint: - self.partMovedByPresolved = joint.Part2 - self.presolveBackupPlc = joint.Part2.Placement + self.partMovedByPresolved = part2 + self.presolveBackupPlc = part2.Placement - joint.Part2.Placement = UtilsAssembly.applyRotationToPlacementAlongAxis( - joint.Part2.Placement, 10, App.Vector(1, 0, 0) + part2.Placement = UtilsAssembly.applyRotationToPlacementAlongAxis( + part2.Placement, 10, App.Vector(1, 0, 0) ) elif part1ConnectedByJoint: - self.partMovedByPresolved = joint.Part1 - self.presolveBackupPlc = joint.Part1.Placement + self.partMovedByPresolved = part1 + self.presolveBackupPlc = part1.Placement - joint.Part1.Placement = UtilsAssembly.applyRotationToPlacementAlongAxis( - joint.Part1.Placement, 10, App.Vector(1, 0, 0) + part1.Placement = UtilsAssembly.applyRotationToPlacementAlongAxis( + part1.Placement, 10, App.Vector(1, 0, 0) ) def areJcsSameDir(self, joint): - globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc( - joint.Placement1, joint.Object1[0], joint.Part1 - ) - globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc( - joint.Placement2, joint.Object2[0], joint.Part2 - ) + globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc(joint.Placement1, joint.Reference1) + globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc(joint.Placement2, joint.Reference2) return UtilsAssembly.arePlacementSameDir(globalJcsPlc1, globalJcsPlc2) def areJcsZParallel(self, joint): - globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc( - joint.Placement1, joint.Object1[0], joint.Part1 - ) - globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc( - joint.Placement2, joint.Object2[0], joint.Part2 - ) + globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc(joint.Placement1, joint.Reference1) + globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc(joint.Placement2, joint.Reference2) return UtilsAssembly.arePlacementZParallel(globalJcsPlc1, globalJcsPlc2) @@ -872,9 +923,9 @@ class ViewProviderJoint: def get_JCS_size(self): return get_camera_height(self.gui_doc) / 20 - def set_JCS_placement(self, soTransform, placement, obj, part): + def set_JCS_placement(self, soTransform, placement, ref): # change plc to be relative to the origin of the document. - global_plc = UtilsAssembly.getGlobalPlacement(obj, part) + global_plc = UtilsAssembly.getGlobalPlacement(ref) placement = global_plc * placement t = placement.Base @@ -887,29 +938,27 @@ class ViewProviderJoint: """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 joint.Object1: + if hasattr(joint, "Reference1") and joint.Reference1: plc = joint.Placement1 self.switch_JCS1.whichChild = coin.SO_SWITCH_ALL - if joint.Part1: - self.set_JCS_placement(self.transform1, plc, joint.Object1[0], joint.Part1) + self.set_JCS_placement(self.transform1, plc, joint.Reference1) else: self.switch_JCS1.whichChild = coin.SO_SWITCH_NONE if prop == "Placement2": - if joint.Object2: + if hasattr(joint, "Reference2") and joint.Reference2: plc = joint.Placement2 self.switch_JCS2.whichChild = coin.SO_SWITCH_ALL - if joint.Part2: - self.set_JCS_placement(self.transform2, plc, joint.Object2[0], joint.Part2) + self.set_JCS_placement(self.transform2, plc, joint.Reference2) else: self.switch_JCS2.whichChild = coin.SO_SWITCH_NONE - def showPreviewJCS(self, visible, placement=None, obj=None, part=None): + def showPreviewJCS(self, visible, placement=None, ref=None): if visible: self.switch_JCS_preview.whichChild = coin.SO_SWITCH_ALL - self.set_JCS_placement(self.transform3, placement, obj, part) + self.set_JCS_placement(self.transform3, placement, ref) else: self.switch_JCS_preview.whichChild = coin.SO_SWITCH_NONE @@ -1232,9 +1281,8 @@ class MakeJointSelGate: # Only objects within the assembly. return False - full_obj_name = ".".join(objs_names) - full_element_name = full_obj_name + "." + element_name - selected_object = UtilsAssembly.getObject(full_element_name) + ref = [obj, [sub]] + selected_object = UtilsAssembly.getObject(ref) if not ( selected_object.isDerivedFrom("Part::Feature") @@ -1329,8 +1377,8 @@ class TaskAssemblyCreateJoint(QtCore.QObject): else: App.setActiveTransaction("Create " + self.jointName + " Joint") - self.current_selection = [] - self.preselection_dict = None + self.refs = [] + self.presel_ref = None self.createJointObject() self.visibilityBackup = False @@ -1359,7 +1407,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.addition_rejected = False def accept(self): - if len(self.current_selection) != 2: + if len(self.refs) != 2: App.Console.PrintWarning( translate("Assembly", "You need to select 2 elements from 2 separate parts.") ) @@ -1419,52 +1467,27 @@ class TaskAssemblyCreateJoint(QtCore.QObject): continue 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) + # Only objects within the assembly. - objs_names, element_name = UtilsAssembly.getObjsNamesAndElement( - sel.ObjectName, sub_name - ) - if self.assembly.Name not in objs_names: + if moving_part is None: Gui.Selection.removeSelection(sel.Object, sub_name) continue - obj_name = sel.ObjectName - - full_obj_name = UtilsAssembly.getFullObjName(obj_name, sub_name) - full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) - selected_object = UtilsAssembly.getObject(full_element_name) - element_name = UtilsAssembly.getElementName(full_element_name) - part_containing_selected_object = self.getContainingPart( - full_element_name, selected_object - ) - - if selected_object == self.assembly: - # do not accept selection of assembly itself - Gui.Selection.removeSelection(sel.Object, sub_name) - continue - - if ( - len(self.current_selection) == 1 - and selected_object == self.current_selection[0]["object"] - ): + if len(self.refs) == 1 and moving_part == self.getMovingPart(self.refs[0]): # do not select several feature of the same object. - self.current_selection.clear() + self.refs.clear() Gui.Selection.clearSelection() return - selection_dict = { - "object": selected_object, - "part": part_containing_selected_object, - "element_name": element_name, - "full_element_name": full_element_name, - "full_obj_name": full_obj_name, - "vertex_name": element_name, - } - - self.current_selection.append(selection_dict) + self.refs.append(ref) # do not accept initial selection if we don't have 2 selected features - if len(self.current_selection) != 2: - self.current_selection.clear() + if len(self.refs) != 2: + self.refs.clear() Gui.Selection.clearSelection() else: self.updateJoint() @@ -1635,46 +1658,17 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.form.groupBox_limits.hide() def updateTaskboxFromJoint(self): - self.current_selection = [] - self.preselection_dict = None + self.refs = [] + self.presel_ref = None - obj1 = self.joint.Object1[0] - part1 = self.joint.Part1 - el1 = self.joint.Object1[1][0] - vtx1 = self.joint.Object1[1][1] + ref1 = self.joint.Reference1 + ref2 = self.joint.Reference2 - obj2 = self.joint.Object2[0] - part2 = self.joint.Part2 - el2 = self.joint.Object2[1][0] - vtx2 = self.joint.Object2[1][1] + self.refs.append(ref1) + self.refs.append(ref2) - selection_dict1 = { - "object": obj1, - "part": part1, - "element_name": el1, - "vertex_name": vtx1, - } - - selection_dict2 = { - "object": obj2, - "part": part2, - "element_name": el2, - "vertex_name": vtx2, - } - - self.current_selection.append(selection_dict1) - self.current_selection.append(selection_dict2) - - # Add the elements to the selection. Note we cannot do : - # Gui.Selection.addSelection(self.doc.Name, obj1.Name, elName) - # Because obj1 can be external in which case addSelection will fail. And - # Gui.Selection.addSelection(obj1.Document.Name, obj1.Name, elName) - # will not select in the assembly doc. - elName = self.getSubnameForSelection(obj1, part1, el1) - Gui.Selection.addSelection(self.doc.Name, part1.Name, elName) - - elName = self.getSubnameForSelection(obj2, part2, el2) - Gui.Selection.addSelection(self.doc.Name, part2.Name, elName) + Gui.Selection.addSelection(ref1[0].Document.Name, ref1[0].Name, ref1[1][0]) + Gui.Selection.addSelection(ref2[0].Document.Name, ref2[0].Name, ref2[1][0]) self.form.distanceSpinbox.setProperty("rawValue", self.joint.Distance) self.form.distanceSpinbox2.setProperty("rawValue", self.joint.Distance2) @@ -1693,63 +1687,23 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.form.jointType.setCurrentIndex(JointTypes.index(self.joint.JointType)) self.updateJointList() - def getSubnameForSelection(self, obj, part, elName): - # We need the subname starting from the part. - # Example for : Assembly.Part1.LinkToPart2.Part3.Body.Tip.Face1 - # part is Part1 and obj is Body - # we should get : LinkToPart2.Part3.Body.Tip.Face1 - - if obj is None or part is None: - return elName - - if obj.TypeId == "PartDesign::Body": - elName = obj.Tip.Name + "." + elName - elif obj.TypeId == "App::Link": - linked_obj = obj.getLinkedObject() - if linked_obj.TypeId == "PartDesign::Body": - elName = linked_obj.Tip.Name + "." + elName - - if obj != part and obj in part.OutListRecursive: - bSub = "" - currentObj = part - - limit = 0 - while limit < 1000: - limit = limit + 1 - - if currentObj != part: - if bSub != "": - bSub = bSub + "." - bSub = bSub + currentObj.Name - - if currentObj == obj: - break - - if currentObj.TypeId == "App::Link": - currentObj = currentObj.getLinkedObject() - - for obji in currentObj.OutList: - if obji == obj or obj in obji.OutListRecursive: - currentObj = obji - break - - elName = bSub + "." + elName - return elName - def updateJoint(self): # First we build the listwidget self.updateJointList() # Then we pass the new list to the joint object - self.joint.Proxy.setJointConnectors(self.joint, self.current_selection) + self.joint.Proxy.setJointConnectors(self.joint, self.refs) def updateJointList(self): self.form.featureList.clear() simplified_names = [] - for sel in self.current_selection: - sname = sel["object"].Label - if sel["element_name"] != "": - sname = sname + "." + sel["element_name"] + for ref in self.refs: + + sname = UtilsAssembly.getObject(ref).Label + + element_name = UtilsAssembly.getElementName(ref[1][0]) + if element_name != "": + sname = sname + "." + element_name simplified_names.append(sname) self.form.featureList.addItems(simplified_names) @@ -1771,15 +1725,15 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.form.limitRotMaxSpinbox.setProperty("rawValue", angle) def moveMouse(self, info): - if len(self.current_selection) >= 2 or ( - len(self.current_selection) == 1 + if len(self.refs) >= 2 or ( + len(self.refs) == 1 and ( - not self.preselection_dict - or self.current_selection[0]["part"] == self.preselection_dict["part"] + not self.presel_ref + or self.getMovingPart(self.refs[0]) == self.getMovingPart(self.presel_ref) ) ): self.joint.ViewObject.Proxy.showPreviewJCS(False) - if len(self.current_selection) >= 2: + if len(self.refs) >= 2: self.updateLimits() return @@ -1789,38 +1743,24 @@ class TaskAssemblyCreateJoint(QtCore.QObject): if ( not cursor_info - or not self.preselection_dict - # or cursor_info["SubName"] != self.preselection_dict["sub_name"] + or not self.presel_ref + # or cursor_info["SubName"] != self.presel_ref["sub_name"] # Removed because they are not equal when hovering a line endpoints. # But we don't actually need to test because if there's no preselection then not cursor is None ): self.joint.ViewObject.Proxy.showPreviewJCS(False) return - # newPos = self.view.getPoint(*info["Position"]) # This is not what we want, it's not pos on the object but on the focal plane + ref = self.presel_ref + # newPos = self.view.getPoint(*info["Position"]) is not OK: it's not pos on the object but on the focal plane newPos = App.Vector(cursor_info["x"], cursor_info["y"], cursor_info["z"]) - self.preselection_dict["mouse_pos"] = newPos + vertex_name = UtilsAssembly.findElementClosestVertex(self.assembly, ref, newPos) - if self.preselection_dict["element_name"] == "": - self.preselection_dict["vertex_name"] = "" - else: - self.preselection_dict["vertex_name"] = UtilsAssembly.findElementClosestVertex( - self.preselection_dict - ) + ref = UtilsAssembly.addVertexToReference(ref, vertex_name) - isSecond = len(self.current_selection) == 1 - obj = self.preselection_dict["object"] - part = self.preselection_dict["part"] - placement = self.joint.Proxy.findPlacement( - self.joint, - obj, - part, - self.preselection_dict["element_name"], - self.preselection_dict["vertex_name"], - isSecond, - ) - self.joint.ViewObject.Proxy.showPreviewJCS(True, placement, obj, part) + placement = self.joint.Proxy.findPlacement(self.joint, ref, 0) + self.joint.ViewObject.Proxy.showPreviewJCS(True, placement, ref) self.previewJCSVisible = True # 3D view keyboard handler @@ -1845,38 +1785,46 @@ class TaskAssemblyCreateJoint(QtCore.QObject): for index in selected_indexes: row = index.row() - if row < len(self.current_selection): - selection_dict = self.current_selection[row] - elName = self.getSubnameForSelection( - selection_dict["object"], - selection_dict["part"], - selection_dict["element_name"], - ) - Gui.Selection.removeSelection(selection_dict["object"], elName) + if row < len(self.refs): + ref = self.refs[row] + + Gui.Selection.removeSelection(ref[0], ref[1][0]) return True # Consume the event return super().eventFilter(watched, event) - def getContainingPart(self, full_element_name, obj): - return UtilsAssembly.getContainingPart(full_element_name, obj, self.assembly) + def getMovingPart(self, ref): + return UtilsAssembly.getMovingPart(self.assembly, ref) # selectionObserver stuff def addSelection(self, doc_name, obj_name, sub_name, mousePos): - full_obj_name = UtilsAssembly.getFullObjName(obj_name, sub_name) - full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) - selected_object = UtilsAssembly.getObject(full_element_name) - element_name = UtilsAssembly.getElementName(full_element_name) - part_containing_selected_object = self.getContainingPart(full_element_name, selected_object) + rootObj = App.getDocument(doc_name).getObject(obj_name) + resolved = rootObj.resolveSubElement(sub_name) + element_name_TNP = resolved[1] + element_name = resolved[2] + + # Preprocess the sub_name to remove the TNP string + # We do this because after we need to add the vertex_name as well. + # And the names will be resolved anyway after. + if len(element_name_TNP.split(".")) == 2: + names = sub_name.split(".") + names.pop(-2) # remove the TNP string + sub_name = ".".join(names) + + ref = [rootObj, [sub_name]] + + moving_part = self.getMovingPart(ref) # Check if the addition is acceptable (we are not doing this in selection gate to let user move objects) acceptable = True - if len(self.current_selection) >= 2: + if len(self.refs) >= 2: # No more than 2 elements can be selected for basic joints. acceptable = False - for selection_dict in self.current_selection: - if selection_dict["part"] == part_containing_selected_object: + for reference in self.refs: + sel_moving_part = self.getMovingPart(reference) + if sel_moving_part == moving_part: # Can't join a solid to itself. So the user need to select 2 different parts. acceptable = False @@ -1886,20 +1834,14 @@ class TaskAssemblyCreateJoint(QtCore.QObject): return # Selection is acceptable so add it - selection_dict = { - "object": selected_object, - "part": part_containing_selected_object, - "element_name": element_name, - "full_element_name": full_element_name, - "full_obj_name": full_obj_name, - "mouse_pos": App.Vector(mousePos[0], mousePos[1], mousePos[2]), - } - if element_name == "": - selection_dict["vertex_name"] = "" - else: - selection_dict["vertex_name"] = UtilsAssembly.findElementClosestVertex(selection_dict) - self.current_selection.append(selection_dict) + mousePos = App.Vector(mousePos[0], mousePos[1], mousePos[2]) + vertex_name = UtilsAssembly.findElementClosestVertex(self.assembly, ref, mousePos) + + # add the vertex name to the reference + ref = UtilsAssembly.addVertexToReference(ref, vertex_name) + + self.refs.append(ref) self.updateJoint() # We hide the preview JCS if we just added to the selection @@ -1910,41 +1852,27 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.addition_rejected = False return - full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) - selected_object = UtilsAssembly.getObject(full_element_name) - element_name = UtilsAssembly.getElementName(full_element_name) - part_containing_selected_object = self.getContainingPart(full_element_name, selected_object) + ref = [App.getDocument(doc_name).getObject(obj_name), [sub_name]] + moving_part = self.getMovingPart(ref) # Find and remove the corresponding dictionary from the combined list - for selection_dict in self.current_selection: - if selection_dict["part"] == part_containing_selected_object: - self.current_selection.remove(selection_dict) + for reference in self.refs: + sel_moving_part = self.getMovingPart(reference) + if sel_moving_part == moving_part: + self.refs.remove(reference) break self.updateJoint() def setPreselection(self, doc_name, obj_name, sub_name): if not sub_name: - self.preselection_dict = None + self.presel_ref = None return - full_obj_name = UtilsAssembly.getFullObjName(obj_name, sub_name) - full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) - selected_object = UtilsAssembly.getObject(full_element_name) - element_name = UtilsAssembly.getElementName(full_element_name) - part_containing_selected_object = self.getContainingPart(full_element_name, selected_object) - - self.preselection_dict = { - "object": selected_object, - "part": part_containing_selected_object, - "sub_name": sub_name, - "element_name": element_name, - "full_element_name": full_element_name, - "full_obj_name": full_obj_name, - } + self.presel_ref = [App.getDocument(doc_name).getObject(obj_name), [sub_name]] def clearSelection(self, doc_name): - self.current_selection.clear() + self.refs.clear() self.updateJoint() def setJointsPickableState(self, state: bool): diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index 7669d4faab..27724aba2f 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -95,32 +95,27 @@ def assembly_has_at_least_n_parts(n): return False -def getObject(full_name): - # full_name is "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBox.Edge16" - # or "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBody.pad.Edge16" +def getObject(ref): + if len(ref) != 2: + return None + subs = ref[1] + if len(subs) < 1: + return None + sub_name = subs[0] + + # sub_name is "LinkOrAssembly1.LinkOrPart1.LinkOrBox.Edge16" + # or "LinkOrAssembly1.LinkOrPart1.LinkOrBody.pad.Edge16" # or "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBody.Local_CS.X" # We want either LinkOrBody or LinkOrBox or Local_CS. - names = full_name.split(".") - doc = App.ActiveDocument + names = sub_name.split(".") - if len(names) < 3: + if len(names) < 2: return None - prevObj = None + doc = ref[0].Document - for i, objName in enumerate(names): - if i == 0: - prevObj = doc.getObject(objName) - if prevObj.TypeId == "App::Link": - prevObj = prevObj.getLinkedObject() - continue - - obj = None - if prevObj.TypeId in {"App::Part", "Assembly::AssemblyObject", "App::DocumentObjectGroup"}: - for obji in prevObj.OutList: - if obji.Name == objName: - obj = obji - break + for i, obj_name in enumerate(names): + obj = doc.getObject(obj_name) if obj is None: return None @@ -129,26 +124,7 @@ def getObject(full_name): if i == len(names) - 2: return obj - if obj.TypeId == "App::Link": - linked_obj = obj.getLinkedObject() - if linked_obj.TypeId == "PartDesign::Body": - if i + 1 < len(names): - obj2 = None - for obji in linked_obj.OutList: - if obji.Name == names[i + 1]: - obj2 = obji - break - if obj2 and isBodySubObject(obj2.TypeId): - return obj2 - return obj - elif linked_obj.isDerivedFrom("Part::Feature"): - return obj - else: - prevObj = linked_obj - continue - - elif obj.TypeId in {"App::Part", "Assembly::AssemblyObject", "App::DocumentObjectGroup"}: - prevObj = obj + if obj.TypeId in {"App::Part", "Assembly::AssemblyObject"}: continue elif obj.TypeId == "PartDesign::Body": @@ -166,6 +142,24 @@ def getObject(full_name): # primitive, fastener, gear ... return obj + elif obj.TypeId == "App::Link": + linked_obj = obj.getLinkedObject() + if linked_obj.TypeId == "PartDesign::Body": + if i + 1 < len(names): + obj2 = None + for obji in linked_obj.OutList: + if obji.Name == names[i + 1]: + obj2 = obji + break + if obj2 and isBodySubObject(obj2.TypeId): + return obj2 + return obj + elif linked_obj.isDerivedFrom("Part::Feature"): + return obj + else: + doc = linked_obj.Document + continue + return None @@ -179,6 +173,7 @@ def isBodySubObject(typeId): ) +# To be deprecated. CommandCreateView needs to stop using it. def getContainingPart(full_name, selected_object, activeAssemblyOrPart=None): # full_name is "Assembly.Assembly1.LinkOrPart1.LinkOrBox.Edge16" -> LinkOrPart1 # or "Assembly.Assembly1.LinkOrPart1.LinkOrBody.pad.Edge16" -> LinkOrPart1 @@ -243,6 +238,7 @@ def getContainingPart(full_name, selected_object, activeAssemblyOrPart=None): return selected_object +# To be deprecated. Kept for migrationScript. def getObjectInPart(objName, part): if part is None: return None @@ -266,43 +262,85 @@ def getObjectInPart(objName, part): return None -# get the placement of Obj relative to its containing Part +# Used by migrationScript. +def getRootPath(obj, part): + sels = obj.Parents + for sel in sels: + rootObj = sel[0] + # The part and the rootObj should be in the same doc + if rootObj.Document.Name != part.Document.Name: + continue + + path = sel[1] + # we need to check that the part name is in the list. + names = path.split(".") + if part.Name not in names: + continue + + # for bodies we need to add the tip to the path. + if obj.TypeId == "PartDesign::Body": + path.append(obj.Tip.Name + ".") + + return rootObj, path + + 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(obj, part): - # we need plc to be relative to the containing part - obj_global_plc = getGlobalPlacement(obj, part) - part_global_plc = getGlobalPlacement(part) +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(jcsPlc, obj, part): - obj_relative_plc = getObjPlcRelativeToPart(obj, part) +def getJcsPlcRelativeToPart(assembly, jcsPlc, ref): + obj_relative_plc = getObjPlcRelativeToPart(assembly, ref) return obj_relative_plc * jcsPlc # Return the jcs global placement -def getJcsGlobalPlc(jcsPlc, obj, part): - obj_global_plc = getGlobalPlacement(obj, part) +def getJcsGlobalPlc(jcsPlc, ref): + obj_global_plc = getGlobalPlacement(ref) return obj_global_plc * jcsPlc -# The container is used to support cases where the same object appears at several places -# which happens when you have a link to a part. -def getGlobalPlacement(targetObj, container=None): +def getGlobalPlacement(ref, targetObj=None): + if not isRefValid(ref, 1): + return App.Placement() + + if targetObj is None: # If no targetObj is given, we consider it's the getObject(ref) + targetObj = getObject(ref) + if targetObj is None: return App.Placement() - inContainerBranch = container is None - for rootObj in App.activeDocument().RootObjectsIgnoreLinks: - foundPlacement = getTargetPlacementRelativeTo( - targetObj, rootObj, container, inContainerBranch - ) - if foundPlacement is not None: - return foundPlacement + rootObj = ref[0] + names = ref[1][0].split(".") + doc = rootObj.Document + plc = rootObj.Placement + + for objName in names: + obj = doc.getObject(objName) + if not obj: + continue + + plc = plc * obj.Placement + + if obj == targetObj: + return plc + + if obj.TypeId == "App::Link": + linked_obj = obj.getLinkedObject() + doc = linked_obj.Document # in case its an external link. + + # If targetObj has not been found there's a problem return App.Placement() @@ -313,71 +351,13 @@ def isThereOneRootAssembly(): return False -def getTargetPlacementRelativeTo( - targetObj, part, container, inContainerBranch, ignorePlacement=False -): - inContainerBranch = inContainerBranch or (not ignorePlacement and part == container) - - if targetObj == part and inContainerBranch and not ignorePlacement: - return targetObj.Placement - - if part.TypeId == "App::DocumentObjectGroup": - for obj in part.OutList: - foundPlacement = getTargetPlacementRelativeTo( - targetObj, obj, container, inContainerBranch, ignorePlacement - ) - if foundPlacement is not None: - return foundPlacement - - elif part.TypeId in {"App::Part", "Assembly::AssemblyObject", "PartDesign::Body"}: - for obj in part.OutList: - foundPlacement = getTargetPlacementRelativeTo( - targetObj, obj, container, inContainerBranch - ) - if foundPlacement is None: - continue - - # If we were called from a link then we need to ignore this placement as we use the link placement instead. - if not ignorePlacement: - foundPlacement = part.Placement * foundPlacement - - return foundPlacement - - elif part.TypeId == "App::Link": - linked_obj = part.getLinkedObject() - if part == linked_obj or linked_obj is None: - return None # upon loading this can happen for external links. - - if linked_obj.TypeId in {"App::Part", "Assembly::AssemblyObject", "PartDesign::Body"}: - for obj in linked_obj.OutList: - foundPlacement = getTargetPlacementRelativeTo( - targetObj, obj, container, inContainerBranch - ) - if foundPlacement is None: - continue - - foundPlacement = part.Placement * foundPlacement - return foundPlacement - - foundPlacement = getTargetPlacementRelativeTo( - targetObj, linked_obj, container, inContainerBranch, True - ) - - if foundPlacement is not None and not ignorePlacement: - foundPlacement = part.Placement * foundPlacement - - return foundPlacement - - return None - - def getElementName(full_name): # full_name is "Assembly.Assembly1.Assembly2.Assembly3.Box.Edge16" # We want either Edge16. parts = full_name.split(".") - if len(parts) < 3: - # At minimum "Assembly.Box.edge16". It shouldn't be shorter + if len(parts) < 2: + # At minimum "Box.edge16". It shouldn't be shorter return "" # case of PartDesign datums : CoordinateSystem, point, line, plane @@ -452,23 +432,27 @@ def extract_type_and_number(element_name): return None, None -def findElementClosestVertex(selection_dict): - obj = selection_dict["object"] +def findElementClosestVertex(assembly, ref, mousePos): + element_name = getElementName(ref[1][0]) + if element_name == "": + return "" - mousePos = selection_dict["mouse_pos"] + moving_part = getMovingPart(assembly, ref) + obj = getObject(ref) # We need mousePos to be relative to the part containing obj global placement - if selection_dict["object"] != selection_dict["part"]: + if obj != moving_part: plc = App.Placement() plc.Base = mousePos - global_plc = getGlobalPlacement(selection_dict["part"]) - plc = global_plc.inverse() * plc + global_plc = getGlobalPlacement(ref) + plc = global_plc.inverse() * plc # We make it relative to obj Origin + plc = obj.Placement * plc # Make plc in the same lcs as obj mousePos = plc.Base - elt_type, elt_index = extract_type_and_number(selection_dict["element_name"]) + elt_type, elt_index = extract_type_and_number(element_name) if elt_type == "Vertex": - return selection_dict["element_name"] + return element_name elif elt_type == "Edge": edge = obj.Shape.Edges[elt_index - 1] @@ -476,7 +460,7 @@ def findElementClosestVertex(selection_dict): if curve.TypeId == "Part::GeomCircle": # For centers, as they are not shape vertexes, we return the element name. # For now we only allow selecting the center of arcs / circles. - return selection_dict["element_name"] + return element_name edge_points = getPointsFromVertexes(edge.Vertexes) @@ -489,7 +473,7 @@ def findElementClosestVertex(selection_dict): if curve.TypeId == "Part::GeomLine" and closest_vertex_index == 2: # If line center is closest then we have no vertex name to set so we put element name - return selection_dict["element_name"] + return element_name vertex_name = findVertexNameInObject(edge.Vertexes[closest_vertex_index], obj) @@ -500,7 +484,7 @@ def findElementClosestVertex(selection_dict): surface = face.Surface _type = surface.TypeId if _type == "Part::GeomSphere" or _type == "Part::GeomTorus": - return selection_dict["element_name"] + return element_name # Handle the circle/arc edges for their centers center_points = [] @@ -558,11 +542,11 @@ def findElementClosestVertex(selection_dict): return "Edge" + str(index) if _type == "Part::GeomCylinder" or _type == "Part::GeomCone": - return selection_dict["element_name"] + return element_name if closest_vertex_index == len(face.Vertexes): # If center of gravity then we have no vertex name to set so we put element name - return selection_dict["element_name"] + return element_name vertex_name = findVertexNameInObject(face.Vertexes[closest_vertex_index], obj) @@ -785,10 +769,10 @@ def getObjMassAndCom(obj, containingPart=None): return 0, App.Vector(0, 0, 0) -def getCenterOfBoundingBox(objs, parts): +def getCenterOfBoundingBox(objs, refs): i = 0 center = App.Vector() - for obj, part in zip(objs, parts): + for obj, ref in zip(objs, refs): viewObject = obj.ViewObject if viewObject is None: continue @@ -796,15 +780,15 @@ def getCenterOfBoundingBox(objs, parts): if boundingBox is None: continue bboxCenter = boundingBox.Center - if part != obj: - # bboxCenter does not take into account obj global placement - plc = App.Placement(bboxCenter, App.Rotation()) - # change plc to be relative to the object placement. - plc = obj.Placement.inverse() * plc - # change plc to be relative to the origin of the document. - global_plc = getGlobalPlacement(obj, part) - plc = global_plc * plc - bboxCenter = plc.Base + + # bboxCenter does not take into account obj global placement + plc = App.Placement(bboxCenter, App.Rotation()) + # change plc to be relative to the object placement. + plc = obj.Placement.inverse() * plc + # change plc to be relative to the origin of the document. + global_plc = getGlobalPlacement(ref, obj) + plc = global_plc * plc + bboxCenter = plc.Base center = center + bboxCenter i = i + 1 @@ -880,9 +864,15 @@ So here we want to find a placement that corresponds to a local coordinate syste """ -def findPlacement(obj, part, elt, vtx, ignoreVertex=False): - if not obj or not part: +def findPlacement(ref, ignoreVertex=False): + if not isRefValid(ref, 2): return App.Placement() + obj = getObject(ref) + if not obj: + return App.Placement() + + elt = getElementName(ref[1][0]) + vtx = getElementName(ref[1][1]) # case of origin objects. if elt == "X_Axis" or elt == "YZ_Plane": @@ -999,6 +989,17 @@ def findPlacement(obj, part, elt, vtx, ignoreVertex=False): return plc +def isRefValid(ref, number_sub): + if ref is None: + return False + if len(ref) != 2: + return False + if len(ref[1]) < number_sub: + return False + + return True + + def round_vector(v, decimals=10): """Round each component of the vector to a specified number of decimal places.""" return App.Vector(round(v.x, decimals), round(v.y, decimals), round(v.z, decimals)) @@ -1045,8 +1046,8 @@ def getAssemblyShapes(assembly): def getJointDistance(joint): - plc1 = getJcsGlobalPlc(joint.Placement1, joint.Object1, joint.Part1) - plc2 = getJcsGlobalPlc(joint.Placement2, joint.Object2, joint.Part2) + plc1 = getJcsGlobalPlc(joint.Placement1, joint.Reference1) + plc2 = getJcsGlobalPlc(joint.Placement2, joint.Reference2) # Find the sign sign = 1 @@ -1058,10 +1059,112 @@ def getJointDistance(joint): def getJointXYAngle(joint): - plc1 = getJcsGlobalPlc(joint.Placement1, joint.Object1, joint.Part1) - plc2 = getJcsGlobalPlc(joint.Placement2, joint.Object2, joint.Part2) + plc1 = getJcsGlobalPlc(joint.Placement1, joint.Reference1) + plc2 = getJcsGlobalPlc(joint.Placement2, joint.Reference2) plc3 = plc1.inverse() * plc2 # plc3 is plc2 relative to plc1 x_axis = plc3.Rotation.multVec(App.Vector(1, 0, 0)) 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: + return None + + obj = ref[0] + subs = ref[1] + + if subs is None or len(subs) < 1: + return None + + 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 + + doc = assembly.Document + + if len(names) < 2: + App.Console.PrintError( + "getMovingPart() in UtilsAssembly.py the object name is too short, at minimum it should be something like ['Box','edge16']. It shouldn't be shorter" + ) + return None + + for objName in names: + obj = doc.getObject(objName) + + if not obj: + continue + + return obj + + return None + + +def truncateSubAtFirst(sub, target): + # target=part1 & sub=asm.part1.link1.part1.obj -> asm.part1. + names = sub.split(".") + sub = "" + for name in names: + sub = sub + name + "." + if name == target: + break + + return sub + + +def truncateSubAtLast(sub, target): + # target=part1 & sub=asm.part1.link1.part1.obj -> asm.part1.link1.part1. + names = sub.split(".") + sub = "" + target_indices = [i for i, name in enumerate(names) if name == target] + + if target_indices: + last_index = target_indices[-1] + for i, name in enumerate(names): + sub += name + "." + if i == last_index: + break + + return sub + + +def swapElNameInSubname(sub_name, new_elName): + # turns assembly.box.edge1 into assembly.box.new_elName + names = sub_name.split(".") + + # Replace the last element + names[-1] = new_elName + + # Join the names back together + modified_sub = ".".join(names) + + return modified_sub + + +def addVertexToReference(ref, vertex_name): + # Turns [obj, ['box.face1']] and 'vertex1' into [obj, ['box.face1', 'box.vertex1']] + if len(ref) == 2: + subs = ref[1] + if len(subs) > 0: + sub_name = subs[0] + vertex_full_sub = swapElNameInSubname(sub_name, vertex_name) + if len(subs) == 2: # Update the vertex sub + subs[1] = vertex_full_sub + else: + subs.append(vertex_full_sub) + + ref = [ref[0], subs] + + return ref