From 60b902fc7eef5fc556c9850093e7a0feecd15ad9 Mon Sep 17 00:00:00 2001 From: PaddleStroke Date: Thu, 4 Apr 2024 17:03:49 +0200 Subject: [PATCH] Assembly: Adds limit and RackPinion/Screw/Gears --- src/Mod/Assembly/App/AssemblyObject.cpp | 161 +++++- src/Mod/Assembly/App/AssemblyObject.h | 10 +- src/Mod/Assembly/CommandCreateJoint.py | 94 +++- .../panels/TaskAssemblyCreateJoint.ui | 126 ++++- src/Mod/Assembly/Gui/ViewProviderAssembly.cpp | 15 +- src/Mod/Assembly/InitGui.py | 4 + src/Mod/Assembly/JointObject.py | 497 ++++++++++++------ 7 files changed, 714 insertions(+), 193 deletions(-) diff --git a/src/Mod/Assembly/App/AssemblyObject.cpp b/src/Mod/Assembly/App/AssemblyObject.cpp index 4f1d4a0d01..7fa5dc88d6 100644 --- a/src/Mod/Assembly/App/AssemblyObject.cpp +++ b/src/Mod/Assembly/App/AssemblyObject.cpp @@ -58,6 +58,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,8 @@ #include #include #include +#include +#include #include #include #include @@ -84,6 +87,25 @@ namespace PartApp = Part; using namespace Assembly; using namespace MbD; +void printPlacement(Base::Placement plc, const char* name) +{ + Base::Vector3d pos = plc.getPosition(); + Base::Vector3d axis; + double angle; + Base::Rotation rot = plc.getRotation(); + rot.getRawValue(axis, angle); + Base::Console().Warning( + "placement %s : position (%.1f, %.1f, %.1f) - axis (%.1f, %.1f, %.1f) angle %.1f\n", + name, + pos.x, + pos.y, + pos.z, + axis.x, + axis.y, + axis.z, + angle); +} + // ================================ Assembly Object ============================ PROPERTY_SOURCE(Assembly::AssemblyObject, App::Part) @@ -115,7 +137,7 @@ App::DocumentObjectExecReturn* AssemblyObject::execute() return ret; } -int AssemblyObject::solve(bool enableRedo) +int AssemblyObject::solve(bool enableRedo, bool updateJCS) { mbdAssembly = makeMbdAssembly(); objectPartMap.clear(); @@ -126,7 +148,7 @@ int AssemblyObject::solve(bool enableRedo) return -6; } - std::vector joints = getJoints(); + std::vector joints = getJoints(updateJCS); removeUnconnectedJoints(joints, groundedObjs); @@ -153,6 +175,7 @@ int AssemblyObject::solve(bool enableRedo) void AssemblyObject::preDrag(std::vector dragParts) { + Base::Console().Warning("pre drag\n"); solve(); dragMbdParts.clear(); @@ -163,8 +186,9 @@ void AssemblyObject::preDrag(std::vector dragParts) mbdAssembly->runPreDrag(); } -void AssemblyObject::doDragStep() +void AssemblyObject::doDragStep(Base::Vector3d delta) { + Base::Console().Warning("doDragStep\n"); for (auto& mbdPart : dragMbdParts) { App::DocumentObject* part = nullptr; for (auto& pair : objectPartMap) { @@ -177,7 +201,13 @@ void AssemblyObject::doDragStep() continue; } - Base::Placement plc = getPlacementFromProp(part, "Placement"); + FColDsptr pos3D, delta2; + pos3D = mbdPart->position3D; + delta2 = std::make_shared>(ListD {delta.x, delta.y, delta.z}); + mbdPart->updateMbDFromPosition3D(pos3D->plusFullColumn(delta2)); + + /*Base::Placement plc = getPlacementFromProp(part, "Placement"); + printPlacement(plc, "init plc"); Base::Vector3d pos = plc.getPosition(); mbdPart->setPosition3D(pos.x, pos.y, pos.z); @@ -187,7 +217,7 @@ void AssemblyObject::doDragStep() Base::Vector3d r0 = mat.getRow(0); Base::Vector3d r1 = mat.getRow(1); Base::Vector3d r2 = mat.getRow(2); - mbdPart->setRotationMatrix(r0.x, r0.y, r0.z, r1.x, r1.y, r1.z, r2.x, r2.y, r2.z); + mbdPart->setRotationMatrix(r0.x, r0.y, r0.z, r1.x, r1.y, r1.z, r2.x, r2.y, r2.z);*/ } auto dragPartsVec = std::make_shared>>(dragMbdParts); @@ -198,6 +228,7 @@ void AssemblyObject::doDragStep() void AssemblyObject::postDrag() { + Base::Console().Warning("post drag \n"); mbdAssembly->runPostDrag(); // Do this after last drag } @@ -614,6 +645,9 @@ void AssemblyObject::fixGroundedPart(App::DocumentObject* obj, bool AssemblyObject::isJointConnectingPartToGround(App::DocumentObject* joint, const char* propname) { + if (!isJointTypeConnecting(joint)) { + return false; + } auto* propPart = dynamic_cast(joint->getPropertyByName(propname)); if (!propPart) { @@ -661,6 +695,13 @@ bool AssemblyObject::isJointConnectingPartToGround(App::DocumentObject* joint, c return isConnected; } +bool AssemblyObject::isJointTypeConnecting(App::DocumentObject* joint) +{ + JointType jointType = getJointType(joint); + return jointType != JointType::RackPinion && jointType != JointType::Screw + && jointType != JointType::Gears && jointType != JointType::Pulleys; +} + void AssemblyObject::removeUnconnectedJoints(std::vector& joints, std::vector groundedObjs) { @@ -716,6 +757,10 @@ AssemblyObject::getConnectedParts(App::DocumentObject* part, { std::vector connectedParts; for (auto joint : joints) { + if (!isJointTypeConnecting(joint)) { + continue; + } + App::DocumentObject* obj1 = getLinkObjFromProp(joint, "Part1"); App::DocumentObject* obj2 = getLinkObjFromProp(joint, "Part2"); if (obj1 == part) { @@ -802,6 +847,22 @@ std::shared_ptr AssemblyObject::makeMbdJointOfType(App::DocumentObjec else if (type == JointType::Distance) { return makeMbdJointDistance(joint); } + else if (type == JointType::RackPinion) { + auto mbdJoint = CREATE::With(); + mbdJoint->pitchRadius = getJointDistance(joint); + return mbdJoint; + } + else if (type == JointType::Screw) { + auto mbdJoint = CREATE::With(); + mbdJoint->pitch = getJointDistance(joint); + return mbdJoint; + } + else if (type == JointType::Gears) { + auto mbdJoint = CREATE::With(); + mbdJoint->radiusI = getJointDistance(joint); + mbdJoint->radiusJ = getJointDistance2(joint); + return mbdJoint; + } return nullptr; } @@ -1009,11 +1070,70 @@ AssemblyObject::makeMbdJoint(App::DocumentObject* joint) std::string fullMarkerName1 = handleOneSideOfJoint(joint, "Object1", "Part1", "Placement1"); std::string fullMarkerName2 = handleOneSideOfJoint(joint, "Object2", "Part2", "Placement2"); + std::string jointName = joint->getFullName(); mbdJoint->setName(joint->getFullName()); mbdJoint->setMarkerI(fullMarkerName1); mbdJoint->setMarkerJ(fullMarkerName2); + // Add limits if needed. + auto* prop = dynamic_cast(joint->getPropertyByName("EnableLimits")); + if (prop && prop->getValue()) { + if (jointType == JointType::Slider || jointType == JointType::Cylindrical) { + auto* propLenMin = + dynamic_cast(joint->getPropertyByName("LengthMin")); + if (propLenMin) { + auto limit = ASMTTranslationLimit::With(); + limit->setName(joint->getFullName() + "-LimitLenMin"); + limit->setMarkerI(fullMarkerName1); + limit->setMarkerJ(fullMarkerName2); + // limit->setmotionJoint(jointName); + limit->settype("=>"); + limit->setlimit(std::to_string(propLenMin->getValue())); + limit->settol("1.0e-9"); + mbdAssembly->addLimit(limit); + } + auto* propLenMax = + dynamic_cast(joint->getPropertyByName("LengthMax")); + if (propLenMax) { + auto limit = ASMTTranslationLimit::With(); + limit->setName(joint->getFullName() + "-LimitLenMax"); + limit->setMarkerI(fullMarkerName1); + limit->setMarkerJ(fullMarkerName2); + limit->settype("=<"); + limit->setlimit(std::to_string(propLenMax->getValue())); + limit->settol("1.0e-9"); + mbdAssembly->addLimit(limit); + } + } + if (jointType == JointType::Revolute || jointType == JointType::Cylindrical) { + auto* propRotMin = + dynamic_cast(joint->getPropertyByName("AngleMin")); + if (propRotMin) { + auto limit = ASMTRotationLimit::With(); + limit->setName(joint->getFullName() + "-LimitRotMin"); + limit->setMarkerI(fullMarkerName1); + limit->setMarkerJ(fullMarkerName2); + limit->settype("=>"); + limit->setlimit(std::to_string(propRotMin->getValue()) + "*pi/180.0"); + limit->settol("1.0e-9"); + mbdAssembly->addLimit(limit); + } + auto* propRotMax = + dynamic_cast(joint->getPropertyByName("AngleMax")); + if (propRotMax) { + auto limit = ASMTRotationLimit::With(); + limit->setName(joint->getFullName() + "-LimiRotMax"); + limit->setMarkerI(fullMarkerName1); + limit->setMarkerJ(fullMarkerName2); + limit->settype("=<"); + limit->setlimit(std::to_string(propRotMax->getValue()) + "*pi/180.0"); + limit->settol("1.0e-9"); + mbdAssembly->addLimit(limit); + } + } + } + return {mbdJoint}; } @@ -1626,25 +1746,6 @@ DistanceType AssemblyObject::getDistanceType(App::DocumentObject* joint) return DistanceType::Other; } -void printPlacement(Base::Placement plc, const char* name) -{ - Base::Vector3d pos = plc.getPosition(); - Base::Vector3d axis; - double angle; - Base::Rotation rot = plc.getRotation(); - rot.getRawValue(axis, angle); - Base::Console().Warning( - "placement %s : position (%.1f, %.1f, %.1f) - axis (%.1f, %.1f, %.1f) angle %.1f\n", - name, - pos.x, - pos.y, - pos.z, - axis.x, - axis.y, - axis.z, - angle); -} - void AssemblyObject::setJointActivated(App::DocumentObject* joint, bool val) { auto* propActivated = dynamic_cast(joint->getPropertyByName("Activated")); @@ -1794,6 +1895,18 @@ double AssemblyObject::getJointDistance(App::DocumentObject* joint) return distance; } +double AssemblyObject::getJointDistance2(App::DocumentObject* joint) +{ + double distance = 0.0; + + auto* prop = dynamic_cast(joint->getPropertyByName("Distance2")); + if (prop) { + distance = prop->getValue(); + } + + return distance; +} + JointType AssemblyObject::getJointType(App::DocumentObject* joint) { JointType jointType = JointType::Fixed; diff --git a/src/Mod/Assembly/App/AssemblyObject.h b/src/Mod/Assembly/App/AssemblyObject.h index 8d0a48d8c5..15fe4f4307 100644 --- a/src/Mod/Assembly/App/AssemblyObject.h +++ b/src/Mod/Assembly/App/AssemblyObject.h @@ -66,6 +66,10 @@ enum class JointType Slider, Ball, Distance, + RackPinion, + Screw, + Gears, + Pulleys, }; enum class DistanceType @@ -138,9 +142,9 @@ public: /* Solve the assembly. It will update first the joints, solve, update placements of the parts and redraw the joints Args : enableRedo : This store initial positions to enable undo while being in an active transaction (joint creation).*/ - int solve(bool enableRedo = false); + int solve(bool enableRedo = false, bool updateJCS = true); void preDrag(std::vector dragParts); - void doDragStep(); + void doDragStep(Base::Vector3d delta); void postDrag(); void savePlacementsForUndo(); void undoSolve(); @@ -182,6 +186,7 @@ public: void fixGroundedPart(App::DocumentObject* obj, Base::Placement& plc, std::string& jointName); bool isJointConnectingPartToGround(App::DocumentObject* joint, const char* partPropName); + bool isJointTypeConnecting(App::DocumentObject* joint); void removeUnconnectedJoints(std::vector& joints, std::vector groundedObjs); @@ -234,6 +239,7 @@ public: static void setJointActivated(App::DocumentObject* joint, bool val); static bool getJointActivated(App::DocumentObject* joint); static double getJointDistance(App::DocumentObject* joint); + static double getJointDistance2(App::DocumentObject* joint); static JointType getJointType(App::DocumentObject* joint); static const char* getElementFromProp(App::DocumentObject* obj, const char* propName); static std::string getElementTypeFromProp(App::DocumentObject* obj, const char* propName); diff --git a/src/Mod/Assembly/CommandCreateJoint.py b/src/Mod/Assembly/CommandCreateJoint.py index ff83f8066f..0d23610836 100644 --- a/src/Mod/Assembly/CommandCreateJoint.py +++ b/src/Mod/Assembly/CommandCreateJoint.py @@ -232,13 +232,102 @@ class CommandCreateJointDistance: } def IsActive(self): - # return False return isCreateJointActive() def Activated(self): activateJoint(5) +class CommandCreateJointRackPinion: + def __init__(self): + pass + + def GetResources(self): + + return { + "Pixmap": "Assembly_CreateJointRackPinion", + "MenuText": QT_TRANSLATE_NOOP( + "Assembly_CreateJointRackPinion", "Create Rack and Pinion Joint" + ), + "Accel": "P", + "ToolTip": "

