Assembly: Add 'Angle', 'Perpendicular' and 'Parallel' joints.

This commit is contained in:
PaddleStroke
2024-05-14 16:18:56 +02:00
committed by Chris Hennes
parent 751aed1813
commit 5fb3589f26
10 changed files with 935 additions and 28 deletions

View File

@@ -58,12 +58,15 @@
#include <OndselSolver/ASMTMarker.h>
#include <OndselSolver/ASMTPart.h>
#include <OndselSolver/ASMTJoint.h>
#include <OndselSolver/ASMTAngleJoint.h>
#include <OndselSolver/ASMTFixedJoint.h>
#include <OndselSolver/ASMTGearJoint.h>
#include <OndselSolver/ASMTRevoluteJoint.h>
#include <OndselSolver/ASMTCylindricalJoint.h>
#include <OndselSolver/ASMTTranslationalJoint.h>
#include <OndselSolver/ASMTSphericalJoint.h>
#include <OndselSolver/ASMTParallelAxesJoint.h>
#include <OndselSolver/ASMTPerpendicularJoint.h>
#include <OndselSolver/ASMTPointInPlaneJoint.h>
#include <OndselSolver/ASMTPointInLineJoint.h>
#include <OndselSolver/ASMTLineInPlaneJoint.h>
@@ -846,6 +849,23 @@ std::shared_ptr<ASMTJoint> AssemblyObject::makeMbdJointOfType(App::DocumentObjec
else if (type == JointType::Distance) {
return makeMbdJointDistance(joint);
}
else if (type == JointType::Parallel) {
return CREATE<ASMTParallelAxesJoint>::With();
}
else if (type == JointType::Perpendicular) {
return CREATE<ASMTPerpendicularJoint>::With();
}
else if (type == JointType::Angle) {
double angle = fabs(Base::toRadians(getJointDistance(joint)));
if (fmod(angle, 2 * M_PI) < Precision::Confusion()) {
return CREATE<ASMTParallelAxesJoint>::With();
}
else {
auto mbdJoint = CREATE<ASMTAngleJoint>::With();
mbdJoint->theIzJz = angle;
return mbdJoint;
}
}
else if (type == JointType::RackPinion) {
auto mbdJoint = CREATE<ASMTRackPinionJoint>::With();
mbdJoint->pitchRadius = getJointDistance(joint);

View File

@@ -66,6 +66,9 @@ enum class JointType
Slider,
Ball,
Distance,
Parallel,
Perpendicular,
Angle,
RackPinion,
Screw,
Gears,

View File

@@ -238,6 +238,86 @@ class CommandCreateJointDistance:
activateJoint(5)
class CommandCreateJointParallel:
def __init__(self):
pass
def GetResources(self):
return {
"Pixmap": "Assembly_CreateJointParallel",
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointParallel", "Create Parallel Joint"),
"Accel": "N",
"ToolTip": "<p>"
+ QT_TRANSLATE_NOOP(
"Assembly_CreateJointParallel",
"Create an Parallel Joint: Make the Z axis of selected coordinate systems parallel.",
)
+ "</p>",
"CmdType": "ForEdit",
}
def IsActive(self):
return isCreateJointActive()
def Activated(self):
activateJoint(6)
class CommandCreateJointPerpendicular:
def __init__(self):
pass
def GetResources(self):
return {
"Pixmap": "Assembly_CreateJointPerpendicular",
"MenuText": QT_TRANSLATE_NOOP(
"Assembly_CreateJointPerpendicular", "Create Perpendicular Joint"
),
"Accel": "M",
"ToolTip": "<p>"
+ QT_TRANSLATE_NOOP(
"Assembly_CreateJointPerpendicular",
"Create an Perpendicular Joint: Make the Z axis of selected coordinate systems perpendicular.",
)
+ "</p>",
"CmdType": "ForEdit",
}
def IsActive(self):
return isCreateJointActive()
def Activated(self):
activateJoint(7)
class CommandCreateJointAngle:
def __init__(self):
pass
def GetResources(self):
return {
"Pixmap": "Assembly_CreateJointAngle",
"MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointAngle", "Create Angle Joint"),
"Accel": "X",
"ToolTip": "<p>"
+ QT_TRANSLATE_NOOP(
"Assembly_CreateJointAngle",
"Create an Angle Joint: Fix the angle between the Z axis of selected coordinate systems.",
)
+ "</p>",
"CmdType": "ForEdit",
}
def IsActive(self):
return isCreateJointActive()
def Activated(self):
activateJoint(8)
class CommandCreateJointRackPinion:
def __init__(self):
pass
@@ -268,7 +348,7 @@ class CommandCreateJointRackPinion:
return isCreateJointActive()
def Activated(self):
activateJoint(6)
activateJoint(9)
class CommandCreateJointScrew:
@@ -299,7 +379,7 @@ class CommandCreateJointScrew:
return isCreateJointActive()
def Activated(self):
activateJoint(7)
activateJoint(10)
class CommandCreateJointGears:
@@ -330,7 +410,7 @@ class CommandCreateJointGears:
return isCreateJointActive()
def Activated(self):
activateJoint(8)
activateJoint(11)
class CommandCreateJointBelt:
@@ -361,7 +441,7 @@ class CommandCreateJointBelt:
return isCreateJointActive()
def Activated(self):
activateJoint(9)
activateJoint(12)
class CommandGroupGearBelt:
@@ -483,6 +563,9 @@ if App.GuiUp:
Gui.addCommand("Assembly_CreateJointSlider", CommandCreateJointSlider())
Gui.addCommand("Assembly_CreateJointBall", CommandCreateJointBall())
Gui.addCommand("Assembly_CreateJointDistance", CommandCreateJointDistance())
Gui.addCommand("Assembly_CreateJointParallel", CommandCreateJointParallel())
Gui.addCommand("Assembly_CreateJointPerpendicular", CommandCreateJointPerpendicular())
Gui.addCommand("Assembly_CreateJointAngle", CommandCreateJointAngle())
Gui.addCommand("Assembly_CreateJointRackPinion", CommandCreateJointRackPinion())
Gui.addCommand("Assembly_CreateJointScrew", CommandCreateJointScrew())
Gui.addCommand("Assembly_CreateJointGears", CommandCreateJointGears())

View File

@@ -3,9 +3,12 @@
<file>icons/Assembly_InsertLink.svg</file>
<file>icons/preferences-assembly.svg</file>
<file>icons/Assembly_ToggleGrounded.svg</file>
<file>icons/Assembly_CreateJointAngle.svg</file>
<file>icons/Assembly_CreateJointBall.svg</file>
<file>icons/Assembly_CreateJointCylindrical.svg</file>
<file>icons/Assembly_CreateJointFixed.svg</file>
<file>icons/Assembly_CreateJointParallel.svg</file>
<file>icons/Assembly_CreateJointPerpendicular.svg</file>
<file>icons/Assembly_CreateJointPlanar.svg</file>
<file>icons/Assembly_CreateJointRevolute.svg</file>
<file>icons/Assembly_CreateJointSlider.svg</file>

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@@ -93,7 +93,11 @@ class AssemblyWorkbench(Workbench):
"Assembly_CreateJointCylindrical",
"Assembly_CreateJointSlider",
"Assembly_CreateJointBall",
"Separator",
"Assembly_CreateJointDistance",
"Assembly_CreateJointParallel",
"Assembly_CreateJointPerpendicular",
"Assembly_CreateJointAngle",
"Separator",
"Assembly_CreateJointRackPinion",
"Assembly_CreateJointScrew",

View File

@@ -51,6 +51,9 @@ TranslatedJointTypes = [
translate("Assembly", "Slider"),
translate("Assembly", "Ball"),
translate("Assembly", "Distance"),
translate("Assembly", "Parallel"),
translate("Assembly", "Perpendicular"),
translate("Assembly", "Angle"),
translate("Assembly", "RackPinion"),
translate("Assembly", "Screw"),
translate("Assembly", "Gears"),
@@ -64,6 +67,9 @@ JointTypes = [
"Slider",
"Ball",
"Distance",
"Parallel",
"Perpendicular",
"Angle",
"RackPinion",
"Screw",
"Gears",
@@ -72,6 +78,7 @@ JointTypes = [
JointUsingDistance = [
"Distance",
"Angle",
"RackPinion",
"Screw",
"Gears",
@@ -106,6 +113,7 @@ JointUsingReverse = [
"Cylindrical",
"Slider",
"Distance",
"Parallel",
]
JointUsingLimitLength = [
@@ -118,6 +126,19 @@ JointUsingLimitAngle = [
"Cylindrical",
]
JointUsingPreSolve = [
"Fixed",
"Revolute",
"Cylindrical",
"Slider",
"Ball",
]
JointParallelForbidden = [
"Angle",
"Perpendicular",
]
def solveIfAllowed(assembly, storePrev=False):
if assembly.Type == "Assembly" and Preferences.preferences().GetBool(
@@ -399,7 +420,6 @@ class Joint:
return None
def loads(self, state):
return None
def getAssembly(self, joint):
@@ -425,14 +445,7 @@ class Joint:
if obj1 is None or obj2 is None:
return
presolved = self.preSolve(
joint,
obj1,
joint.Part1,
obj2,
joint.Part2,
False,
)
presolved = self.preSolve(joint, False)
isAssembly = self.getAssembly(joint).Type == "Assembly"
if isAssembly and not presolved:
@@ -440,10 +453,13 @@ class Joint:
else:
self.updateJCSPlacements(joint)
if prop == "Distance" and joint.JointType == "Distance":
if prop == "Distance" and (joint.JointType == "Distance" or joint.JointType == "Angle"):
# during loading the onchanged may be triggered before full init.
if hasattr(joint, "Vertex1"): # so we check Vertex1
solveIfAllowed(self.getAssembly(joint))
if joint.Part1 and joint.Part2:
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"""
@@ -479,14 +495,10 @@ class Joint:
joint.Placement2 = self.findPlacement(
joint, joint.Object2, joint.Part2, joint.Element2, joint.Vertex2, True
)
if joint.JointType != "Distance":
self.preSolve(
joint,
current_selection[0]["object"],
joint.Part1,
current_selection[1]["object"],
joint.Part2,
)
if joint.JointType in JointUsingPreSolve:
self.preSolve(joint)
elif joint.JointType in JointParallelForbidden:
self.preventParallel(joint)
if isAssembly:
solveIfAllowed(assembly, True)
@@ -569,7 +581,7 @@ class Joint:
solveIfAllowed(self.getAssembly(joint))
def preSolve(self, joint, obj1, part1, obj2, part2, savePlc=True):
def preSolve(self, joint, savePlc=True):
# The goal of this is to put the part in the correct position to avoid wrong placement by the solve.
# we actually don't want to match perfectly the JCS, it is best to match them
@@ -624,12 +636,49 @@ class Joint:
joint.Placement1 = joint.Placement1 # Make sure plc1 is redrawn
def preventParallel(self, joint):
# Angle and perpendicular joints in the solver cannot handle the situation where both JCS are Parallel
parallel = self.areJcsZParallel(joint)
if not parallel:
return
assembly = self.getAssembly(joint)
isAssembly = assembly.Type == "Assembly"
if isAssembly:
part1ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Part1")
part2ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Part2")
else:
part1ConnectedByJoint = False
part2ConnectedByJoint = True
if part2ConnectedByJoint:
self.partMovedByPresolved = joint.Part2
self.presolveBackupPlc = joint.Part2.Placement
joint.Part2.Placement = UtilsAssembly.applyRotationToPlacementAlongAxis(
joint.Part2.Placement, 10, App.Vector(1, 0, 0)
)
elif part1ConnectedByJoint:
self.partMovedByPresolved = joint.Part1
self.presolveBackupPlc = joint.Part1.Placement
joint.Part1.Placement = UtilsAssembly.applyRotationToPlacementAlongAxis(
joint.Part1.Placement, 10, App.Vector(1, 0, 0)
)
def areJcsSameDir(self, joint):
globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc(joint.Placement1, joint.Object1, joint.Part1)
globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc(joint.Placement2, joint.Object2, joint.Part2)
return UtilsAssembly.arePlacementSameDir(globalJcsPlc1, globalJcsPlc2)
def areJcsZParallel(self, joint):
globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc(joint.Placement1, joint.Object1, joint.Part1)
globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc(joint.Placement2, joint.Object2, joint.Part2)
return UtilsAssembly.arePlacementZParallel(globalJcsPlc1, globalJcsPlc2)
class ViewProviderJoint:
def __init__(self, vobj):
@@ -848,6 +897,12 @@ class ViewProviderJoint:
return ":/icons/Assembly_CreateJointBall.svg"
elif self.app_obj.JointType == "Distance":
return ":/icons/Assembly_CreateJointDistance.svg"
elif self.app_obj.JointType == "Parallel":
return ":/icons/Assembly_CreateJointParallel.svg"
elif self.app_obj.JointType == "Perpendicular":
return ":/icons/Assembly_CreateJointPerpendicular.svg"
elif self.app_obj.JointType == "Angle":
return ":/icons/Assembly_CreateJointAngle.svg"
elif self.app_obj.JointType == "RackPinion":
return ":/icons/Assembly_CreateJointRackPinion.svg"
elif self.app_obj.JointType == "Screw":
@@ -1179,6 +1234,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
self.form.jointType.addItems(TranslatedJointTypes)
self.form.jointType.setCurrentIndex(jointTypeIndex)
self.jType = JointTypes[self.form.jointType.currentIndex()]
self.form.jointType.currentIndexChanged.connect(self.onJointTypeChanged)
self.form.distanceSpinbox.valueChanged.connect(self.onDistanceChanged)
@@ -1192,8 +1248,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
self.form.limitRotMinSpinbox.valueChanged.connect(self.onLimitRotMinChanged)
self.form.limitRotMaxSpinbox.valueChanged.connect(self.onLimitRotMaxChanged)
jType = JointTypes[self.form.jointType.currentIndex()]
self.form.reverseRotCheckbox.setChecked(jType == "Gears")
self.form.reverseRotCheckbox.setChecked(self.jType == "Gears")
self.form.reverseRotCheckbox.stateChanged.connect(self.reverseRotToggled)
if jointObj:
@@ -1355,7 +1410,8 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
ViewProviderJoint(self.joint.ViewObject)
def onJointTypeChanged(self, index):
self.joint.Proxy.setJointType(self.joint, JointTypes[self.form.jointType.currentIndex()])
self.jType = JointTypes[self.form.jointType.currentIndex()]
self.joint.Proxy.setJointType(self.joint, self.jType)
self.adaptUi()
def onDistanceChanged(self, quantity):
@@ -1392,17 +1448,25 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
self.form.jointType.setCurrentIndex(9)
def adaptUi(self):
jType = JointTypes[self.form.jointType.currentIndex()]
jType = self.jType
if jType in JointUsingDistance:
self.form.distanceLabel.show()
self.form.distanceSpinbox.show()
if jType == "Distance":
self.form.distanceLabel.setText("Distance")
elif jType == "Angle":
self.form.distanceLabel.setText("Angle")
elif jType == "Gears" or jType == "Belt":
self.form.distanceLabel.setText("Radius 1")
else:
self.form.distanceLabel.setText("Pitch radius")
if jType == "Angle":
self.form.distanceSpinbox.setProperty("unit", "deg")
else:
self.form.distanceSpinbox.setProperty("unit", "mm")
else:
self.form.distanceLabel.hide()
self.form.distanceSpinbox.hide()

View File

@@ -850,6 +850,12 @@ def arePlacementSameDir(plc1, plc2):
return zAxis1.dot(zAxis2) > 0
def arePlacementZParallel(plc1, plc2):
zAxis1 = plc1.Rotation.multVec(App.Vector(0, 0, 1))
zAxis2 = plc2.Rotation.multVec(App.Vector(0, 0, 1))
return zAxis1.cross(zAxis2).Length < 1e-06
"""
So here we want to find a placement that corresponds to a local coordinate system that would be placed at the selected vertex.
- obj is usually a App::Link to a PartDesign::Body, or primitive, fasteners. But can also be directly the object.1