Merge pull request 'fix(assembly): extend findPlacement() datum and origin handling (#55)' (#83) from fix/assembly-datum-placement into main
Some checks failed
Build and Test / build (push) Has been cancelled

Reviewed-on: #83
This commit was merged in pull request #83.
This commit is contained in:
2026-02-09 02:11:50 +00:00

View File

@@ -49,7 +49,9 @@ def activePartOrAssembly():
def activeAssembly():
active_assembly = activePartOrAssembly()
if active_assembly is not None and active_assembly.isDerivedFrom("Assembly::AssemblyObject"):
if active_assembly is not None and active_assembly.isDerivedFrom(
"Assembly::AssemblyObject"
):
if active_assembly.ViewObject.isInEditMode():
return active_assembly
@@ -59,7 +61,9 @@ def activeAssembly():
def activePart():
active_part = activePartOrAssembly()
if active_part is not None and not active_part.isDerivedFrom("Assembly::AssemblyObject"):
if active_part is not None and not active_part.isDerivedFrom(
"Assembly::AssemblyObject"
):
return active_part
return None
@@ -120,7 +124,9 @@ def number_of_components_in(assembly):
def isLink(obj):
# If element count is not 0, then its a link group in which case the Link
# is a container and it's the LinkElement that is linking to external doc.
return (obj.TypeId == "App::Link" and obj.ElementCount == 0) or obj.TypeId == "App::LinkElement"
return (
obj.TypeId == "App::Link" and obj.ElementCount == 0
) or obj.TypeId == "App::LinkElement"
def isLinkGroup(obj):
@@ -375,7 +381,9 @@ def getGlobalPlacement(ref, targetObj=None):
if not isRefValid(ref, 1):
return App.Placement()
if targetObj is None: # If no targetObj is given, we consider it's the getObject(ref)
if (
targetObj is None
): # If no targetObj is given, we consider it's the getObject(ref)
targetObj = getObject(ref)
if targetObj is None:
return App.Placement()
@@ -520,11 +528,17 @@ def findElementClosestVertex(ref, mousePos):
for i, edge in enumerate(edges):
curve = edge.Curve
if curve.TypeId == "Part::GeomCircle" or curve.TypeId == "Part::GeomEllipse":
if (
curve.TypeId == "Part::GeomCircle"
or curve.TypeId == "Part::GeomEllipse"
):
center_points.append(curve.Location)
center_points_edge_indexes.append(i)
elif _type == "Part::GeomCylinder" and curve.TypeId == "Part::GeomBSplineCurve":
elif (
_type == "Part::GeomCylinder"
and curve.TypeId == "Part::GeomBSplineCurve"
):
# handle special case of 2 cylinder intersecting.
for j, facej in enumerate(obj.Shape.Faces):
surfacej = facej.Surface
@@ -553,7 +567,9 @@ def findElementClosestVertex(ref, mousePos):
if _type == "Part::GeomCylinder" or _type == "Part::GeomCone":
centerOfG = face.CenterOfGravity - surface.Center
centerPoint = surface.Center + centerOfG
centerPoint = centerPoint + App.Vector().projectToLine(centerOfG, surface.Axis)
centerPoint = centerPoint + App.Vector().projectToLine(
centerOfG, surface.Axis
)
face_points.append(centerPoint)
else:
face_points.append(face.CenterOfGravity)
@@ -623,7 +639,8 @@ def color_from_unsigned(c):
def getJointsOfType(asm, jointTypes):
if not (
asm.isDerivedFrom("Assembly::AssemblyObject") or asm.isDerivedFrom("Assembly::AssemblyLink")
asm.isDerivedFrom("Assembly::AssemblyObject")
or asm.isDerivedFrom("Assembly::AssemblyLink")
):
return []
@@ -763,7 +780,9 @@ def getSubMovingParts(obj, partsAsSolid):
if isLink(obj):
linked_obj = obj.getLinkedObject()
if linked_obj.isDerivedFrom("App::Part") or linked_obj.isDerivedFrom("Part::Feature"):
if linked_obj.isDerivedFrom("App::Part") or linked_obj.isDerivedFrom(
"Part::Feature"
):
return [obj]
return []
@@ -996,7 +1015,7 @@ def findPlacement(ref, ignoreVertex=False):
vtx = getElementName(ref[1][1])
if not elt or not vtx:
# case of whole parts such as PartDesign::Body or App/PartDesign::CordinateSystem/Point/Line/Plane.
# Origin objects (App::Line, App::Plane, App::Point)
if obj.TypeId == "App::Line":
if obj.Role == "X_Axis":
return App.Placement(App.Vector(), App.Rotation(0.5, 0.5, 0.5, 0.5))
@@ -1005,9 +1024,25 @@ def findPlacement(ref, ignoreVertex=False):
if obj.Role == "Z_Axis":
return App.Placement(App.Vector(), App.Rotation(-0.5, 0.5, -0.5, 0.5))
# PartDesign datum planes (including ZTools datums like ZPlane_Mid, ZPlane_Offset)
if obj.TypeId == "App::Plane":
if obj.Role == "XY_Plane":
return App.Placement()
if obj.Role == "XZ_Plane":
return App.Placement(
App.Vector(), App.Rotation(App.Vector(1, 0, 0), -90)
)
if obj.Role == "YZ_Plane":
return App.Placement(
App.Vector(), App.Rotation(App.Vector(0, 1, 0), 90)
)
return App.Placement()
if obj.TypeId == "App::Point":
return App.Placement()
# PartDesign datum planes
if obj.isDerivedFrom("PartDesign::Plane"):
if hasattr(obj, "Shape") and obj.Shape.Faces:
if hasattr(obj, "Shape") and not obj.Shape.isNull() and obj.Shape.Faces:
face = obj.Shape.Faces[0]
surface = face.Surface
plc = App.Placement()
@@ -1015,9 +1050,28 @@ def findPlacement(ref, ignoreVertex=False):
if hasattr(surface, "Rotation") and surface.Rotation is not None:
plc.Rotation = App.Rotation(surface.Rotation)
return obj.Placement.inverse() * plc
return obj.Placement
# PartDesign datum lines
if obj.isDerivedFrom("PartDesign::Line"):
if hasattr(obj, "Shape") and not obj.Shape.isNull() and obj.Shape.Edges:
edge = obj.Shape.Edges[0]
points = getPointsFromVertexes(edge.Vertexes)
mid = (points[0] + points[1]) * 0.5
direction = round_vector(edge.Curve.Direction)
plane = Part.Plane(App.Vector(), direction)
plc = App.Placement()
plc.Base = mid
plc.Rotation = App.Rotation(plane.Rotation)
return obj.Placement.inverse() * plc
return obj.Placement
# PartDesign datum points
if obj.isDerivedFrom("PartDesign::Point"):
if hasattr(obj, "Shape") and not obj.Shape.isNull() and obj.Shape.Vertexes:
plc = App.Placement()
plc.Base = obj.Shape.Vertexes[0].Point
return obj.Placement.inverse() * plc
return obj.Placement
return App.Placement()
@@ -1080,9 +1134,14 @@ def findPlacement(ref, ignoreVertex=False):
if surface.TypeId == "Part::GeomCylinder":
centerOfG = face.CenterOfGravity - surface.Center
centerPoint = surface.Center + centerOfG
centerPoint = centerPoint + App.Vector().projectToLine(centerOfG, surface.Axis)
centerPoint = centerPoint + App.Vector().projectToLine(
centerOfG, surface.Axis
)
plc.Base = centerPoint
elif surface.TypeId == "Part::GeomTorus" or surface.TypeId == "Part::GeomSphere":
elif (
surface.TypeId == "Part::GeomTorus"
or surface.TypeId == "Part::GeomSphere"
):
plc.Base = surface.Center
elif surface.TypeId == "Part::GeomCone":
plc.Base = surface.Apex
@@ -1100,7 +1159,8 @@ def findPlacement(ref, ignoreVertex=False):
plc.Base = (center_point.x, center_point.y, center_point.z)
elif (
surface.TypeId == "Part::GeomCylinder" and curve.TypeId == "Part::GeomBSplineCurve"
surface.TypeId == "Part::GeomCylinder"
and curve.TypeId == "Part::GeomBSplineCurve"
):
# handle special case of 2 cylinder intersecting.
plc.Base = findCylindersIntersection(obj, surface, edge, elt_index)
@@ -1394,13 +1454,16 @@ def generatePropertySettings(documentObject):
commands.append(f"obj.{propertyName} = {propertyValue:.5f}")
elif propertyType == "App::PropertyInt" or propertyType == "App::PropertyBool":
commands.append(f"obj.{propertyName} = {propertyValue}")
elif propertyType == "App::PropertyString" or propertyType == "App::PropertyEnumeration":
elif (
propertyType == "App::PropertyString"
or propertyType == "App::PropertyEnumeration"
):
commands.append(f'obj.{propertyName} = "{propertyValue}"')
elif propertyType == "App::PropertyPlacement":
commands.append(
f"obj.{propertyName} = App.Placement("
f"App.Vector({propertyValue.Base.x:.5f},{propertyValue.Base.y:.5f},{propertyValue.Base.z:.5f}),"
f"App.Rotation(*{[round(n,5) for n in propertyValue.Rotation.getYawPitchRoll()]}))"
f"App.Rotation(*{[round(n, 5) for n in propertyValue.Rotation.getYawPitchRoll()]}))"
)
elif propertyType == "App::PropertyXLinkSubHidden":
commands.append(