diff --git a/src/Mod/Assembly/App/AssemblyObject.cpp b/src/Mod/Assembly/App/AssemblyObject.cpp
index 9c11651638..a78bfb6267 100644
--- a/src/Mod/Assembly/App/AssemblyObject.cpp
+++ b/src/Mod/Assembly/App/AssemblyObject.cpp
@@ -845,6 +845,15 @@ std::shared_ptr AssemblyObject::makeMbdJointOfType(App::DocumentObjec
return mbdJoint;
}
else if (type == JointType::Screw) {
+ int slidingIndex = slidingPartIndex(joint);
+ if (slidingIndex == 0) { // invalid this joint needs a slider
+ return nullptr;
+ }
+
+ if (slidingIndex != 1) {
+ swapJCS(joint); // make sure that sliding is first.
+ }
+
auto mbdJoint = CREATE::With();
mbdJoint->pitch = getJointDistance(joint);
return mbdJoint;
@@ -1066,13 +1075,21 @@ AssemblyObject::makeMbdJoint(App::DocumentObject* joint)
return {};
}
- std::string fullMarkerName1 = handleOneSideOfJoint(joint, "Object1", "Part1", "Placement1");
- std::string fullMarkerName2 = handleOneSideOfJoint(joint, "Object2", "Part2", "Placement2");
- std::string jointName = joint->getFullName();
+ std::string fullMarkerNameI, fullMarkerNameJ;
+ if (jointType == JointType::RackPinion) {
+ getRackPinionMarkers(joint, fullMarkerNameI, fullMarkerNameJ);
+ }
+ else {
+ fullMarkerNameI = handleOneSideOfJoint(joint, "Object1", "Part1", "Placement1");
+ fullMarkerNameJ = handleOneSideOfJoint(joint, "Object2", "Part2", "Placement2");
+ }
+ if (fullMarkerNameI == "" || fullMarkerNameJ == "") {
+ return {};
+ }
mbdJoint->setName(joint->getFullName());
- mbdJoint->setMarkerI(fullMarkerName1);
- mbdJoint->setMarkerJ(fullMarkerName2);
+ mbdJoint->setMarkerI(fullMarkerNameI);
+ mbdJoint->setMarkerJ(fullMarkerNameJ);
// Add limits if needed.
auto* prop = dynamic_cast(joint->getPropertyByName("EnableLimits"));
@@ -1083,9 +1100,8 @@ AssemblyObject::makeMbdJoint(App::DocumentObject* joint)
if (propLenMin) {
auto limit = ASMTTranslationLimit::With();
limit->setName(joint->getFullName() + "-LimitLenMin");
- limit->setMarkerI(fullMarkerName1);
- limit->setMarkerJ(fullMarkerName2);
- // limit->setmotionJoint(jointName);
+ limit->setMarkerI(fullMarkerNameI);
+ limit->setMarkerJ(fullMarkerNameJ);
limit->settype("=>");
limit->setlimit(std::to_string(propLenMin->getValue()));
limit->settol("1.0e-9");
@@ -1096,8 +1112,8 @@ AssemblyObject::makeMbdJoint(App::DocumentObject* joint)
if (propLenMax) {
auto limit = ASMTTranslationLimit::With();
limit->setName(joint->getFullName() + "-LimitLenMax");
- limit->setMarkerI(fullMarkerName1);
- limit->setMarkerJ(fullMarkerName2);
+ limit->setMarkerI(fullMarkerNameI);
+ limit->setMarkerJ(fullMarkerNameJ);
limit->settype("=<");
limit->setlimit(std::to_string(propLenMax->getValue()));
limit->settol("1.0e-9");
@@ -1110,8 +1126,8 @@ AssemblyObject::makeMbdJoint(App::DocumentObject* joint)
if (propRotMin) {
auto limit = ASMTRotationLimit::With();
limit->setName(joint->getFullName() + "-LimitRotMin");
- limit->setMarkerI(fullMarkerName1);
- limit->setMarkerJ(fullMarkerName2);
+ limit->setMarkerI(fullMarkerNameI);
+ limit->setMarkerJ(fullMarkerNameJ);
limit->settype("=>");
limit->setlimit(std::to_string(propRotMin->getValue()) + "*pi/180.0");
limit->settol("1.0e-9");
@@ -1122,8 +1138,8 @@ AssemblyObject::makeMbdJoint(App::DocumentObject* joint)
if (propRotMax) {
auto limit = ASMTRotationLimit::With();
limit->setName(joint->getFullName() + "-LimiRotMax");
- limit->setMarkerI(fullMarkerName1);
- limit->setMarkerJ(fullMarkerName2);
+ limit->setMarkerI(fullMarkerNameI);
+ limit->setMarkerJ(fullMarkerNameJ);
limit->settype("=<");
limit->setlimit(std::to_string(propRotMax->getValue()) + "*pi/180.0");
limit->settol("1.0e-9");
@@ -1144,9 +1160,10 @@ std::string AssemblyObject::handleOneSideOfJoint(App::DocumentObject* joint,
App::DocumentObject* obj = getObjFromNameProp(joint, propObjName, propPartName);
if (!part) {
- std::string msg = std::string("The property ") + propPartName + " of Joint "
- + joint->getFullName() + " is empty.\n";
- THROWM(Base::ValueError, msg);
+ Base::Console().Warning("The property %s or Joint %s is empty.",
+ propPartName,
+ joint->getFullName());
+ return "";
}
std::shared_ptr mbdPart = getMbDPart(part);
@@ -1172,6 +1189,128 @@ std::string AssemblyObject::handleOneSideOfJoint(App::DocumentObject* joint,
return "/OndselAssembly/" + mbdPart->name + "/" + markerName;
}
+void AssemblyObject::getRackPinionMarkers(App::DocumentObject* joint,
+ std::string& markerNameI,
+ std::string& markerNameJ)
+{
+ // ASMT rack pinion joint must get the rack as I and pinion as J.
+ // - rack marker has to have Z axis parallel to pinion Z axis.
+ // - rack marker has to have X axis parallel to the sliding axis.
+ // The user will have selected the sliding marker so we need to transform it.
+ // And we need to detect which marker is the rack.
+
+ int slidingIndex = slidingPartIndex(joint);
+ if (slidingIndex == 0) {
+ return;
+ }
+
+ if (slidingIndex != 1) {
+ swapJCS(joint); // make sure that rack is first.
+ }
+
+ App::DocumentObject* part1 = getLinkObjFromProp(joint, "Part1");
+ App::DocumentObject* obj1 = getObjFromNameProp(joint, "Object1", "Part1");
+ Base::Placement plc1 = getPlacementFromProp(joint, "Placement1");
+
+ App::DocumentObject* part2 = getLinkObjFromProp(joint, "Part2");
+ App::DocumentObject* obj2 = getObjFromNameProp(joint, "Object2", "Part2");
+ Base::Placement plc2 = getPlacementFromProp(joint, "Placement2");
+
+ // For the pinion nothing special needed :
+ markerNameJ = handleOneSideOfJoint(joint, "Object2", "Part2", "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);
+ plc2 = pinion_global_plc * plc2;
+ Base::Placement rack_global_plc = getGlobalPlacement(obj1, part1);
+ 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
+ // slider axis.
+ Base::Rotation rot = plc2.getRotation();
+ // the yaw of rot has to be the same as plc1
+ Base::Vector3d currentZAxis = rot.multVec(Base::Vector3d(0, 0, 1));
+ Base::Vector3d currentXAxis = rot.multVec(Base::Vector3d(1, 0, 0));
+ Base::Vector3d targetXAxis = plc1.getRotation().multVec(Base::Vector3d(0, 0, 1));
+
+ // Calculate the angle between the current X axis and the target X axis
+ double yawAdjustment = currentXAxis.GetAngle(targetXAxis);
+
+ // Determine the direction of the yaw adjustment using cross product
+ Base::Vector3d crossProd = currentXAxis.Cross(targetXAxis);
+ if (currentZAxis * crossProd < 0) { // If cross product is in opposite direction to Z axis
+ yawAdjustment = -yawAdjustment;
+ }
+
+ // Create a yaw rotation around the Z axis
+ Base::Rotation yawRotation(currentZAxis, yawAdjustment);
+
+ // Combine the initial rotation with the yaw adjustment
+ Base::Rotation adjustedRotation = rot * yawRotation;
+ plc1.setRotation(adjustedRotation);
+
+ // Then end of processing similar to handleOneSideOfJoint :
+
+ if (obj1->getNameInDocument() != part1->getNameInDocument()) {
+ plc1 = rack_global_plc * plc1;
+
+ Base::Placement part_global_plc = getGlobalPlacement(part1);
+ plc1 = part_global_plc.inverse() * plc1;
+ }
+
+ std::string markerName = joint->getFullName();
+ auto mbdMarker = makeMbdMarker(markerName, plc1);
+ std::shared_ptr mbdPart = getMbDPart(part1);
+ mbdPart->addMarker(mbdMarker);
+
+ markerNameI = "/OndselAssembly/" + mbdPart->name + "/" + markerName;
+}
+
+int AssemblyObject::slidingPartIndex(App::DocumentObject* joint)
+{
+ App::DocumentObject* part1 = getLinkObjFromProp(joint, "Part1");
+ App::DocumentObject* obj1 = getObjFromNameProp(joint, "Object1", "Part1");
+ Base::Placement plc1 = getPlacementFromProp(joint, "Placement1");
+
+ App::DocumentObject* part2 = getLinkObjFromProp(joint, "Part2");
+ App::DocumentObject* obj2 = getObjFromNameProp(joint, "Object2", "Part2");
+ Base::Placement plc2 = getPlacementFromProp(joint, "Placement2");
+
+ int slidingFound = 0;
+ for (auto* jt : getJoints(false, false)) {
+ if (getJointType(jt) == JointType::Slider) {
+ App::DocumentObject* jpart1 = getLinkObjFromProp(jt, "Part1");
+ App::DocumentObject* jpart2 = getLinkObjFromProp(jt, "Part2");
+ int found = 0;
+ Base::Placement plcjt, plci;
+ if (jpart1 == part1 || jpart1 == part2) {
+ found = (jpart1 == part1) ? 1 : 2;
+ plci = (jpart1 == part1) ? plc1 : plc2;
+ plcjt = getPlacementFromProp(jt, "Placement1");
+ }
+ else if (jpart2 == part1 || jpart2 == part2) {
+ found = (jpart2 == part1) ? 1 : 2;
+ plci = (jpart2 == part1) ? plc1 : plc2;
+ plcjt = getPlacementFromProp(jt, "Placement2");
+ }
+
+ if (found != 0) {
+ // check the placements plcjt and (jcs1 or jcs2 depending on found value) Z axis are
+ // colinear ie if their pitch and roll are the same.
+ double y1, p1, r1, y2, p2, r2;
+ plcjt.getRotation().getYawPitchRoll(y1, p1, r1);
+ plci.getRotation().getYawPitchRoll(y2, p2, r2);
+ if (fabs(p1 - p2) < Precision::Confusion()
+ && fabs(r1 - r2) < Precision::Confusion()) {
+ slidingFound = found;
+ }
+ }
+ }
+ }
+ return slidingFound;
+}
+
std::shared_ptr AssemblyObject::getMbDPart(App::DocumentObject* obj)
{
std::shared_ptr mbdPart;
diff --git a/src/Mod/Assembly/App/AssemblyObject.h b/src/Mod/Assembly/App/AssemblyObject.h
index f2eafafb72..7bac83394d 100644
--- a/src/Mod/Assembly/App/AssemblyObject.h
+++ b/src/Mod/Assembly/App/AssemblyObject.h
@@ -171,6 +171,10 @@ public:
const char* propObjLinkName,
const char* propPartName,
const char* propPlcName);
+ void getRackPinionMarkers(App::DocumentObject* joint,
+ std::string& markerNameI,
+ std::string& markerNameJ);
+ int slidingPartIndex(App::DocumentObject* joint);
void jointParts(std::vector joints);
JointGroup* getJointGroup();
diff --git a/src/Mod/Assembly/CommandCreateJoint.py b/src/Mod/Assembly/CommandCreateJoint.py
index 06bb359e21..240f35647e 100644
--- a/src/Mod/Assembly/CommandCreateJoint.py
+++ b/src/Mod/Assembly/CommandCreateJoint.py
@@ -258,7 +258,7 @@ class CommandCreateJointRackPinion:
+ "
"
+ QT_TRANSLATE_NOOP(
"Assembly_CreateJointRackPinion",
- "The pitch radius defines the movement ratio between the rack and the pinion.",
+ "Select the same coordinate systems as the revolute and sliding joints. The pitch radius defines the movement ratio between the rack and the pinion.",
)
+ "
",
"CmdType": "ForEdit",
@@ -289,7 +289,7 @@ class CommandCreateJointScrew:
+ ""
+ QT_TRANSLATE_NOOP(
"Assembly_CreateJointScrew",
- "The pitch radius defines the movement ratio between the rotating screw and the sliding part.",
+ "Select the same coordinate systems as the revolute and sliding joints. The pitch radius defines the movement ratio between the rotating screw and the sliding part.",
)
+ "
",
"CmdType": "ForEdit",
@@ -317,6 +317,11 @@ class CommandCreateJointGears:
"Assembly_CreateJointGears",
"Create a Gears Joint: Links two rotating gears together. They will have inverse rotation direction.",
)
+ + ""
+ + QT_TRANSLATE_NOOP(
+ "Assembly_CreateJointScrew",
+ "Select the same coordinate systems as the revolute joints.",
+ )
+ "
",
"CmdType": "ForEdit",
}
@@ -343,6 +348,11 @@ class CommandCreateJointBelt:
"Assembly_CreateJointBelt",
"Create a Belt Joint: Links two rotating objects together. They will have the same rotation direction.",
)
+ + ""
+ + QT_TRANSLATE_NOOP(
+ "Assembly_CreateJointScrew",
+ "Select the same coordinate systems as the revolute joints.",
+ )
+ "
",
"CmdType": "ForEdit",
}
@@ -363,12 +373,17 @@ class CommandGroupGearBelt:
return {
"Pixmap": "Assembly_CreateJointGears",
- "MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointGears", "Create Gear/Belt Joint"),
+ "MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointGearBelt", "Create Gear/Belt Joint"),
"ToolTip": ""
+ QT_TRANSLATE_NOOP(
- "Assembly_CreateJointGears",
+ "Assembly_CreateJointGearBelt",
"Create a Gears/Belt Joint: Links two rotating gears together.",
)
+ + "
"
+ + QT_TRANSLATE_NOOP(
+ "Assembly_CreateJointGearBelt",
+ "Select the same coordinate systems as the revolute joints.",
+ )
+ "
",
"CmdType": "ForEdit",
}
diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py
index 2cdf34cb9b..55d6e0cc3b 100644
--- a/src/Mod/Assembly/UtilsAssembly.py
+++ b/src/Mod/Assembly/UtilsAssembly.py
@@ -949,10 +949,8 @@ def findPlacement(obj, part, elt, vtx, ignoreVertex=False):
plc.Base = (vertex.X, vertex.Y, vertex.Z)
# Then we find the Rotation
- if surface.TypeId == "Part::GeomPlane":
+ if hasattr(surface, "Rotation") and surface.Rotation is not None:
plc.Rotation = App.Rotation(surface.Rotation)
- else:
- plc.Rotation = surface.Rotation
# Now plc is the placement relative to the origin determined by the object placement.
# But it does not take into account Part placements. So if the solid is in a part and