Assembly: Move static functions from JointObject to UtilsAssembly so they can be reused. And adds few functions

This commit is contained in:
PaddleStroke
2024-03-08 09:48:12 +01:00
committed by Yorik van Havre
parent 833913a342
commit d76e33cdd1
2 changed files with 302 additions and 158 deletions

View File

@@ -386,193 +386,46 @@ class Joint:
return App.Placement()
obj = UtilsAssembly.getObjectInPart(objName, part)
plc = App.Placement()
if not obj:
return App.Placement()
if not elt or not vtx:
# case of whole parts such as PartDesign::Body or PartDesign::CordinateSystem/Point/Line/Plane.
return App.Placement()
elt_type, elt_index = UtilsAssembly.extract_type_and_number(elt)
vtx_type, vtx_index = UtilsAssembly.extract_type_and_number(vtx)
isLine = False
if elt_type == "Vertex":
vertex = obj.Shape.Vertexes[elt_index - 1]
plc.Base = (vertex.X, vertex.Y, vertex.Z)
elif elt_type == "Edge":
edge = obj.Shape.Edges[elt_index - 1]
curve = edge.Curve
# First we find the translation
if vtx_type == "Edge" or joint.JointType == "Distance":
# In this case the wanted vertex is the center.
if curve.TypeId == "Part::GeomCircle":
center_point = curve.Location
plc.Base = (center_point.x, center_point.y, center_point.z)
elif curve.TypeId == "Part::GeomLine":
edge_points = UtilsAssembly.getPointsFromVertexes(edge.Vertexes)
line_middle = (edge_points[0] + edge_points[1]) * 0.5
plc.Base = line_middle
else:
vertex = obj.Shape.Vertexes[vtx_index - 1]
plc.Base = (vertex.X, vertex.Y, vertex.Z)
# Then we find the Rotation
if curve.TypeId == "Part::GeomCircle":
plc.Rotation = App.Rotation(curve.Rotation)
if curve.TypeId == "Part::GeomLine":
isLine = True
plane_normal = curve.Direction
plane_origin = App.Vector(0, 0, 0)
plane = Part.Plane(plane_origin, plane_normal)
plc.Rotation = App.Rotation(plane.Rotation)
elif elt_type == "Face":
face = obj.Shape.Faces[elt_index - 1]
surface = face.Surface
# First we find the translation
if vtx_type == "Face" or joint.JointType == "Distance":
if surface.TypeId == "Part::GeomCylinder" or surface.TypeId == "Part::GeomCone":
centerOfG = face.CenterOfGravity - surface.Center
centerPoint = surface.Center + centerOfG
centerPoint = centerPoint + App.Vector().projectToLine(centerOfG, surface.Axis)
plc.Base = centerPoint
elif surface.TypeId == "Part::GeomTorus" or surface.TypeId == "Part::GeomSphere":
plc.Base = surface.Center
else:
plc.Base = face.CenterOfGravity
elif vtx_type == "Edge":
# In this case the edge is a circle/arc and the wanted vertex is its center.
edge = face.Edges[vtx_index - 1]
curve = edge.Curve
if curve.TypeId == "Part::GeomCircle":
center_point = curve.Location
plc.Base = (center_point.x, center_point.y, center_point.z)
elif (
surface.TypeId == "Part::GeomCylinder"
and curve.TypeId == "Part::GeomBSplineCurve"
):
# handle special case of 2 cylinder intersecting.
plc.Base = self.findCylindersIntersection(obj, surface, edge, elt_index)
else:
vertex = obj.Shape.Vertexes[vtx_index - 1]
plc.Base = (vertex.X, vertex.Y, vertex.Z)
# Then we find the Rotation
if surface.TypeId == "Part::GeomPlane":
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
# if the part has a placement then plc is wrong.
# change plc to be relative to the object placement.
plc = obj.Placement.inverse() * plc
# post-process of plc for some special cases
if elt_type == "Vertex":
plc.Rotation = App.Rotation()
elif isLine:
plane_normal = plc.Rotation.multVec(App.Vector(0, 0, 1))
plane_origin = App.Vector(0, 0, 0)
plane = Part.Plane(plane_origin, plane_normal)
plc.Rotation = App.Rotation(plane.Rotation)
# change plc to be relative to the origin of the document.
# global_plc = UtilsAssembly.getGlobalPlacement(obj, part)
# plc = global_plc * plc
# change plc to be relative to the assembly.
# assembly = self.getAssembly(joint)
# plc = assembly.Placement.inverse() * plc
ignoreVertex = joint.JointType == "Distance"
plc = UtilsAssembly.findPlacement(obj, part, elt, vtx, ignoreVertex)
# We apply rotation / reverse / offset it necessary, but only to the second JCS.
if isSecond:
if joint.Offset.Length != 0.0:
plc = self.applyOffsetToPlacement(plc, joint.Offset)
plc = UtilsAssembly.applyOffsetToPlacement(plc, joint.Offset)
if joint.Rotation != 0.0:
plc = self.applyRotationToPlacement(plc, joint.Rotation)
plc = UtilsAssembly.applyRotationToPlacement(plc, joint.Rotation)
return plc
def applyOffsetToPlacement(self, plc, offset):
plc.Base = plc.Base + plc.Rotation.multVec(offset)
return plc
def applyRotationToPlacement(self, plc, angle):
return self.applyRotationToPlacementAlongAxis(plc, angle, App.Vector(0, 0, 1))
def applyRotationToPlacementAlongAxis(self, plc, angle, axis):
rot = plc.Rotation
zRotation = App.Rotation(axis, angle)
plc.Rotation = rot * zRotation
return plc
def flipPlacement(self, plc):
return self.applyRotationToPlacementAlongAxis(plc, 180, App.Vector(1, 0, 0))
def flipOnePart(self, joint):
assembly = self.getAssembly(joint)
part2ConnectedByJoint = assembly.isJointConnectingPartToGround(joint, "Part2")
part1Grounded = assembly.isPartGrounded(joint.Part1)
part2Grounded = assembly.isPartGrounded(joint.Part2)
if part2ConnectedByJoint and not part2Grounded:
print("flipOnePart if hasattr(self, part2Connected) and not self.part2Connected")
print(joint.Part2.Name)
jcsPlc = UtilsAssembly.getJcsPlcRelativeToPart(
joint.Placement2, joint.Object2, joint.Part2
)
globalJcsPlc = UtilsAssembly.getJcsGlobalPlc(
joint.Placement2, joint.Object2, joint.Part2
)
jcsPlc = self.flipPlacement(jcsPlc)
jcsPlc = UtilsAssembly.flipPlacement(jcsPlc)
joint.Part2.Placement = globalJcsPlc * jcsPlc.inverse()
elif not part1Grounded:
print("flipOnePart else")
print(joint.Part1.Name)
jcsPlc = UtilsAssembly.getJcsPlcRelativeToPart(
joint.Placement1, joint.Object1, joint.Part1
)
globalJcsPlc = UtilsAssembly.getJcsGlobalPlc(
joint.Placement1, joint.Object1, joint.Part1
)
jcsPlc = self.flipPlacement(jcsPlc)
jcsPlc = UtilsAssembly.flipPlacement(jcsPlc)
joint.Part1.Placement = globalJcsPlc * jcsPlc.inverse()
solveIfAllowed(self.getAssembly(joint))
def findCylindersIntersection(self, obj, surface, edge, elt_index):
for j, facej in enumerate(obj.Shape.Faces):
surfacej = facej.Surface
if (elt_index - 1) == j or surfacej.TypeId != "Part::GeomCylinder":
continue
for edgej in facej.Edges:
if (
edgej.Curve.TypeId == "Part::GeomBSplineCurve"
and edgej.CenterOfGravity == edge.CenterOfGravity
and edgej.Length == edge.Length
):
# we need intersection between the 2 cylinder axis.
line1 = Part.Line(surface.Center, surface.Center + surface.Axis)
line2 = Part.Line(surfacej.Center, surfacej.Center + surfacej.Axis)
res = line1.intersect(line2, Part.Precision.confusion())
if res:
return App.Vector(res[0].X, res[0].Y, res[0].Z)
return surface.Center
def preSolve(self, joint, obj1, part1, obj2, part2, savePlc=True):
# The goal of this is to put the part in the correct position to avoid wrong placement by the solve.
@@ -594,7 +447,7 @@ class Joint:
joint.Placement2, joint.Object2, joint.Part2
)
if not sameDir:
jcsPlc2 = self.flipPlacement(jcsPlc2)
jcsPlc2 = UtilsAssembly.flipPlacement(jcsPlc2)
joint.Part2.Placement = globalJcsPlc1 * jcsPlc2.inverse()
return True
@@ -610,7 +463,7 @@ class Joint:
joint.Placement1, joint.Object1, joint.Part1
)
if not sameDir:
jcsPlc1 = self.flipPlacement(jcsPlc1)
jcsPlc1 = UtilsAssembly.flipPlacement(jcsPlc1)
joint.Part1.Placement = globalJcsPlc2 * jcsPlc1.inverse()
return True
return False

