Assembly: Make pre-solve and reverse move all the downstream parts (#24193)

This commit is contained in:
PaddleStroke
2025-10-07 18:44:11 +02:00
committed by GitHub
parent c64f06c3ad
commit 6282dd48bb
3 changed files with 105 additions and 61 deletions

View File

@@ -148,5 +148,28 @@ class AssemblyObject(Part):
Args:
fileName: The name of the file where the ASMT will be exported."""
...
@constmethod
def getDownstreamParts(
self, start_part: "App.DocumentObject", joint_to_ignore: "App.DocumentObject", /
) -> list["App.DocumentObject"]:
"""
Finds all parts connected to a start_part that are not connected to ground
when a specific joint is ignored.
getDownstreamParts(start_part, joint_to_ignore) -> list
This is used to find the entire rigid group of unconstrained components that
should be moved together during a pre-solve operation or a drag.
Args:
start_part: The App.DocumentObject to begin the search from.
joint_to_ignore: The App.DocumentObject (a joint) to temporarily
suppress during the connectivity check.
Returns:
A list of App.DocumentObject instances representing the downstream parts.
"""
...
Joints: Final[list]
"""A list of all joints this assembly has."""

View File

@@ -198,3 +198,36 @@ Py::List AssemblyObjectPy::getJoints() const
return ret;
}
PyObject* AssemblyObjectPy::getDownstreamParts(PyObject* args) const
{
PyObject* pyPart;
PyObject* pyJoint;
// Parse the two arguments: a part object and a joint object
if (!PyArg_ParseTuple(args,
"O!O!",
&(App::DocumentObjectPy::Type),
&pyPart,
&(App::DocumentObjectPy::Type),
&pyJoint)) {
return nullptr;
}
auto* part = static_cast<App::DocumentObjectPy*>(pyPart)->getDocumentObjectPtr();
auto* joint = static_cast<App::DocumentObjectPy*>(pyJoint)->getDocumentObjectPtr();
// Call the C++ method
std::vector<Assembly::ObjRef> downstreamParts =
this->getAssemblyObjectPtr()->getDownstreamParts(part, joint);
// Convert the result into a Python list of DocumentObjects
Py::List ret;
for (const auto& objRef : downstreamParts) {
if (objRef.obj) {
ret.append(Py::Object(objRef.obj->getPyObject(), true));
}
}
return Py::new_reference_to(ret);
}

View File

@@ -832,46 +832,27 @@ class Joint:
return plc
def flipOnePart(self, joint):
assembly = self.getAssembly(joint)
part2 = UtilsAssembly.getMovingPart(assembly, joint.Reference2)
if part2 is not None:
part2ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Reference2")
part2Grounded = assembly.isPartGrounded(part2)
if part2ConnectedByJoint and not part2Grounded:
jcsPlc = UtilsAssembly.getJcsPlcRelativeToPart(
assembly, joint.Placement2, joint.Reference2
)
globalJcsPlc = UtilsAssembly.getJcsGlobalPlc(joint.Placement2, joint.Reference2)
jcsPlc = UtilsAssembly.flipPlacement(jcsPlc)
part2.Placement = globalJcsPlc * jcsPlc.inverse()
solveIfAllowed(self.getAssembly(joint))
return
part1 = UtilsAssembly.getMovingPart(assembly, joint.Reference1)
if part1 is not None:
part1Grounded = assembly.isPartGrounded(part1)
if not part1Grounded:
jcsPlc = UtilsAssembly.getJcsPlcRelativeToPart(
assembly, joint.Placement1, joint.Reference1
)
globalJcsPlc = UtilsAssembly.getJcsGlobalPlc(joint.Placement1, joint.Reference1)
jcsPlc = UtilsAssembly.flipPlacement(jcsPlc)
part1.Placement = globalJcsPlc * jcsPlc.inverse()
return
self.matchJCS(joint, False, 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
# in the current closest direction, ie either matched or flipped.
self.matchJCS(joint, savePlc)
sameDir = self.areJcsSameDir(joint)
def matchJCS(self, joint, savePlc=True, reverse=False):
assembly = self.getAssembly(joint)
sameDir = self.areJcsSameDir(joint)
if reverse:
sameDir = not sameDir
part1 = UtilsAssembly.getMovingPart(assembly, joint.Reference1)
part2 = UtilsAssembly.getMovingPart(assembly, joint.Reference2)
if not part1 or not part2:
return False
isAssembly = assembly.Type == "Assembly"
if isAssembly:
joint.Suppressed = True
@@ -882,47 +863,54 @@ class Joint:
part1Connected = True
part2Connected = False
if not part1Connected:
if savePlc:
self.partMovedByPresolved = part1
self.presolveBackupPlc = part1.Placement
moving_part = None
moving_part_ref = None
fixed_part_ref = None
moving_placement = None
fixed_placement = None
globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc(joint.Placement2, joint.Reference2)
jcsPlc1 = UtilsAssembly.getJcsPlcRelativeToPart(
assembly, joint.Placement1, joint.Reference1
)
if not sameDir:
jcsPlc1 = UtilsAssembly.flipPlacement(jcsPlc1)
if not part1Connected and part1:
moving_part = part1
moving_part_ref = joint.Reference1
fixed_part_ref = joint.Reference2
moving_placement = joint.Placement1
fixed_placement = joint.Placement2
elif not part2Connected and part2:
moving_part = part2
moving_part_ref = joint.Reference2
fixed_part_ref = joint.Reference1
moving_placement = joint.Placement2
fixed_placement = joint.Placement1
else:
# Both parts are constrained, or something is invalid. Nothing to pre-solve.
return False
# For link groups and sub-assemblies we have to take into account
# the parent placement (ie the linkgroup plc) as the linkgroup is not the moving part
# But instead of doing as follow, we rather enforce identity placement for linkgroups.
# parentPlc = UtilsAssembly.getParentPlacementIfNeeded(part2)
# part2.Placement = globalJcsPlc1 * jcsPlc2.inverse() * parentPlc.inverse()
parts_to_move = [moving_part]
if isAssembly:
parts_to_move = parts_to_move + assembly.getDownstreamParts(moving_part, joint)
part1.Placement = globalJcsPlc2 * jcsPlc1.inverse()
return True
if savePlc:
self.partsMovedByPresolved = {p: p.Placement for p in parts_to_move}
elif not part2Connected:
if savePlc:
self.partMovedByPresolved = part2
self.presolveBackupPlc = part2.Placement
moving_part_global_jcs = UtilsAssembly.getJcsGlobalPlc(moving_placement, moving_part_ref)
fixed_part_global_jcs = UtilsAssembly.getJcsGlobalPlc(fixed_placement, fixed_part_ref)
globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc(joint.Placement1, joint.Reference1)
jcsPlc2 = UtilsAssembly.getJcsPlcRelativeToPart(
assembly, joint.Placement2, joint.Reference2
)
if not sameDir:
jcsPlc2 = UtilsAssembly.flipPlacement(jcsPlc2)
if not sameDir:
moving_part_global_jcs = UtilsAssembly.flipPlacement(moving_part_global_jcs)
part2.Placement = globalJcsPlc1 * jcsPlc2.inverse()
return True
return False
transform_plc = fixed_part_global_jcs * moving_part_global_jcs.inverse()
for part in parts_to_move:
part.Placement = transform_plc * part.Placement
return True
def undoPreSolve(self, joint):
if hasattr(self, "partMovedByPresolved") and self.partMovedByPresolved:
self.partMovedByPresolved.Placement = self.presolveBackupPlc
self.partMovedByPresolved = None
if hasattr(self, "partsMovedByPresolved") and self.partsMovedByPresolved:
for part, plc in self.partsMovedByPresolved.items():
if part and hasattr(part, "Placement"):
part.Placement = plc
self.partsMovedByPresolved = {}
joint.Placement1 = joint.Placement1 # Make sure plc1 is redrawn