Assembly: Property change from Object/Part to Reference
This commit is contained in:
committed by
Yorik van Havre
parent
828e85963e
commit
8d3e3acd11
@@ -95,32 +95,27 @@ def assembly_has_at_least_n_parts(n):
|
||||
return False
|
||||
|
||||
|
||||
def getObject(full_name):
|
||||
# full_name is "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBox.Edge16"
|
||||
# or "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBody.pad.Edge16"
|
||||
def getObject(ref):
|
||||
if len(ref) != 2:
|
||||
return None
|
||||
subs = ref[1]
|
||||
if len(subs) < 1:
|
||||
return None
|
||||
sub_name = subs[0]
|
||||
|
||||
# sub_name is "LinkOrAssembly1.LinkOrPart1.LinkOrBox.Edge16"
|
||||
# or "LinkOrAssembly1.LinkOrPart1.LinkOrBody.pad.Edge16"
|
||||
# or "Assembly.LinkOrAssembly1.LinkOrPart1.LinkOrBody.Local_CS.X"
|
||||
# We want either LinkOrBody or LinkOrBox or Local_CS.
|
||||
names = full_name.split(".")
|
||||
doc = App.ActiveDocument
|
||||
names = sub_name.split(".")
|
||||
|
||||
if len(names) < 3:
|
||||
if len(names) < 2:
|
||||
return None
|
||||
|
||||
prevObj = None
|
||||
doc = ref[0].Document
|
||||
|
||||
for i, objName in enumerate(names):
|
||||
if i == 0:
|
||||
prevObj = doc.getObject(objName)
|
||||
if prevObj.TypeId == "App::Link":
|
||||
prevObj = prevObj.getLinkedObject()
|
||||
continue
|
||||
|
||||
obj = None
|
||||
if prevObj.TypeId in {"App::Part", "Assembly::AssemblyObject", "App::DocumentObjectGroup"}:
|
||||
for obji in prevObj.OutList:
|
||||
if obji.Name == objName:
|
||||
obj = obji
|
||||
break
|
||||
for i, obj_name in enumerate(names):
|
||||
obj = doc.getObject(obj_name)
|
||||
|
||||
if obj is None:
|
||||
return None
|
||||
@@ -129,26 +124,7 @@ def getObject(full_name):
|
||||
if i == len(names) - 2:
|
||||
return obj
|
||||
|
||||
if obj.TypeId == "App::Link":
|
||||
linked_obj = obj.getLinkedObject()
|
||||
if linked_obj.TypeId == "PartDesign::Body":
|
||||
if i + 1 < len(names):
|
||||
obj2 = None
|
||||
for obji in linked_obj.OutList:
|
||||
if obji.Name == names[i + 1]:
|
||||
obj2 = obji
|
||||
break
|
||||
if obj2 and isBodySubObject(obj2.TypeId):
|
||||
return obj2
|
||||
return obj
|
||||
elif linked_obj.isDerivedFrom("Part::Feature"):
|
||||
return obj
|
||||
else:
|
||||
prevObj = linked_obj
|
||||
continue
|
||||
|
||||
elif obj.TypeId in {"App::Part", "Assembly::AssemblyObject", "App::DocumentObjectGroup"}:
|
||||
prevObj = obj
|
||||
if obj.TypeId in {"App::Part", "Assembly::AssemblyObject"}:
|
||||
continue
|
||||
|
||||
elif obj.TypeId == "PartDesign::Body":
|
||||
@@ -166,6 +142,24 @@ def getObject(full_name):
|
||||
# primitive, fastener, gear ...
|
||||
return obj
|
||||
|
||||
elif obj.TypeId == "App::Link":
|
||||
linked_obj = obj.getLinkedObject()
|
||||
if linked_obj.TypeId == "PartDesign::Body":
|
||||
if i + 1 < len(names):
|
||||
obj2 = None
|
||||
for obji in linked_obj.OutList:
|
||||
if obji.Name == names[i + 1]:
|
||||
obj2 = obji
|
||||
break
|
||||
if obj2 and isBodySubObject(obj2.TypeId):
|
||||
return obj2
|
||||
return obj
|
||||
elif linked_obj.isDerivedFrom("Part::Feature"):
|
||||
return obj
|
||||
else:
|
||||
doc = linked_obj.Document
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@@ -179,6 +173,7 @@ def isBodySubObject(typeId):
|
||||
)
|
||||
|
||||
|
||||
# To be deprecated. CommandCreateView needs to stop using it.
|
||||
def getContainingPart(full_name, selected_object, activeAssemblyOrPart=None):
|
||||
# full_name is "Assembly.Assembly1.LinkOrPart1.LinkOrBox.Edge16" -> LinkOrPart1
|
||||
# or "Assembly.Assembly1.LinkOrPart1.LinkOrBody.pad.Edge16" -> LinkOrPart1
|
||||
@@ -243,6 +238,7 @@ def getContainingPart(full_name, selected_object, activeAssemblyOrPart=None):
|
||||
return selected_object
|
||||
|
||||
|
||||
# To be deprecated. Kept for migrationScript.
|
||||
def getObjectInPart(objName, part):
|
||||
if part is None:
|
||||
return None
|
||||
@@ -266,43 +262,85 @@ def getObjectInPart(objName, part):
|
||||
return None
|
||||
|
||||
|
||||
# get the placement of Obj relative to its containing Part
|
||||
# Used by migrationScript.
|
||||
def getRootPath(obj, part):
|
||||
sels = obj.Parents
|
||||
for sel in sels:
|
||||
rootObj = sel[0]
|
||||
# The part and the rootObj should be in the same doc
|
||||
if rootObj.Document.Name != part.Document.Name:
|
||||
continue
|
||||
|
||||
path = sel[1]
|
||||
# we need to check that the part name is in the list.
|
||||
names = path.split(".")
|
||||
if part.Name not in names:
|
||||
continue
|
||||
|
||||
# for bodies we need to add the tip to the path.
|
||||
if obj.TypeId == "PartDesign::Body":
|
||||
path.append(obj.Tip.Name + ".")
|
||||
|
||||
return rootObj, path
|
||||
|
||||
return None, ""
|
||||
|
||||
|
||||
# get the placement of Obj relative to its moving Part
|
||||
# Example : assembly.part1.part2.partn.body1 : placement of Obj relative to part1
|
||||
def getObjPlcRelativeToPart(obj, part):
|
||||
# we need plc to be relative to the containing part
|
||||
obj_global_plc = getGlobalPlacement(obj, part)
|
||||
part_global_plc = getGlobalPlacement(part)
|
||||
def getObjPlcRelativeToPart(assembly, ref):
|
||||
# we need plc to be relative to the moving part
|
||||
moving_part = getMovingPart(assembly, ref)
|
||||
obj_global_plc = getGlobalPlacement(ref)
|
||||
part_global_plc = getGlobalPlacement(ref, moving_part)
|
||||
|
||||
return part_global_plc.inverse() * obj_global_plc
|
||||
|
||||
|
||||
# Example : assembly.part1.part2.partn.body1 : jcsPlc is relative to body1
|
||||
# This function returns jcsPlc relative to part1
|
||||
def getJcsPlcRelativeToPart(jcsPlc, obj, part):
|
||||
obj_relative_plc = getObjPlcRelativeToPart(obj, part)
|
||||
def getJcsPlcRelativeToPart(assembly, jcsPlc, ref):
|
||||
obj_relative_plc = getObjPlcRelativeToPart(assembly, ref)
|
||||
return obj_relative_plc * jcsPlc
|
||||
|
||||
|
||||
# Return the jcs global placement
|
||||
def getJcsGlobalPlc(jcsPlc, obj, part):
|
||||
obj_global_plc = getGlobalPlacement(obj, part)
|
||||
def getJcsGlobalPlc(jcsPlc, ref):
|
||||
obj_global_plc = getGlobalPlacement(ref)
|
||||
return obj_global_plc * jcsPlc
|
||||
|
||||
|
||||
# The container is used to support cases where the same object appears at several places
|
||||
# which happens when you have a link to a part.
|
||||
def getGlobalPlacement(targetObj, container=None):
|
||||
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)
|
||||
targetObj = getObject(ref)
|
||||
|
||||
if targetObj is None:
|
||||
return App.Placement()
|
||||
|
||||
inContainerBranch = container is None
|
||||
for rootObj in App.activeDocument().RootObjectsIgnoreLinks:
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, rootObj, container, inContainerBranch
|
||||
)
|
||||
if foundPlacement is not None:
|
||||
return foundPlacement
|
||||
rootObj = ref[0]
|
||||
names = ref[1][0].split(".")
|
||||
|
||||
doc = rootObj.Document
|
||||
plc = rootObj.Placement
|
||||
|
||||
for objName in names:
|
||||
obj = doc.getObject(objName)
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
plc = plc * obj.Placement
|
||||
|
||||
if obj == targetObj:
|
||||
return plc
|
||||
|
||||
if obj.TypeId == "App::Link":
|
||||
linked_obj = obj.getLinkedObject()
|
||||
doc = linked_obj.Document # in case its an external link.
|
||||
|
||||
# If targetObj has not been found there's a problem
|
||||
return App.Placement()
|
||||
|
||||
|
||||
@@ -313,71 +351,13 @@ def isThereOneRootAssembly():
|
||||
return False
|
||||
|
||||
|
||||
def getTargetPlacementRelativeTo(
|
||||
targetObj, part, container, inContainerBranch, ignorePlacement=False
|
||||
):
|
||||
inContainerBranch = inContainerBranch or (not ignorePlacement and part == container)
|
||||
|
||||
if targetObj == part and inContainerBranch and not ignorePlacement:
|
||||
return targetObj.Placement
|
||||
|
||||
if part.TypeId == "App::DocumentObjectGroup":
|
||||
for obj in part.OutList:
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, obj, container, inContainerBranch, ignorePlacement
|
||||
)
|
||||
if foundPlacement is not None:
|
||||
return foundPlacement
|
||||
|
||||
elif part.TypeId in {"App::Part", "Assembly::AssemblyObject", "PartDesign::Body"}:
|
||||
for obj in part.OutList:
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, obj, container, inContainerBranch
|
||||
)
|
||||
if foundPlacement is None:
|
||||
continue
|
||||
|
||||
# If we were called from a link then we need to ignore this placement as we use the link placement instead.
|
||||
if not ignorePlacement:
|
||||
foundPlacement = part.Placement * foundPlacement
|
||||
|
||||
return foundPlacement
|
||||
|
||||
elif part.TypeId == "App::Link":
|
||||
linked_obj = part.getLinkedObject()
|
||||
if part == linked_obj or linked_obj is None:
|
||||
return None # upon loading this can happen for external links.
|
||||
|
||||
if linked_obj.TypeId in {"App::Part", "Assembly::AssemblyObject", "PartDesign::Body"}:
|
||||
for obj in linked_obj.OutList:
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, obj, container, inContainerBranch
|
||||
)
|
||||
if foundPlacement is None:
|
||||
continue
|
||||
|
||||
foundPlacement = part.Placement * foundPlacement
|
||||
return foundPlacement
|
||||
|
||||
foundPlacement = getTargetPlacementRelativeTo(
|
||||
targetObj, linked_obj, container, inContainerBranch, True
|
||||
)
|
||||
|
||||
if foundPlacement is not None and not ignorePlacement:
|
||||
foundPlacement = part.Placement * foundPlacement
|
||||
|
||||
return foundPlacement
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def getElementName(full_name):
|
||||
# full_name is "Assembly.Assembly1.Assembly2.Assembly3.Box.Edge16"
|
||||
# We want either Edge16.
|
||||
parts = full_name.split(".")
|
||||
|
||||
if len(parts) < 3:
|
||||
# At minimum "Assembly.Box.edge16". It shouldn't be shorter
|
||||
if len(parts) < 2:
|
||||
# At minimum "Box.edge16". It shouldn't be shorter
|
||||
return ""
|
||||
|
||||
# case of PartDesign datums : CoordinateSystem, point, line, plane
|
||||
@@ -452,23 +432,27 @@ def extract_type_and_number(element_name):
|
||||
return None, None
|
||||
|
||||
|
||||
def findElementClosestVertex(selection_dict):
|
||||
obj = selection_dict["object"]
|
||||
def findElementClosestVertex(assembly, ref, mousePos):
|
||||
element_name = getElementName(ref[1][0])
|
||||
if element_name == "":
|
||||
return ""
|
||||
|
||||
mousePos = selection_dict["mouse_pos"]
|
||||
moving_part = getMovingPart(assembly, ref)
|
||||
obj = getObject(ref)
|
||||
|
||||
# We need mousePos to be relative to the part containing obj global placement
|
||||
if selection_dict["object"] != selection_dict["part"]:
|
||||
if obj != moving_part:
|
||||
plc = App.Placement()
|
||||
plc.Base = mousePos
|
||||
global_plc = getGlobalPlacement(selection_dict["part"])
|
||||
plc = global_plc.inverse() * plc
|
||||
global_plc = getGlobalPlacement(ref)
|
||||
plc = global_plc.inverse() * plc # We make it relative to obj Origin
|
||||
plc = obj.Placement * plc # Make plc in the same lcs as obj
|
||||
mousePos = plc.Base
|
||||
|
||||
elt_type, elt_index = extract_type_and_number(selection_dict["element_name"])
|
||||
elt_type, elt_index = extract_type_and_number(element_name)
|
||||
|
||||
if elt_type == "Vertex":
|
||||
return selection_dict["element_name"]
|
||||
return element_name
|
||||
|
||||
elif elt_type == "Edge":
|
||||
edge = obj.Shape.Edges[elt_index - 1]
|
||||
@@ -476,7 +460,7 @@ def findElementClosestVertex(selection_dict):
|
||||
if curve.TypeId == "Part::GeomCircle":
|
||||
# For centers, as they are not shape vertexes, we return the element name.
|
||||
# For now we only allow selecting the center of arcs / circles.
|
||||
return selection_dict["element_name"]
|
||||
return element_name
|
||||
|
||||
edge_points = getPointsFromVertexes(edge.Vertexes)
|
||||
|
||||
@@ -489,7 +473,7 @@ def findElementClosestVertex(selection_dict):
|
||||
|
||||
if curve.TypeId == "Part::GeomLine" and closest_vertex_index == 2:
|
||||
# If line center is closest then we have no vertex name to set so we put element name
|
||||
return selection_dict["element_name"]
|
||||
return element_name
|
||||
|
||||
vertex_name = findVertexNameInObject(edge.Vertexes[closest_vertex_index], obj)
|
||||
|
||||
@@ -500,7 +484,7 @@ def findElementClosestVertex(selection_dict):
|
||||
surface = face.Surface
|
||||
_type = surface.TypeId
|
||||
if _type == "Part::GeomSphere" or _type == "Part::GeomTorus":
|
||||
return selection_dict["element_name"]
|
||||
return element_name
|
||||
|
||||
# Handle the circle/arc edges for their centers
|
||||
center_points = []
|
||||
@@ -558,11 +542,11 @@ def findElementClosestVertex(selection_dict):
|
||||
return "Edge" + str(index)
|
||||
|
||||
if _type == "Part::GeomCylinder" or _type == "Part::GeomCone":
|
||||
return selection_dict["element_name"]
|
||||
return element_name
|
||||
|
||||
if closest_vertex_index == len(face.Vertexes):
|
||||
# If center of gravity then we have no vertex name to set so we put element name
|
||||
return selection_dict["element_name"]
|
||||
return element_name
|
||||
|
||||
vertex_name = findVertexNameInObject(face.Vertexes[closest_vertex_index], obj)
|
||||
|
||||
@@ -785,10 +769,10 @@ def getObjMassAndCom(obj, containingPart=None):
|
||||
return 0, App.Vector(0, 0, 0)
|
||||
|
||||
|
||||
def getCenterOfBoundingBox(objs, parts):
|
||||
def getCenterOfBoundingBox(objs, refs):
|
||||
i = 0
|
||||
center = App.Vector()
|
||||
for obj, part in zip(objs, parts):
|
||||
for obj, ref in zip(objs, refs):
|
||||
viewObject = obj.ViewObject
|
||||
if viewObject is None:
|
||||
continue
|
||||
@@ -796,15 +780,15 @@ def getCenterOfBoundingBox(objs, parts):
|
||||
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
|
||||
|
||||
# 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(ref, obj)
|
||||
plc = global_plc * plc
|
||||
bboxCenter = plc.Base
|
||||
|
||||
center = center + bboxCenter
|
||||
i = i + 1
|
||||
@@ -880,9 +864,15 @@ So here we want to find a placement that corresponds to a local coordinate syste
|
||||
"""
|
||||
|
||||
|
||||
def findPlacement(obj, part, elt, vtx, ignoreVertex=False):
|
||||
if not obj or not part:
|
||||
def findPlacement(ref, ignoreVertex=False):
|
||||
if not isRefValid(ref, 2):
|
||||
return App.Placement()
|
||||
obj = getObject(ref)
|
||||
if not obj:
|
||||
return App.Placement()
|
||||
|
||||
elt = getElementName(ref[1][0])
|
||||
vtx = getElementName(ref[1][1])
|
||||
|
||||
# case of origin objects.
|
||||
if elt == "X_Axis" or elt == "YZ_Plane":
|
||||
@@ -999,6 +989,17 @@ def findPlacement(obj, part, elt, vtx, ignoreVertex=False):
|
||||
return plc
|
||||
|
||||
|
||||
def isRefValid(ref, number_sub):
|
||||
if ref is None:
|
||||
return False
|
||||
if len(ref) != 2:
|
||||
return False
|
||||
if len(ref[1]) < number_sub:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def round_vector(v, decimals=10):
|
||||
"""Round each component of the vector to a specified number of decimal places."""
|
||||
return App.Vector(round(v.x, decimals), round(v.y, decimals), round(v.z, decimals))
|
||||
@@ -1045,8 +1046,8 @@ def getAssemblyShapes(assembly):
|
||||
|
||||
|
||||
def getJointDistance(joint):
|
||||
plc1 = getJcsGlobalPlc(joint.Placement1, joint.Object1, joint.Part1)
|
||||
plc2 = getJcsGlobalPlc(joint.Placement2, joint.Object2, joint.Part2)
|
||||
plc1 = getJcsGlobalPlc(joint.Placement1, joint.Reference1)
|
||||
plc2 = getJcsGlobalPlc(joint.Placement2, joint.Reference2)
|
||||
|
||||
# Find the sign
|
||||
sign = 1
|
||||
@@ -1058,10 +1059,112 @@ def getJointDistance(joint):
|
||||
|
||||
|
||||
def getJointXYAngle(joint):
|
||||
plc1 = getJcsGlobalPlc(joint.Placement1, joint.Object1, joint.Part1)
|
||||
plc2 = getJcsGlobalPlc(joint.Placement2, joint.Object2, joint.Part2)
|
||||
plc1 = getJcsGlobalPlc(joint.Placement1, joint.Reference1)
|
||||
plc2 = getJcsGlobalPlc(joint.Placement2, joint.Reference2)
|
||||
|
||||
plc3 = plc1.inverse() * plc2 # plc3 is plc2 relative to plc1
|
||||
x_axis = plc3.Rotation.multVec(App.Vector(1, 0, 0))
|
||||
|
||||
return math.atan2(x_axis.y, x_axis.x)
|
||||
|
||||
|
||||
def getMovingPart(assembly, ref):
|
||||
# ref can be :
|
||||
# [assembly, ['box.edge1', 'box.vertex2']]
|
||||
# [Part, ['Assembly.box.edge1', 'Assembly.box.vertex2']]
|
||||
# [assembly, ['Body.Pad.edge1', 'Body.Pad.vertex2']]
|
||||
|
||||
if assembly is None or ref is None or len(ref) != 2:
|
||||
return None
|
||||
|
||||
obj = ref[0]
|
||||
subs = ref[1]
|
||||
|
||||
if subs is None or len(subs) < 1:
|
||||
return None
|
||||
|
||||
sub = ref[1][0] # All subs should have the same object paths.
|
||||
names = [obj.Name] + sub.split(".")
|
||||
|
||||
try:
|
||||
index = names.index(assembly.Name)
|
||||
# Get the sublist starting after the after the assembly (in case of Part1/Assembly/...)
|
||||
names = names[index + 1 :]
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
doc = assembly.Document
|
||||
|
||||
if len(names) < 2:
|
||||
App.Console.PrintError(
|
||||
"getMovingPart() in UtilsAssembly.py the object name is too short, at minimum it should be something like ['Box','edge16']. It shouldn't be shorter"
|
||||
)
|
||||
return None
|
||||
|
||||
for objName in names:
|
||||
obj = doc.getObject(objName)
|
||||
|
||||
if not obj:
|
||||
continue
|
||||
|
||||
return obj
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def truncateSubAtFirst(sub, target):
|
||||
# target=part1 & sub=asm.part1.link1.part1.obj -> asm.part1.
|
||||
names = sub.split(".")
|
||||
sub = ""
|
||||
for name in names:
|
||||
sub = sub + name + "."
|
||||
if name == target:
|
||||
break
|
||||
|
||||
return sub
|
||||
|
||||
|
||||
def truncateSubAtLast(sub, target):
|
||||
# target=part1 & sub=asm.part1.link1.part1.obj -> asm.part1.link1.part1.
|
||||
names = sub.split(".")
|
||||
sub = ""
|
||||
target_indices = [i for i, name in enumerate(names) if name == target]
|
||||
|
||||
if target_indices:
|
||||
last_index = target_indices[-1]
|
||||
for i, name in enumerate(names):
|
||||
sub += name + "."
|
||||
if i == last_index:
|
||||
break
|
||||
|
||||
return sub
|
||||
|
||||
|
||||
def swapElNameInSubname(sub_name, new_elName):
|
||||
# turns assembly.box.edge1 into assembly.box.new_elName
|
||||
names = sub_name.split(".")
|
||||
|
||||
# Replace the last element
|
||||
names[-1] = new_elName
|
||||
|
||||
# Join the names back together
|
||||
modified_sub = ".".join(names)
|
||||
|
||||
return modified_sub
|
||||
|
||||
|
||||
def addVertexToReference(ref, vertex_name):
|
||||
# Turns [obj, ['box.face1']] and 'vertex1' into [obj, ['box.face1', 'box.vertex1']]
|
||||
if len(ref) == 2:
|
||||
subs = ref[1]
|
||||
if len(subs) > 0:
|
||||
sub_name = subs[0]
|
||||
vertex_full_sub = swapElNameInSubname(sub_name, vertex_name)
|
||||
if len(subs) == 2: # Update the vertex sub
|
||||
subs[1] = vertex_full_sub
|
||||
else:
|
||||
subs.append(vertex_full_sub)
|
||||
|
||||
ref = [ref[0], subs]
|
||||
|
||||
return ref
|
||||
|
||||
Reference in New Issue
Block a user