fix(assembly): extend findPlacement() datum and origin handling (#55)
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Add support for missing datum and origin object types in the Assembly solver's findPlacement() function: - App::Plane: handle XY/XZ/YZ origin planes with correct rotations - App::Point: handle origin point (identity placement) - PartDesign::Line: extract edge midpoint and direction from shape - PartDesign::Point: extract vertex position from shape geometry instead of returning raw obj.Placement - Add obj.Shape.isNull() validation to all PartDesign datum branches - PartDesign::Plane falls back to obj.Placement on invalid shape instead of falling through to App.Placement() Closes #55
This commit is contained in:
@@ -49,7 +49,9 @@ def activePartOrAssembly():
|
|||||||
|
|
||||||
def activeAssembly():
|
def activeAssembly():
|
||||||
active_assembly = activePartOrAssembly()
|
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():
|
if active_assembly.ViewObject.isInEditMode():
|
||||||
return active_assembly
|
return active_assembly
|
||||||
|
|
||||||
@@ -59,7 +61,9 @@ def activeAssembly():
|
|||||||
def activePart():
|
def activePart():
|
||||||
active_part = activePartOrAssembly()
|
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 active_part
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@@ -120,7 +124,9 @@ def number_of_components_in(assembly):
|
|||||||
def isLink(obj):
|
def isLink(obj):
|
||||||
# If element count is not 0, then its a link group in which case the Link
|
# 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.
|
# 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):
|
def isLinkGroup(obj):
|
||||||
@@ -375,7 +381,9 @@ def getGlobalPlacement(ref, targetObj=None):
|
|||||||
if not isRefValid(ref, 1):
|
if not isRefValid(ref, 1):
|
||||||
return App.Placement()
|
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)
|
targetObj = getObject(ref)
|
||||||
if targetObj is None:
|
if targetObj is None:
|
||||||
return App.Placement()
|
return App.Placement()
|
||||||
@@ -520,11 +528,17 @@ def findElementClosestVertex(ref, mousePos):
|
|||||||
|
|
||||||
for i, edge in enumerate(edges):
|
for i, edge in enumerate(edges):
|
||||||
curve = edge.Curve
|
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.append(curve.Location)
|
||||||
center_points_edge_indexes.append(i)
|
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.
|
# handle special case of 2 cylinder intersecting.
|
||||||
for j, facej in enumerate(obj.Shape.Faces):
|
for j, facej in enumerate(obj.Shape.Faces):
|
||||||
surfacej = facej.Surface
|
surfacej = facej.Surface
|
||||||
@@ -553,7 +567,9 @@ def findElementClosestVertex(ref, mousePos):
|
|||||||
if _type == "Part::GeomCylinder" or _type == "Part::GeomCone":
|
if _type == "Part::GeomCylinder" or _type == "Part::GeomCone":
|
||||||
centerOfG = face.CenterOfGravity - surface.Center
|
centerOfG = face.CenterOfGravity - surface.Center
|
||||||
centerPoint = surface.Center + centerOfG
|
centerPoint = surface.Center + centerOfG
|
||||||
centerPoint = centerPoint + App.Vector().projectToLine(centerOfG, surface.Axis)
|
centerPoint = centerPoint + App.Vector().projectToLine(
|
||||||
|
centerOfG, surface.Axis
|
||||||
|
)
|
||||||
face_points.append(centerPoint)
|
face_points.append(centerPoint)
|
||||||
else:
|
else:
|
||||||
face_points.append(face.CenterOfGravity)
|
face_points.append(face.CenterOfGravity)
|
||||||
@@ -623,7 +639,8 @@ def color_from_unsigned(c):
|
|||||||
|
|
||||||
def getJointsOfType(asm, jointTypes):
|
def getJointsOfType(asm, jointTypes):
|
||||||
if not (
|
if not (
|
||||||
asm.isDerivedFrom("Assembly::AssemblyObject") or asm.isDerivedFrom("Assembly::AssemblyLink")
|
asm.isDerivedFrom("Assembly::AssemblyObject")
|
||||||
|
or asm.isDerivedFrom("Assembly::AssemblyLink")
|
||||||
):
|
):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -763,7 +780,9 @@ def getSubMovingParts(obj, partsAsSolid):
|
|||||||
|
|
||||||
if isLink(obj):
|
if isLink(obj):
|
||||||
linked_obj = obj.getLinkedObject()
|
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 [obj]
|
||||||
|
|
||||||
return []
|
return []
|
||||||
@@ -996,7 +1015,7 @@ def findPlacement(ref, ignoreVertex=False):
|
|||||||
vtx = getElementName(ref[1][1])
|
vtx = getElementName(ref[1][1])
|
||||||
|
|
||||||
if not elt or not vtx:
|
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.TypeId == "App::Line":
|
||||||
if obj.Role == "X_Axis":
|
if obj.Role == "X_Axis":
|
||||||
return App.Placement(App.Vector(), App.Rotation(0.5, 0.5, 0.5, 0.5))
|
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":
|
if obj.Role == "Z_Axis":
|
||||||
return App.Placement(App.Vector(), App.Rotation(-0.5, 0.5, -0.5, 0.5))
|
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 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]
|
face = obj.Shape.Faces[0]
|
||||||
surface = face.Surface
|
surface = face.Surface
|
||||||
plc = App.Placement()
|
plc = App.Placement()
|
||||||
@@ -1015,9 +1050,28 @@ def findPlacement(ref, ignoreVertex=False):
|
|||||||
if hasattr(surface, "Rotation") and surface.Rotation is not None:
|
if hasattr(surface, "Rotation") and surface.Rotation is not None:
|
||||||
plc.Rotation = App.Rotation(surface.Rotation)
|
plc.Rotation = App.Rotation(surface.Rotation)
|
||||||
return obj.Placement.inverse() * plc
|
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
|
# PartDesign datum points
|
||||||
if obj.isDerivedFrom("PartDesign::Point"):
|
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 obj.Placement
|
||||||
|
|
||||||
return App.Placement()
|
return App.Placement()
|
||||||
@@ -1080,9 +1134,14 @@ def findPlacement(ref, ignoreVertex=False):
|
|||||||
if surface.TypeId == "Part::GeomCylinder":
|
if surface.TypeId == "Part::GeomCylinder":
|
||||||
centerOfG = face.CenterOfGravity - surface.Center
|
centerOfG = face.CenterOfGravity - surface.Center
|
||||||
centerPoint = surface.Center + centerOfG
|
centerPoint = surface.Center + centerOfG
|
||||||
centerPoint = centerPoint + App.Vector().projectToLine(centerOfG, surface.Axis)
|
centerPoint = centerPoint + App.Vector().projectToLine(
|
||||||
|
centerOfG, surface.Axis
|
||||||
|
)
|
||||||
plc.Base = centerPoint
|
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
|
plc.Base = surface.Center
|
||||||
elif surface.TypeId == "Part::GeomCone":
|
elif surface.TypeId == "Part::GeomCone":
|
||||||
plc.Base = surface.Apex
|
plc.Base = surface.Apex
|
||||||
@@ -1100,7 +1159,8 @@ def findPlacement(ref, ignoreVertex=False):
|
|||||||
plc.Base = (center_point.x, center_point.y, center_point.z)
|
plc.Base = (center_point.x, center_point.y, center_point.z)
|
||||||
|
|
||||||
elif (
|
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.
|
# handle special case of 2 cylinder intersecting.
|
||||||
plc.Base = findCylindersIntersection(obj, surface, edge, elt_index)
|
plc.Base = findCylindersIntersection(obj, surface, edge, elt_index)
|
||||||
@@ -1394,13 +1454,16 @@ def generatePropertySettings(documentObject):
|
|||||||
commands.append(f"obj.{propertyName} = {propertyValue:.5f}")
|
commands.append(f"obj.{propertyName} = {propertyValue:.5f}")
|
||||||
elif propertyType == "App::PropertyInt" or propertyType == "App::PropertyBool":
|
elif propertyType == "App::PropertyInt" or propertyType == "App::PropertyBool":
|
||||||
commands.append(f"obj.{propertyName} = {propertyValue}")
|
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}"')
|
commands.append(f'obj.{propertyName} = "{propertyValue}"')
|
||||||
elif propertyType == "App::PropertyPlacement":
|
elif propertyType == "App::PropertyPlacement":
|
||||||
commands.append(
|
commands.append(
|
||||||
f"obj.{propertyName} = App.Placement("
|
f"obj.{propertyName} = App.Placement("
|
||||||
f"App.Vector({propertyValue.Base.x:.5f},{propertyValue.Base.y:.5f},{propertyValue.Base.z:.5f}),"
|
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":
|
elif propertyType == "App::PropertyXLinkSubHidden":
|
||||||
commands.append(
|
commands.append(
|
||||||
|
|||||||
Reference in New Issue
Block a user