" + + QT_TRANSLATE_NOOP( + "Assembly_CreateJointRackPinion", + "Create a Rack and Pinion Joint: Links a part with a sliding joint with a part with a revolute joint.", + ) + + "

" + + QT_TRANSLATE_NOOP( + "Assembly_CreateJointRackPinion", + "The pitch radius defines the movement ratio between the rack and the pinion.", + ) + + "

", + "CmdType": "ForEdit", + } + + def IsActive(self): + return isCreateJointActive() + + def Activated(self): + activateJoint(6) + + +class CommandCreateJointScrew: + def __init__(self): + pass + + def GetResources(self): + + return { + "Pixmap": "Assembly_CreateJointScrew", + "MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointScrew", "Create Screw Joint"), + "Accel": "W", + "ToolTip": "

" + + QT_TRANSLATE_NOOP( + "Assembly_CreateJointScrew", + "Create a Screw Joint: Links a part with a sliding joint with a part with a revolute joint.", + ) + + "

" + + QT_TRANSLATE_NOOP( + "Assembly_CreateJointScrew", + "The pitch radius defines the movement ratio between the rotating screw and the sliding part.", + ) + + "

", + "CmdType": "ForEdit", + } + + def IsActive(self): + return isCreateJointActive() + + def Activated(self): + activateJoint(7) + + +class CommandCreateJointGears: + def __init__(self): + pass + + def GetResources(self): + + return { + "Pixmap": "Assembly_CreateJointGears", + "MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointGears", "Create Gears Joint"), + "Accel": "X", + "ToolTip": "

