Assembly: Add 'Angle', 'Perpendicular' and 'Parallel' joints.
This commit is contained in:
committed by
Chris Hennes
parent
751aed1813
commit
5fb3589f26
@@ -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);
|
||||
|
||||
@@ -66,6 +66,9 @@ enum class JointType
|
||||
Slider,
|
||||
Ball,
|
||||
Distance,
|
||||
Parallel,
|
||||
Perpendicular,
|
||||
Angle,
|
||||
RackPinion,
|
||||
Screw,
|
||||
Gears,
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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 |
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user