Files
create/src/Mod/Assembly/UtilsAssembly.py
2023-09-22 19:46:58 +03:00

247 lines
8.3 KiB
Python

# SPDX-License-Identifier: LGPL-2.1-or-later
# /****************************************************************************
# *
# Copyright (c) 2023 Ondsel <development@ondsel.com> *
# *
# This file is part of FreeCAD. *
# *
# FreeCAD is free software: you can redistribute it and/or modify it *
# under the terms of the GNU Lesser General Public License as *
# published by the Free Software Foundation, either version 2.1 of the *
# License, or (at your option) any later version. *
# *
# FreeCAD is distributed in the hope that it will be useful, but *
# WITHOUT ANY WARRANTY; without even the implied warranty of *
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# Lesser General Public License for more details. *
# *
# You should have received a copy of the GNU Lesser General Public *
# License along with FreeCAD. If not, see *
# <https://www.gnu.org/licenses/>. *
# *
# ***************************************************************************/
import FreeCAD as App
if App.GuiUp:
import FreeCADGui as Gui
# translate = App.Qt.translate
__title__ = "Assembly utilitary functions"
__author__ = "Ondsel"
__url__ = "https://www.freecad.org"
def activeAssembly():
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":
return active_part
return None
def isDocTemporary(doc):
# Guard against older versions of FreeCad which don't have the Temporary attribute
try:
temp = doc.Temporary
except AttributeError:
temp = False
return temp
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(".")
doc = App.ActiveDocument
if len(parts) < 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'
if not obj:
return None
if obj.TypeId == "PartDesign::Body":
return obj
elif obj.TypeId == "App::Link":
linked_obj = obj.getLinkedObject()
if linked_obj.TypeId == "PartDesign::Body":
return obj
else: # primitive, fastener, gear ... or link to primitive, fastener, gear...
return doc.getObject(parts[-2])
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
return ""
return parts[-1]
def getObjsNamesAndElement(obj_name, sub_name):
# if obj_name = "Assembly" and sub_name = "Assembly1.Assembly2.Assembly3.Box.Edge16"
# this will return ["Assembly","Assembly1","Assembly2","Assembly3","Box"] and "Edge16"
parts = sub_name.split(".")
# The last part is always the element name even if empty
element_name = parts[-1]
# The remaining parts are object names
obj_names = parts[:-1]
obj_names.insert(0, obj_name)
return obj_names, element_name
def getFullObjName(obj_name, sub_name):
# if obj_name = "Assembly" and sub_name = "Assembly1.Assembly2.Assembly3.Box.Edge16"
# this will return "Assembly.Assembly1.Assembly2.Assembly3.Box"
objs_names, element_name = getObjsNamesAndElement(obj_name, sub_name)
return ".".join(objs_names)
def getFullElementName(obj_name, sub_name):
# if obj_name = "Assembly" and sub_name = "Assembly1.Assembly2.Assembly3.Box.Edge16"
# this will return "Assembly.Assembly1.Assembly2.Assembly3.Box.Edge16"
return obj_name + "." + sub_name
def extract_type_and_number(element_name):
element_type = ""
element_number = ""
for char in element_name:
if char.isalpha():
# If the character is a letter, it's part of the type
element_type += char
elif char.isdigit():
# If the character is a digit, it's part of the number
element_number += char
else:
break
if element_type and element_number:
element_number = int(element_number)
return element_type, element_number
else:
return None, None
def findElementClosestVertex(selection_dict):
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]
curve = edge.Curve
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"]
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"]
)
return vertex_name
elif elt_type == "Face":
face = selection_dict["object"].Shape.Faces[elt_index - 1]
# Handle the circle/arc edges for their centers
center_points = []
center_points_edge_indexes = []
edges = face.Edges
for i, edge in enumerate(edges):
curve = edge.Curve
if curve.TypeId == "Part::GeomCircle":
center_points.append(curve.Location)
center_points_edge_indexes.append(i)
if len(center_points) > 0:
closest_center_index, closest_center_distance = findClosestPointToMousePos(
center_points, selection_dict["mouse_pos"]
)
# Hendle the face vertexes
face_points = getPointsFromVertexes(face.Vertexes)
closest_vertex_index, closest_vertex_distance = findClosestPointToMousePos(
face_points, selection_dict["mouse_pos"]
)
if len(center_points) > 0:
if closest_center_distance < closest_vertex_distance:
# Note the index here is the index within the face! Not the object.
index = center_points_edge_indexes[closest_center_index] + 1
return "Edge" + str(index)
vertex_name = findVertexNameInObject(
face.Vertexes[closest_vertex_index], selection_dict["object"]
)
return vertex_name
return ""
def getPointsFromVertexes(vertexes):
points = []
for vtx in vertexes:
points.append(vtx.Point)
return points
def findClosestPointToMousePos(candidates_points, mousePos):
closest_point_index = None
point_min_length = None
for i, point in enumerate(candidates_points):
length = (mousePos - point).Length
if closest_point_index is None or length < point_min_length:
closest_point_index = i
point_min_length = length
return closest_point_index, point_min_length
def findVertexNameInObject(vertex, obj):
for i, vtx in enumerate(obj.Shape.Vertexes):
if vtx.Point == vertex.Point:
return "Vertex" + str(i + 1)
return ""
def color_from_unsigned(c):
return [
float(int((c >> 24) & 0xFF) / 255),
float(int((c >> 16) & 0xFF) / 255),
float(int((c >> 8) & 0xFF) / 255),
]