" + + QT_TRANSLATE_NOOP( + "Assembly_CreateJointGears", + "Create a Gears Joint: Links two rotating gears together.", + ) + + "

", + "CmdType": "ForEdit", + } + + def IsActive(self): + return isCreateJointActive() + + def Activated(self): + activateJoint(8) + + def createGroundedJoint(obj): assembly = UtilsAssembly.activeAssembly() if not assembly: @@ -330,3 +419,6 @@ if App.GuiUp: Gui.addCommand("Assembly_CreateJointSlider", CommandCreateJointSlider()) Gui.addCommand("Assembly_CreateJointBall", CommandCreateJointBall()) Gui.addCommand("Assembly_CreateJointDistance", CommandCreateJointDistance()) + Gui.addCommand("Assembly_CreateJointRackPinion", CommandCreateJointRackPinion()) + Gui.addCommand("Assembly_CreateJointScrew", CommandCreateJointScrew()) + Gui.addCommand("Assembly_CreateJointGears", CommandCreateJointGears()) diff --git a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateJoint.ui b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateJoint.ui index be41367552..a9acc7b808 100644 --- a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateJoint.ui +++ b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyCreateJoint.ui @@ -14,13 +14,13 @@ Create Joint - + - + - + @@ -44,7 +44,31 @@ - + + + + + + Radius 2 + + + + + + + + 0 + 0 + + + + mm + + + + + + @@ -68,7 +92,7 @@ - + @@ -92,7 +116,7 @@ - + @@ -112,6 +136,96 @@ + + + + Limits + + + false + + + + + + + Length min + + + + + + + + 0 + 0 + + + + mm + + + + + + + Length max + + + + + + + + 0 + 0 + + + + mm + + + + + + + Angle min + + + + + + + + 0 + 0 + + + + deg + + + + + + + Angle max + + + + + + + + 0 + 0 + + + + deg + + + diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index 5929a17440..44f100d1d3 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -412,6 +412,12 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent propPlacement->setValue(plc); } } + + // Delta drag test : + /*SbVec3f vec = viewer->getPointOnFocalPlane(cursorPos); + Base::Vector3d newPos = Base::Vector3d(vec[0], vec[1], vec[2]); + Base::Vector3d delta = newPos - prevPosition;*/ + prevPosition = newPos; ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( @@ -419,8 +425,8 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent bool solveOnMove = hGrp->GetBool("SolveOnMove", true); if (solveOnMove) { auto* assemblyPart = static_cast(getObject()); - assemblyPart->solve(); - // assemblyPart->doDragStep(); + assemblyPart->solve(/*enableRedo = */ false, /*updateJCS = */ false); + // assemblyPart->doDragStep(delta); } } return false; @@ -773,11 +779,12 @@ void ViewProviderAssembly::initMove(const SbVec2s& cursorPos, Gui::View3DInvento auto* assemblyPart = static_cast(getObject()); assemblyPart->setObjMasses(objectMasses); - /*std::vector dragParts; + std::vector dragParts; for (auto& pair : docsToMove) { dragParts.push_back(pair.first); } - assemblyPart->preDrag(dragParts);*/ + assemblyPart->solve(); + // assemblyPart->preDrag(dragParts); } } diff --git a/src/Mod/Assembly/InitGui.py b/src/Mod/Assembly/InitGui.py index 23089919eb..aa76bcc18d 100644 --- a/src/Mod/Assembly/InitGui.py +++ b/src/Mod/Assembly/InitGui.py @@ -94,6 +94,10 @@ class AssemblyWorkbench(Workbench): "Assembly_CreateJointSlider", "Assembly_CreateJointBall", "Assembly_CreateJointDistance", + "Separator", + "Assembly_CreateJointRackPinion", + "Assembly_CreateJointScrew", + "Assembly_CreateJointGears", ] self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Assembly"), cmdList) diff --git a/src/Mod/Assembly/JointObject.py b/src/Mod/Assembly/JointObject.py index f234ea9aaf..8752a72f92 100644 --- a/src/Mod/Assembly/JointObject.py +++ b/src/Mod/Assembly/JointObject.py @@ -51,6 +51,10 @@ TranslatedJointTypes = [ translate("Assembly", "Slider"), translate("Assembly", "Ball"), translate("Assembly", "Distance"), + translate("Assembly", "RackPinion"), + translate("Assembly", "Screw"), + translate("Assembly", "Gears"), + # translate("Assembly", "Pulleys"), ] JointTypes = [ @@ -60,10 +64,21 @@ JointTypes = [ "Slider", "Ball", "Distance", + "RackPinion", + "Screw", + "Gears", + # "Pulleys", ] JointUsingDistance = [ "Distance", + "RackPinion", + "Screw", + "Gears", +] + +JointUsingDistance2 = [ + "Gears", ] JointUsingOffset = [ @@ -84,6 +99,16 @@ JointUsingReverse = [ "Distance", ] +JointUsingLimitLength = [ + "Cylindrical", + "Slider", +] + +JointUsingLimitAngle = [ + "Revolute", + "Cylindrical", +] + def solveIfAllowed(assembly, storePrev=False): if assembly.Type == "Assembly" and Preferences.preferences().GetBool( @@ -131,151 +156,241 @@ class Joint: joint.JointType = JointTypes # sets the list joint.JointType = JointTypes[type_index] # set the initial value - # First Joint Connector - joint.addProperty( - "App::PropertyString", # Not PropertyLink because they don't support external objects - "Object1", - "Joint Connector 1", - QT_TRANSLATE_NOOP("App::Property", "The first object of the joint"), - ) - - joint.addProperty( - "App::PropertyLink", - "Part1", - "Joint Connector 1", - QT_TRANSLATE_NOOP("App::Property", "The first part of the joint"), - ) - - joint.addProperty( - "App::PropertyString", - "Element1", - "Joint Connector 1", - QT_TRANSLATE_NOOP("App::Property", "The selected element of the first object"), - ) - - joint.addProperty( - "App::PropertyString", - "Vertex1", - "Joint Connector 1", - QT_TRANSLATE_NOOP("App::Property", "The selected vertex of the first object"), - ) - - joint.addProperty( - "App::PropertyPlacement", - "Placement1", - "Joint Connector 1", - QT_TRANSLATE_NOOP( - "App::Property", - "This is the local coordinate system within object1 that will be used for the joint.", - ), - ) - - joint.addProperty( - "App::PropertyBool", - "Detach1", - "Joint Connector 1", - QT_TRANSLATE_NOOP( - "App::Property", - "This prevents Placement1 from recomputing, enabling custom positioning of the placement.", - ), - ) - - # Second Joint Connector - joint.addProperty( - "App::PropertyString", - "Object2", - "Joint Connector 2", - QT_TRANSLATE_NOOP("App::Property", "The second object of the joint"), - ) - - joint.addProperty( - "App::PropertyLink", - "Part2", - "Joint Connector 2", - QT_TRANSLATE_NOOP("App::Property", "The second part of the joint"), - ) - - joint.addProperty( - "App::PropertyString", - "Element2", - "Joint Connector 2", - QT_TRANSLATE_NOOP("App::Property", "The selected element of the second object"), - ) - - joint.addProperty( - "App::PropertyString", - "Vertex2", - "Joint Connector 2", - QT_TRANSLATE_NOOP("App::Property", "The selected vertex of the second object"), - ) - - joint.addProperty( - "App::PropertyPlacement", - "Placement2", - "Joint Connector 2", - QT_TRANSLATE_NOOP( - "App::Property", - "This is the local coordinate system within object2 that will be used for the joint.", - ), - ) - - joint.addProperty( - "App::PropertyBool", - "Detach2", - "Joint Connector 2", - QT_TRANSLATE_NOOP( - "App::Property", - "This prevents Placement2 from recomputing, enabling custom positioning of the placement.", - ), - ) - - joint.addProperty( - "App::PropertyFloat", - "Distance", - "Joint", - QT_TRANSLATE_NOOP( - "App::Property", - "This is the distance of the joint. It is used only by the distance joint.", - ), - ) - - joint.addProperty( - "App::PropertyFloat", - "Rotation", - "Joint", - QT_TRANSLATE_NOOP( - "App::Property", - "This is the rotation of the joint.", - ), - ) - - joint.addProperty( - "App::PropertyVector", - "Offset", - "Joint", - QT_TRANSLATE_NOOP( - "App::Property", - "This is the offset vector of the joint.", - ), - ) - - joint.addProperty( - "App::PropertyBool", - "Activated", - "Joint", - QT_TRANSLATE_NOOP( - "App::Property", - "This indicates if the joint is active.", - ), - ) - joint.Activated = True + self.createProperties(joint) self.setJointConnectors(joint, []) + def onDocumentRestored(self, joint): + self.createProperties(joint) + + def createProperties(self, joint): + # First Joint Connector + if not hasattr(joint, "Object1"): + joint.addProperty( + "App::PropertyString", # Not PropertyLink because they don't support external objects + "Object1", + "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"), + ) + + if not hasattr(joint, "Element1"): + joint.addProperty( + "App::PropertyString", + "Element1", + "Joint Connector 1", + QT_TRANSLATE_NOOP("App::Property", "The selected element of the first object"), + ) + + if not hasattr(joint, "Vertex1"): + joint.addProperty( + "App::PropertyString", + "Vertex1", + "Joint Connector 1", + QT_TRANSLATE_NOOP("App::Property", "The selected vertex of the first object"), + ) + + if not hasattr(joint, "Placement1"): + joint.addProperty( + "App::PropertyPlacement", + "Placement1", + "Joint Connector 1", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the local coordinate system within object1 that will be used for the joint.", + ), + ) + + if not hasattr(joint, "Detach1"): + joint.addProperty( + "App::PropertyBool", + "Detach1", + "Joint Connector 1", + QT_TRANSLATE_NOOP( + "App::Property", + "This prevents Placement1 from recomputing, enabling custom positioning of the placement.", + ), + ) + + # Second Joint Connector + if not hasattr(joint, "Object2"): + joint.addProperty( + "App::PropertyString", + "Object2", + "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"), + ) + + if not hasattr(joint, "Element2"): + joint.addProperty( + "App::PropertyString", + "Element2", + "Joint Connector 2", + QT_TRANSLATE_NOOP("App::Property", "The selected element of the second object"), + ) + + if not hasattr(joint, "Vertex2"): + joint.addProperty( + "App::PropertyString", + "Vertex2", + "Joint Connector 2", + QT_TRANSLATE_NOOP("App::Property", "The selected vertex of the second object"), + ) + + if not hasattr(joint, "Placement2"): + joint.addProperty( + "App::PropertyPlacement", + "Placement2", + "Joint Connector 2", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the local coordinate system within object2 that will be used for the joint.", + ), + ) + + if not hasattr(joint, "Detach2"): + joint.addProperty( + "App::PropertyBool", + "Detach2", + "Joint Connector 2", + QT_TRANSLATE_NOOP( + "App::Property", + "This prevents Placement2 from recomputing, enabling custom positioning of the placement.", + ), + ) + + if not hasattr(joint, "Distance"): + joint.addProperty( + "App::PropertyFloat", + "Distance", + "Joint", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the distance of the joint. It is used only by the distance joint and by RackPinion (pitch radius), Screw and Gears(radius1)", + ), + ) + + if not hasattr(joint, "Distance2"): + joint.addProperty( + "App::PropertyFloat", + "Distance2", + "Joint", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the second distance of the joint. It is used only by the gear joint to store the second radius.", + ), + ) + + if not hasattr(joint, "Rotation"): + joint.addProperty( + "App::PropertyFloat", + "Rotation", + "Joint", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the rotation of the joint.", + ), + ) + + if not hasattr(joint, "Offset"): + joint.addProperty( + "App::PropertyVector", + "Offset", + "Joint", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the offset vector of the joint.", + ), + ) + + if not hasattr(joint, "Activated"): + joint.addProperty( + "App::PropertyBool", + "Activated", + "Joint", + QT_TRANSLATE_NOOP( + "App::Property", + "This indicates if the joint is active.", + ), + ) + joint.Activated = True + + if not hasattr(joint, "EnableLimits"): + joint.addProperty( + "App::PropertyBool", + "EnableLimits", + "Limits", + QT_TRANSLATE_NOOP( + "App::Property", + "Is this joint using limits.", + ), + ) + joint.EnableLimits = False + + if not hasattr(joint, "LengthMin"): + joint.addProperty( + "App::PropertyFloat", + "LengthMin", + "Limits", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the minimum limit for the length between both coordinate systems (along their Z axis).", + ), + ) + + if not hasattr(joint, "LengthMax"): + joint.addProperty( + "App::PropertyFloat", + "LengthMax", + "Limits", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the maximum limit for the length between both coordinate systems (along their Z axis).", + ), + ) + + if not hasattr(joint, "AngleMin"): + joint.addProperty( + "App::PropertyFloat", + "AngleMin", + "Limits", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the minimum limit for the angle between both coordinate systems (between their X axis).", + ), + ) + + if not hasattr(joint, "AngleMax"): + joint.addProperty( + "App::PropertyFloat", + "AngleMax", + "Limits", + QT_TRANSLATE_NOOP( + "App::Property", + "This is the maximum limit for the angle between both coordinate systems (between their X axis).", + ), + ) + def dumps(self): return None def loads(self, state): + return None def getAssembly(self, joint): @@ -313,7 +428,7 @@ class Joint: else: self.updateJCSPlacements(joint) - if prop == "Distance": + if prop == "Distance" and joint.JointType == "Distance": # during loading the onchanged may be triggered before full init. if hasattr(joint, "Vertex1"): # so we check Vertex1 solveIfAllowed(self.getAssembly(joint)) @@ -723,6 +838,12 @@ class ViewProviderJoint: return ":/icons/Assembly_CreateJointBall.svg" elif self.app_obj.JointType == "Distance": return ":/icons/Assembly_CreateJointDistance.svg" + elif self.app_obj.JointType == "RackPinion": + return ":/icons/Assembly_CreateJointRackPinion.svg" + elif self.app_obj.JointType == "Screw": + return ":/icons/Assembly_CreateJointScrew.svg" + elif self.app_obj.JointType == "Gears": + return ":/icons/Assembly_CreateJointGears.svg" return ":/icons/Assembly_CreateJoint.svg" @@ -1023,7 +1144,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.activeType = "Assembly" self.doc = self.assembly.Document - self.gui_doc = Gui.getDocument(doc) + self.gui_doc = Gui.getDocument(self.doc) self.view = self.gui_doc.activeView() @@ -1046,6 +1167,11 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.form.offsetSpinbox.valueChanged.connect(self.onOffsetChanged) self.form.rotationSpinbox.valueChanged.connect(self.onRotationChanged) self.form.PushButtonReverse.clicked.connect(self.onReverseClicked) + self.form.LimitCheckbox.stateChanged.connect(self.adaptUi) + self.form.limitLenMinSpinbox.valueChanged.connect(self.onLimitLenMinChanged) + self.form.limitLenMaxSpinbox.valueChanged.connect(self.onLimitLenMaxChanged) + self.form.limitRotMinSpinbox.valueChanged.connect(self.onLimitRotMinChanged) + self.form.limitRotMaxSpinbox.valueChanged.connect(self.onLimitRotMaxChanged) if jointObj: Gui.Selection.clearSelection() @@ -1073,10 +1199,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.visibilityBackup = False self.handleInitialSelection() - self.toggleDistanceVisibility() - self.toggleOffsetVisibility() - self.toggleRotationVisibility() - self.toggleReverseVisibility() + self.adaptUi() self.setJointsPickableState(False) @@ -1211,10 +1334,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): def onJointTypeChanged(self, index): self.joint.Proxy.setJointType(self.joint, JointTypes[self.form.jointType.currentIndex()]) - self.toggleDistanceVisibility() - self.toggleOffsetVisibility() - self.toggleRotationVisibility() - self.toggleReverseVisibility() + self.adaptUi() def onDistanceChanged(self, quantity): self.joint.Distance = self.form.distanceSpinbox.property("rawValue") @@ -1225,39 +1345,98 @@ class TaskAssemblyCreateJoint(QtCore.QObject): def onRotationChanged(self, quantity): self.joint.Rotation = self.form.rotationSpinbox.property("rawValue") + def onLimitLenMinChanged(self, quantity): + self.joint.LengthMin = self.form.limitLenMinSpinbox.property("rawValue") + + def onLimitLenMaxChanged(self, quantity): + self.joint.LengthMax = self.form.limitLenMaxSpinbox.property("rawValue") + + def onLimitRotMinChanged(self, quantity): + self.joint.AngleMin = self.form.limitRotMinSpinbox.property("rawValue") + + def onLimitRotMaxChanged(self, quantity): + self.joint.AngleMax = self.form.limitRotMaxSpinbox.property("rawValue") + def onReverseClicked(self): self.joint.Proxy.flipOnePart(self.joint) - def toggleDistanceVisibility(self): - if JointTypes[self.form.jointType.currentIndex()] in JointUsingDistance: + def adaptUi(self): + jType = JointTypes[self.form.jointType.currentIndex()] + + if jType in JointUsingDistance: self.form.distanceLabel.show() self.form.distanceSpinbox.show() + if jType == "Distance": + self.form.distanceLabel.setText("Distance") + elif jType == "Gears": + self.form.distanceLabel.setText("Radius 1") + else: + self.form.distanceLabel.setText("Pitch radius") else: self.form.distanceLabel.hide() self.form.distanceSpinbox.hide() - def toggleOffsetVisibility(self): - if JointTypes[self.form.jointType.currentIndex()] in JointUsingOffset: + if jType in JointUsingDistance2: + self.form.distanceLabel2.show() + self.form.distanceSpinbox2.show() + else: + self.form.distanceLabel2.hide() + self.form.distanceSpinbox2.hide() + + if jType in JointUsingOffset: self.form.offsetLabel.show() self.form.offsetSpinbox.show() else: self.form.offsetLabel.hide() self.form.offsetSpinbox.hide() - def toggleRotationVisibility(self): - if JointTypes[self.form.jointType.currentIndex()] in JointUsingRotation: + if jType in JointUsingRotation: self.form.rotationLabel.show() self.form.rotationSpinbox.show() else: self.form.rotationLabel.hide() self.form.rotationSpinbox.hide() - def toggleReverseVisibility(self): - if JointTypes[self.form.jointType.currentIndex()] in JointUsingReverse: + if jType in JointUsingReverse: self.form.PushButtonReverse.show() else: self.form.PushButtonReverse.hide() + needLengthLimits = jType in JointUsingLimitLength + needAngleLimits = jType in JointUsingLimitAngle + + showLimits = False + if needLengthLimits or needAngleLimits: + self.form.LimitCheckbox.show() + showLimits = True + else: + self.form.LimitCheckbox.hide() + + showLimits = showLimits and self.form.LimitCheckbox.isChecked() + self.joint.EnableLimits = showLimits + + if needLengthLimits and showLimits: + self.form.limitLenMinSpinboxLabel.show() + self.form.limitLenMaxSpinboxLabel.show() + self.form.limitLenMinSpinbox.show() + self.form.limitLenMaxSpinbox.show() + else: + self.form.limitLenMinSpinboxLabel.hide() + self.form.limitLenMaxSpinboxLabel.hide() + self.form.limitLenMinSpinbox.hide() + self.form.limitLenMaxSpinbox.hide() + + if needAngleLimits and showLimits: + self.form.limitRotMinSpinboxLabel.show() + self.form.limitRotMaxSpinboxLabel.show() + self.form.limitRotMinSpinbox.show() + self.form.limitRotMaxSpinbox.show() + else: + self.form.limitRotMinSpinboxLabel.hide() + self.form.limitRotMaxSpinboxLabel.hide() + self.form.limitRotMinSpinbox.hide() + self.form.limitRotMaxSpinbox.hide() + def updateTaskboxFromJoint(self): self.current_selection = [] self.preselection_dict = None @@ -1297,6 +1476,12 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.form.offsetSpinbox.setProperty("rawValue", self.joint.Offset.z) self.form.rotationSpinbox.setProperty("rawValue", self.joint.Rotation) + self.form.LimitCheckbox.setChecked(self.joint.EnableLimits) + self.form.limitLenMinSpinbox.setProperty("rawValue", self.joint.LengthMin) + self.form.limitLenMaxSpinbox.setProperty("rawValue", self.joint.LengthMax) + self.form.limitRotMinSpinbox.setProperty("rawValue", self.joint.AngleMin) + self.form.limitRotMaxSpinbox.setProperty("rawValue", self.joint.AngleMax) + self.form.jointType.setCurrentIndex(JointTypes.index(self.joint.JointType)) self.updateJointList()