Assembly: Replace Tangent+Parallel+Planar by 'Distance'.

This commit is contained in:
Paddle
2023-11-14 18:39:09 +01:00
committed by PaddleStroke
parent 1d7671942f
commit 2a3284808f
28 changed files with 2716 additions and 399 deletions

View File

@@ -22,6 +22,7 @@
# ***************************************************************************/
import FreeCAD as App
import Part
if App.GuiUp:
import FreeCADGui as Gui
@@ -36,17 +37,35 @@ __url__ = "https://www.freecad.org"
def activeAssembly():
doc = Gui.ActiveDocument
if doc is None or doc.ActiveView is None:
return None
active_assembly = doc.ActiveView.getActiveObject("part")
if active_assembly is not None and active_assembly.Type == "Assembly":
return active_assembly
return None
def activePart():
doc = Gui.ActiveDocument
if doc is None or doc.ActiveView is None:
return None
active_part = doc.ActiveView.getActiveObject("part")
if active_part is not None and active_part.Type == "Assembly":
if active_part is not None and active_part.Type != "Assembly":
return active_part
return None
def isAssemblyCommandActive():
return activeAssembly() is not None and not Gui.Control.activeDialog()
def isDocTemporary(doc):
# Guard against older versions of FreeCad which don't have the Temporary attribute
try:
@@ -56,19 +75,39 @@ def isDocTemporary(doc):
return temp
def assembly_has_at_least_n_parts(n):
assembly = activeAssembly()
i = 0
if not assembly:
return False
for obj in assembly.OutList:
# note : groundedJoints comes in the outlist so we filter those out.
if hasattr(obj, "Placement") and not hasattr(obj, "ObjectToGround"):
i = i + 1
if i == n:
return True
return False
def getObject(full_name):
# full_name is "Assembly.Assembly1.Assembly2.Assembly3.Box.Edge16"
# or "Assembly.Assembly1.Assembly2.Assembly3.Body.pad.Edge16"
# We want either Body or Box.
parts = full_name.split(".")
# full_name is "Assembly.Assembly1.LinkOrPart1.Box.Edge16"
# or "Assembly.Assembly1.LinkOrPart1.Body.pad.Edge16"
# or "Assembly.Assembly1.LinkOrPart1.Body.Local_CS.X"
# We want either Body or Box or Local_CS.
names = full_name.split(".")
doc = App.ActiveDocument
if len(parts) < 3:
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
obj = doc.getObject(parts[-3]) # So either 'Body', or 'Assembly'
obj = doc.getObject(names[-2])
if obj and obj.TypeId == "PartDesign::CoordinateSystem":
return doc.getObject(names[-2])
obj = doc.getObject(names[-3]) # So either 'Body', or 'Assembly'
if not obj:
return None
@@ -80,8 +119,116 @@ def getObject(full_name):
if linked_obj.TypeId == "PartDesign::Body":
return obj
else: # primitive, fastener, gear ... or link to primitive, fastener, gear...
return doc.getObject(parts[-2])
# primitive, fastener, gear ... or link to primitive, fastener, gear...
return doc.getObject(names[-2])
def getContainingPart(full_name, selected_object):
# full_name is "Assembly.Assembly1.LinkOrPart1.Box.Edge16"
# or "Assembly.Assembly1.LinkOrPart1.Body.pad.Edge16"
# We want either Body or Box.
names = full_name.split(".")
doc = App.ActiveDocument
if len(names) < 3:
App.Console.PrintError(
"getContainingPart() 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
for objName in names:
obj = doc.getObject(objName)
if not obj:
continue
if (
obj.TypeId == "PartDesign::Body"
and selected_object.TypeId == "PartDesign::CoordinateSystem"
):
if obj.hasObject(selected_object, True):
return obj
# Note here we may want to specify a specific behavior for Assembly::AssemblyObject.
if obj.TypeId == "App::Part":
if obj.hasObject(selected_object, True):
return obj
elif obj.TypeId == "App::Link":
linked_obj = obj.getLinkedObject()
if linked_obj.TypeId == "App::Part":
if linked_obj.hasObject(selected_object, True):
return obj
# no container found so we return the object itself.
return selected_object
# 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):
inContainerBranch = container is None
for part in App.activeDocument().RootObjects:
foundPlacement = getTargetPlacementRelativeTo(targetObj, part, container, inContainerBranch)
if foundPlacement is not None:
return foundPlacement
return App.Placement()
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 == "App::Part" or part.TypeId == "Assembly::AssemblyObject":
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 linked_obj.TypeId == "App::Part" or linked_obj.TypeId == "Assembly::AssemblyObject":
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):
@@ -93,6 +240,10 @@ def getElementName(full_name):
# At minimum "Assembly.Box.edge16". It shouldn't be shorter
return ""
# case of PartDesign::CoordinateSystem
if parts[-1] == "X" or parts[-1] == "Y" or parts[-1] == "Z":
return ""
return parts[-1]
@@ -147,14 +298,25 @@ def extract_type_and_number(element_name):
def findElementClosestVertex(selection_dict):
obj = selection_dict["object"]
mousePos = selection_dict["mouse_pos"]
# We need mousePos to be relative to the part containing obj global placement
if selection_dict["object"] != selection_dict["part"]:
plc = App.Placement()
plc.Base = mousePos
global_plc = getGlobalPlacement(selection_dict["part"])
plc = global_plc.inverse() * plc
mousePos = plc.Base
elt_type, elt_index = extract_type_and_number(selection_dict["element_name"])
if elt_type == "Vertex":
return selection_dict["element_name"]
elif elt_type == "Edge":
edge = selection_dict["object"].Shape.Edges[elt_index - 1]
edge = obj.Shape.Edges[elt_index - 1]
curve = edge.Curve
if curve.TypeId == "Part::GeomCircle":
# For centers, as they are not shape vertexes, we return the element name.
@@ -162,17 +324,28 @@ def findElementClosestVertex(selection_dict):
return selection_dict["element_name"]
edge_points = getPointsFromVertexes(edge.Vertexes)
closest_vertex_index, _ = findClosestPointToMousePos(
edge_points, selection_dict["mouse_pos"]
)
vertex_name = findVertexNameInObject(
edge.Vertexes[closest_vertex_index], selection_dict["object"]
)
if curve.TypeId == "Part::GeomLine":
# For lines we allow users to select the middle of lines as well.
line_middle = (edge_points[0] + edge_points[1]) * 0.5
edge_points.append(line_middle)
closest_vertex_index, _ = findClosestPointToMousePos(edge_points, mousePos)
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"]
vertex_name = findVertexNameInObject(edge.Vertexes[closest_vertex_index], obj)
return vertex_name
elif elt_type == "Face":
face = selection_dict["object"].Shape.Faces[elt_index - 1]
face = obj.Shape.Faces[elt_index - 1]
surface = face.Surface
_type = surface.TypeId
if _type == "Part::GeomSphere" or _type == "Part::GeomTorus":
return selection_dict["element_name"]
# Handle the circle/arc edges for their centers
center_points = []
@@ -181,19 +354,46 @@ def findElementClosestVertex(selection_dict):
for i, edge in enumerate(edges):
curve = edge.Curve
if curve.TypeId == "Part::GeomCircle":
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":
# handle special case of 2 cylinder intersecting.
for j, facej in enumerate(obj.Shape.Faces):
surfacej = facej.Surface
if (elt_index - 1) != j and surfacej.TypeId == "Part::GeomCylinder":
for edgej in facej.Edges:
if edgej.Curve.TypeId == "Part::GeomBSplineCurve":
if (
edgej.CenterOfGravity == edge.CenterOfGravity
and edgej.Length == edge.Length
):
center_points.append(edgej.CenterOfGravity)
center_points_edge_indexes.append(i)
if len(center_points) > 0:
closest_center_index, closest_center_distance = findClosestPointToMousePos(
center_points, selection_dict["mouse_pos"]
center_points, mousePos
)
# Hendle the face vertexes
face_points = getPointsFromVertexes(face.Vertexes)
# Handle the face vertexes
face_points = []
if _type != "Part::GeomCylinder" and _type != "Part::GeomCone":
face_points = getPointsFromVertexes(face.Vertexes)
# We also allow users to select the center of gravity.
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)
face_points.append(centerPoint)
else:
face_points.append(face.CenterOfGravity)
closest_vertex_index, closest_vertex_distance = findClosestPointToMousePos(
face_points, selection_dict["mouse_pos"]
face_points, mousePos
)
if len(center_points) > 0:
@@ -202,9 +402,14 @@ def findElementClosestVertex(selection_dict):
index = center_points_edge_indexes[closest_center_index] + 1
return "Edge" + str(index)
vertex_name = findVertexNameInObject(
face.Vertexes[closest_vertex_index], selection_dict["object"]
)
if _type == "Part::GeomCylinder" or _type == "Part::GeomCone":
return selection_dict["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"]
vertex_name = findVertexNameInObject(face.Vertexes[closest_vertex_index], obj)
return vertex_name
@@ -247,8 +452,51 @@ def color_from_unsigned(c):
def getJointGroup(assembly):
joint_group = assembly.getObject("Joints")
joint_group = None
for obj in assembly.OutList:
if obj.TypeId == "Assembly::JointGroup":
joint_group = obj
break
if not joint_group:
joint_group = assembly.newObject("Assembly::JointGroup", "Joints")
return joint_group
def isAssemblyGrounded():
assembly = activeAssembly()
if not assembly:
return False
jointGroup = getJointGroup(assembly)
for joint in jointGroup.Group:
if hasattr(joint, "ObjectToGround"):
return True
return False
def removeObjAndChilds(obj):
removeObjsAndChilds([obj])
def removeObjsAndChilds(objs):
def addsubobjs(obj, toremoveset):
if obj.TypeId == "App::Origin": # Origins are already handled
return
toremoveset.add(obj)
if obj.TypeId != "App::Link":
for subobj in obj.OutList:
addsubobjs(subobj, toremoveset)
toremove = set()
for obj in objs:
addsubobjs(obj, toremove)
for obj in toremove:
if obj:
obj.Document.removeObject(obj.Name)