View File

@@ -102,9 +102,6 @@ def getObject(full_name):
doc = App.ActiveDocument
if len(names) < 3:
App.Console.PrintError(
"getObject() in UtilsAssembly.py the object name is too short, at minimum it should be something like 'Assembly.Box.edge16'. It shouldn't be shorter"
)
return None
prevObj = None
@@ -641,3 +638,297 @@ def removeObjsAndChilds(objs):
for obj in toremove:
if obj:
obj.Document.removeObject(obj.Name)
# This function returns all the objects within the argument that can move:
# - Part::Features (outside of parts)
# - App::parts
# - App::Links to Part::Features or App::parts.
# It does not include Part::Features that are within App::Parts.
# It includes things inside Groups.
def getMovablePartsWithin(group, partsAsSolid=False):
parts = []
for obj in group.OutList:
parts = parts + getSubMovingParts(obj, partsAsSolid)
return parts
def getSubMovingParts(obj, partsAsSolid):
if obj.isDerivedFrom("Part::Feature"):
return [obj]
elif obj.isDerivedFrom("App::Part"):
objs = []
if not partsAsSolid:
objs = getMovablePartsWithin(obj)
objs.append(obj)
return objs
elif obj.TypeId == "App::DocumentObjectGroup":
return getMovablePartsWithin(obj)
if obj.TypeId == "App::Link":
linked_obj = obj.getLinkedObject()
if linked_obj.TypeId == "App::Part" or linked_obj.isDerivedFrom("Part::Feature"):
return [obj]
return []
# Find the center of mass of a list of parts.
# Note it could be useful to move this to Measure mod.
def getCenterOfMass(parts):
total_mass = 0
total_com = App.Vector(0, 0, 0)
for part in parts:
mass, com = getObjMassAndCom(part)
total_mass += mass
total_com += com
# After all parts are processed, calculate the overall center of mass
if total_mass > 0: # Avoid division by zero
overall_com = total_com / total_mass
else:
overall_com = App.Vector(0, 0, 0) # Default if no mass is found
return overall_com
def getObjMassAndCom(obj, containingPart=None):
link_global_plc = None
if obj.TypeId == "App::Link":
link_global_plc = getGlobalPlacement(obj, containingPart)
obj = obj.getLinkedObject()
if obj.TypeId == "PartDesign::Body":
part = part.Tip
if obj.isDerivedFrom("Part::Feature"):
mass = obj.Shape.Volume
com = obj.Shape.CenterOfGravity
# com takes into account obj placement, but not container placements.
# So we make com relative to the obj :
comPlc = App.Placement()
comPlc.Base = com
comPlc = obj.Placement.inverse() * comPlc
# Then we make it relative to the origin of the doc
global_plc = App.Placement()
if link_global_plc is None:
global_plc = getGlobalPlacement(obj, containingPart)
else:
global_plc = link_global_plc
comPlc = global_plc * comPlc
com = comPlc.Base * mass
return mass, com
elif obj.isDerivedFrom("App::Part") or obj.isDerivedFrom("App::DocumentObjectGroup"):
if containingPart is None and obj.isDerivedFrom("App::Part"):
containingPart = obj
total_mass = 0
total_com = App.Vector(0, 0, 0)
for subObj in obj.OutList:
mass, com = getObjMassAndCom(subObj, containingPart)
total_mass += mass
total_com += com
return total_mass, total_com
return 0, App.Vector(0, 0, 0)
def getCenterOfBoundingBox(objs, parts):
i = 0
center = App.Vector()
for obj, part in zip(objs, parts):
viewObject = obj.ViewObject
if viewObject is None:
continue
boundingBox = viewObject.getBoundingBox()
if boundingBox is None:
continue
bboxCenter = boundingBox.Center
if part != obj:
# bboxCenter does not take into account obj global placement
plc = App.Placement(bboxCenter, App.Rotation())
# change plc to be relative to the object placement.
plc = obj.Placement.inverse() * plc
# change plc to be relative to the origin of the document.
global_plc = getGlobalPlacement(obj, part)
plc = global_plc * plc
bboxCenter = plc.Base
center = center + bboxCenter
i = i + 1
if i != 0:
center = center / i
return center
def findCylindersIntersection(obj, surface, edge, elt_index):
for j, facej in enumerate(obj.Shape.Faces):
surfacej = facej.Surface
if (elt_index - 1) == j or surfacej.TypeId != "Part::GeomCylinder":
continue
for edgej in facej.Edges:
if (
edgej.Curve.TypeId == "Part::GeomBSplineCurve"
and edgej.CenterOfGravity == edge.CenterOfGravity
and edgej.Length == edge.Length
):
# we need intersection between the 2 cylinder axis.
line1 = Part.Line(surface.Center, surface.Center + surface.Axis)
line2 = Part.Line(surfacej.Center, surfacej.Center + surfacej.Axis)
res = line1.intersect(line2, Part.Precision.confusion())
if res:
return App.Vector(res[0].X, res[0].Y, res[0].Z)
return surface.Center
def applyOffsetToPlacement(plc, offset):
plc.Base = plc.Base + plc.Rotation.multVec(offset)
return plc
def applyRotationToPlacement(plc, angle):
return applyRotationToPlacementAlongAxis(plc, angle, App.Vector(0, 0, 1))
def applyRotationToPlacementAlongAxis(plc, angle, axis):
rot = plc.Rotation
zRotation = App.Rotation(axis, angle)
plc.Rotation = rot * zRotation
return plc
def flipPlacement(plc):
return applyRotationToPlacementAlongAxis(plc, 180, App.Vector(1, 0, 0))
"""
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
- elt can be a face, an edge or a vertex.
- If elt is a vertex, then vtx = elt And placement is vtx coordinates without rotation.
- if elt is an edge, then vtx = edge start/end vertex depending on which is closer. If elt is an arc or circle, vtx can also be the center. The rotation is the plane normal to the line positioned at vtx. Or for arcs/circle, the plane of the arc.
- if elt is a plane face, vtx is the face vertex (to the list of vertex we need to add arc/circle centers) the closer to the mouse. The placement is the plane rotation positioned at vtx
- if elt is a cylindrical face, vtx can also be the center of the arcs of the cylindrical face.
"""
def findPlacement(obj, part, elt, vtx, ignoreVertex=False):
if not obj or not part:
return App.Placement()
if not elt or not vtx:
# case of whole parts such as PartDesign::Body or PartDesign::CordinateSystem/Point/Line/Plane.
return App.Placement()
plc = App.Placement()
elt_type, elt_index = extract_type_and_number(elt)
vtx_type, vtx_index = extract_type_and_number(vtx)
isLine = False
if elt_type == "Vertex":
vertex = obj.Shape.Vertexes[elt_index - 1]
plc.Base = (vertex.X, vertex.Y, vertex.Z)
elif elt_type == "Edge":
edge = obj.Shape.Edges[elt_index - 1]
curve = edge.Curve
# First we find the translation
if vtx_type == "Edge" or ignoreVertex:
# In this case the wanted vertex is the center.
if curve.TypeId == "Part::GeomCircle":
center_point = curve.Location
plc.Base = (center_point.x, center_point.y, center_point.z)
elif curve.TypeId == "Part::GeomLine":
edge_points = getPointsFromVertexes(edge.Vertexes)
line_middle = (edge_points[0] + edge_points[1]) * 0.5
plc.Base = line_middle
else:
vertex = obj.Shape.Vertexes[vtx_index - 1]
plc.Base = (vertex.X, vertex.Y, vertex.Z)
# Then we find the Rotation
if curve.TypeId == "Part::GeomCircle":
plc.Rotation = App.Rotation(curve.Rotation)
if curve.TypeId == "Part::GeomLine":
isLine = True
plane_normal = curve.Direction
plane_origin = App.Vector(0, 0, 0)
plane = Part.Plane(plane_origin, plane_normal)
plc.Rotation = App.Rotation(plane.Rotation)
elif elt_type == "Face":
face = obj.Shape.Faces[elt_index - 1]
surface = face.Surface
# First we find the translation
if vtx_type == "Face" or ignoreVertex:
if surface.TypeId == "Part::GeomCylinder" or surface.TypeId == "Part::GeomCone":
centerOfG = face.CenterOfGravity - surface.Center
centerPoint = surface.Center + centerOfG
centerPoint = centerPoint + App.Vector().projectToLine(centerOfG, surface.Axis)
plc.Base = centerPoint
elif surface.TypeId == "Part::GeomTorus" or surface.TypeId == "Part::GeomSphere":
plc.Base = surface.Center
else:
plc.Base = face.CenterOfGravity
elif vtx_type == "Edge":
# In this case the edge is a circle/arc and the wanted vertex is its center.
edge = face.Edges[vtx_index - 1]
curve = edge.Curve
if curve.TypeId == "Part::GeomCircle":
center_point = curve.Location
plc.Base = (center_point.x, center_point.y, center_point.z)
elif (
surface.TypeId == "Part::GeomCylinder" and curve.TypeId == "Part::GeomBSplineCurve"
):
# handle special case of 2 cylinder intersecting.
plc.Base = findCylindersIntersection(obj, surface, edge, elt_index)
else:
vertex = obj.Shape.Vertexes[vtx_index - 1]
plc.Base = (vertex.X, vertex.Y, vertex.Z)
# Then we find the Rotation
if surface.TypeId == "Part::GeomPlane":
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
# if the part has a placement then plc is wrong.
# change plc to be relative to the object placement.
plc = obj.Placement.inverse() * plc
# post-process of plc for some special cases
if elt_type == "Vertex":
plc.Rotation = App.Rotation()
elif isLine:
plane_normal = plc.Rotation.multVec(App.Vector(0, 0, 1))
plane_origin = App.Vector(0, 0, 0)
plane = Part.Plane(plane_origin, plane_normal)
plc.Rotation = App.Rotation(plane.Rotation)
# change plc to be relative to the origin of the document.
# global_plc = getGlobalPlacement(obj, part)
# plc = global_plc * plc
# change plc to be relative to the assembly.
# plc = activeAssembly().Placement.inverse() * plc
return plc