Assembly: Replace Tangent+Parallel+Planar by 'Distance'.
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user