|
|
|
|
@ -1,6 +1,7 @@
|
|
|
|
|
# FreeCADExchange GUI panel for guided manual 3D wiring.
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import math
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import FreeCAD as App
|
|
|
|
|
@ -68,15 +69,32 @@ def _console_error(message):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _repo_root():
|
|
|
|
|
return Path(__file__).resolve().parents[3]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _builtin_carrier_asset_path(carrier_kind):
|
|
|
|
|
file_name = BUILTIN_CARRIER_ASSETS.get((carrier_kind or "").strip())
|
|
|
|
|
if not file_name:
|
|
|
|
|
return ""
|
|
|
|
|
return str(_repo_root() / "data" / "examples" / "qet_cabinet_assets" / file_name)
|
|
|
|
|
module_path = Path(__file__).resolve()
|
|
|
|
|
candidate_roots = []
|
|
|
|
|
for index in (3, 2):
|
|
|
|
|
try:
|
|
|
|
|
candidate_roots.append(module_path.parents[index])
|
|
|
|
|
except IndexError:
|
|
|
|
|
pass
|
|
|
|
|
try:
|
|
|
|
|
app_home = App.ConfigGet("AppHomePath")
|
|
|
|
|
if app_home:
|
|
|
|
|
candidate_roots.append(Path(app_home))
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
for root in candidate_roots:
|
|
|
|
|
candidate = root / "data" / "examples" / "qet_cabinet_assets" / file_name
|
|
|
|
|
if candidate.is_file():
|
|
|
|
|
return str(candidate)
|
|
|
|
|
|
|
|
|
|
if candidate_roots:
|
|
|
|
|
return str(candidate_roots[0] / "data" / "examples" / "qet_cabinet_assets" / file_name)
|
|
|
|
|
return str(module_path.parent / file_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _supported_carrier_asset(path):
|
|
|
|
|
@ -91,6 +109,29 @@ def _active_document():
|
|
|
|
|
return doc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _activate_document(doc):
|
|
|
|
|
if doc is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
setter = getattr(App, "setActiveDocument", None)
|
|
|
|
|
if callable(setter):
|
|
|
|
|
try:
|
|
|
|
|
setter(doc.Name)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
App.ActiveDocument = doc
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if Gui is not None:
|
|
|
|
|
Gui.ActiveDocument = Gui.getDocument(doc.Name)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _selection():
|
|
|
|
|
if Gui is None:
|
|
|
|
|
return []
|
|
|
|
|
@ -120,6 +161,195 @@ def _shape_center(shape):
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vector(x=0.0, y=0.0, z=0.0):
|
|
|
|
|
return App.Vector(float(x), float(y), float(z))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vector_add(left, right):
|
|
|
|
|
return _vector(left.x + right.x, left.y + right.y, left.z + right.z)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vector_sub(left, right):
|
|
|
|
|
return _vector(left.x - right.x, left.y - right.y, left.z - right.z)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vector_scale(vector, factor):
|
|
|
|
|
return _vector(vector.x * float(factor), vector.y * float(factor), vector.z * float(factor))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vector_length(vector):
|
|
|
|
|
return math.sqrt(vector.x * vector.x + vector.y * vector.y + vector.z * vector.z)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_vector(vector):
|
|
|
|
|
if vector is None:
|
|
|
|
|
return None
|
|
|
|
|
length = _vector_length(vector)
|
|
|
|
|
if length <= 1e-9:
|
|
|
|
|
return None
|
|
|
|
|
return _vector_scale(vector, 1.0 / length)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vector_dot(left, right):
|
|
|
|
|
return left.x * right.x + left.y * right.y + left.z * right.z
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vectors_close(left, right, tolerance=1e-6):
|
|
|
|
|
return _vector_length(_vector_sub(left, right)) <= tolerance
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _face_anchor_point(picked, sub_object):
|
|
|
|
|
picked_points = list(getattr(picked, "PickedPoints", []) or [])
|
|
|
|
|
if picked_points:
|
|
|
|
|
return picked_points[0]
|
|
|
|
|
center = getattr(sub_object, "CenterOfMass", None)
|
|
|
|
|
if center is not None:
|
|
|
|
|
return center
|
|
|
|
|
return _shape_center(sub_object)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _face_normal(sub_object):
|
|
|
|
|
if not hasattr(sub_object, "normalAt"):
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
return _normalize_vector(sub_object.normalAt(0.5, 0.5))
|
|
|
|
|
except Exception:
|
|
|
|
|
try:
|
|
|
|
|
return _normalize_vector(sub_object.normalAt(0.5))
|
|
|
|
|
except Exception:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _selected_contact_face_refs():
|
|
|
|
|
refs = []
|
|
|
|
|
for picked in _selection_ex():
|
|
|
|
|
obj = getattr(picked, "Object", None)
|
|
|
|
|
if obj is None:
|
|
|
|
|
continue
|
|
|
|
|
subelement_names = list(getattr(picked, "SubElementNames", []) or [])
|
|
|
|
|
for index, sub_object in enumerate(list(getattr(picked, "SubObjects", []) or [])):
|
|
|
|
|
shape_type = (getattr(sub_object, "ShapeType", "") or "").strip().lower()
|
|
|
|
|
if shape_type != "face":
|
|
|
|
|
continue
|
|
|
|
|
point = _face_anchor_point(picked, sub_object)
|
|
|
|
|
normal = _face_normal(sub_object)
|
|
|
|
|
if point is None or normal is None:
|
|
|
|
|
continue
|
|
|
|
|
refs.append(
|
|
|
|
|
{
|
|
|
|
|
"object": obj,
|
|
|
|
|
"face": sub_object,
|
|
|
|
|
"point": point,
|
|
|
|
|
"normal": normal,
|
|
|
|
|
"subelement_name": subelement_names[index] if index < len(subelement_names) else "",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
break
|
|
|
|
|
return refs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _has_placement(obj):
|
|
|
|
|
return getattr(obj, "Placement", None) is not None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _contact_transform_object(obj):
|
|
|
|
|
carrier = _carrier_object_from_object(obj)
|
|
|
|
|
if carrier is not None and _has_placement(carrier):
|
|
|
|
|
return carrier
|
|
|
|
|
|
|
|
|
|
best = obj if _has_placement(obj) else None
|
|
|
|
|
current = obj
|
|
|
|
|
visited = set()
|
|
|
|
|
while current is not None and id(current) not in visited:
|
|
|
|
|
visited.add(id(current))
|
|
|
|
|
parents = list(getattr(current, "InList", []) or [])
|
|
|
|
|
parent = parents[0] if parents else None
|
|
|
|
|
if parent is None:
|
|
|
|
|
break
|
|
|
|
|
if _has_placement(parent):
|
|
|
|
|
name = getattr(parent, "Name", "") or ""
|
|
|
|
|
if (
|
|
|
|
|
name.startswith("QETDevice_")
|
|
|
|
|
or (getattr(parent, "QetInstanceId", "") or "").strip()
|
|
|
|
|
or (getattr(parent, "QetCarrierKind", "") or "").strip()
|
|
|
|
|
):
|
|
|
|
|
best = parent
|
|
|
|
|
current = parent
|
|
|
|
|
return best
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _rotation_for_face_contact(moving_normal, target_normal):
|
|
|
|
|
desired = _vector_scale(target_normal, -1.0)
|
|
|
|
|
moving = _normalize_vector(moving_normal)
|
|
|
|
|
desired = _normalize_vector(desired)
|
|
|
|
|
if moving is None or desired is None or _vectors_close(moving, desired):
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
return App.Rotation(moving, desired)
|
|
|
|
|
except Exception:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _normal_contact_translation(target_point, target_normal, moving_point):
|
|
|
|
|
normal = _normalize_vector(target_normal)
|
|
|
|
|
if normal is None:
|
|
|
|
|
return None
|
|
|
|
|
signed_distance = _vector_dot(_vector_sub(moving_point, target_point), normal)
|
|
|
|
|
return _vector_scale(normal, -signed_distance)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _rotate_object_about_point(obj, rotation, pivot):
|
|
|
|
|
if rotation is None:
|
|
|
|
|
return False
|
|
|
|
|
placement = getattr(obj, "Placement", None)
|
|
|
|
|
if placement is None or not hasattr(rotation, "multVec"):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
base = getattr(placement, "Base", None)
|
|
|
|
|
if base is None:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
rotated_base = _vector_add(pivot, rotation.multVec(_vector_sub(base, pivot)))
|
|
|
|
|
except Exception:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
old_rotation = getattr(placement, "Rotation", None)
|
|
|
|
|
new_rotation = old_rotation
|
|
|
|
|
try:
|
|
|
|
|
if hasattr(rotation, "multiply"):
|
|
|
|
|
new_rotation = rotation.multiply(old_rotation)
|
|
|
|
|
except Exception:
|
|
|
|
|
new_rotation = old_rotation
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
obj.Placement = App.Placement(rotated_base, new_rotation)
|
|
|
|
|
return True
|
|
|
|
|
except Exception:
|
|
|
|
|
try:
|
|
|
|
|
placement.Base = rotated_base
|
|
|
|
|
placement.Rotation = new_rotation
|
|
|
|
|
obj.Placement = placement
|
|
|
|
|
return True
|
|
|
|
|
except Exception:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _translate_object(obj, translation):
|
|
|
|
|
placement = getattr(obj, "Placement", None)
|
|
|
|
|
base = getattr(placement, "Base", None)
|
|
|
|
|
if placement is None or base is None:
|
|
|
|
|
raise ManualWiringPanelError("所选对象没有可移动的 Placement,不能执行贴合。")
|
|
|
|
|
|
|
|
|
|
new_base = _vector_add(base, translation)
|
|
|
|
|
try:
|
|
|
|
|
placement.Base = new_base
|
|
|
|
|
obj.Placement = placement
|
|
|
|
|
except Exception:
|
|
|
|
|
obj.Placement = App.Placement(new_base, getattr(placement, "Rotation", App.Rotation()))
|
|
|
|
|
return new_base
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _selected_point():
|
|
|
|
|
for picked in _selection_ex():
|
|
|
|
|
picked_points = list(getattr(picked, "PickedPoints", []) or [])
|
|
|
|
|
@ -377,32 +607,36 @@ def _import_carrier_objects_from_path(doc, path):
|
|
|
|
|
if not _supported_carrier_asset(path):
|
|
|
|
|
raise ManualWiringPanelError("请选择 STEP/STP/FCStd 线槽或导轨模型。")
|
|
|
|
|
|
|
|
|
|
suffix = Path(path).suffix.lower()
|
|
|
|
|
if suffix == ".fcstd":
|
|
|
|
|
source_doc = None
|
|
|
|
|
try:
|
|
|
|
|
source_doc = _open_fcstd_source(path)
|
|
|
|
|
copied = []
|
|
|
|
|
for source_obj in _top_level_objects(list(getattr(source_doc, "Objects", []) or [])):
|
|
|
|
|
copied.append(doc.copyObject(source_obj, True))
|
|
|
|
|
return copied
|
|
|
|
|
finally:
|
|
|
|
|
if source_doc is not None:
|
|
|
|
|
try:
|
|
|
|
|
App.closeDocument(source_doc.Name)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
if ImportGui is None:
|
|
|
|
|
raise ManualWiringPanelError("当前 FreeCAD 无法导入 STEP/STP 文件。")
|
|
|
|
|
before = _existing_object_names(doc)
|
|
|
|
|
ImportGui.insert(
|
|
|
|
|
name=path,
|
|
|
|
|
docName=doc.Name,
|
|
|
|
|
merge=False,
|
|
|
|
|
useLinkGroup=True,
|
|
|
|
|
)
|
|
|
|
|
return _top_level_objects(_new_objects_since(doc, before))
|
|
|
|
|
try:
|
|
|
|
|
suffix = Path(path).suffix.lower()
|
|
|
|
|
if suffix == ".fcstd":
|
|
|
|
|
source_doc = None
|
|
|
|
|
try:
|
|
|
|
|
source_doc = _open_fcstd_source(path)
|
|
|
|
|
copied = []
|
|
|
|
|
for source_obj in _top_level_objects(list(getattr(source_doc, "Objects", []) or [])):
|
|
|
|
|
copied.append(doc.copyObject(source_obj, True))
|
|
|
|
|
return copied
|
|
|
|
|
finally:
|
|
|
|
|
if source_doc is not None:
|
|
|
|
|
try:
|
|
|
|
|
App.closeDocument(source_doc.Name)
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
if ImportGui is None:
|
|
|
|
|
raise ManualWiringPanelError("当前 FreeCAD 无法导入 STEP/STP 文件。")
|
|
|
|
|
before = _existing_object_names(doc)
|
|
|
|
|
ImportGui.insert(
|
|
|
|
|
name=path,
|
|
|
|
|
docName=doc.Name,
|
|
|
|
|
merge=False,
|
|
|
|
|
useLinkGroup=True,
|
|
|
|
|
)
|
|
|
|
|
return _top_level_objects(_new_objects_since(doc, before))
|
|
|
|
|
finally:
|
|
|
|
|
_activate_document(doc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_float_property(obj, prop_name, value, description):
|
|
|
|
|
@ -776,30 +1010,33 @@ class ManualWiringController:
|
|
|
|
|
raise ManualWiringPanelError("找不到内置线槽/导轨资产。")
|
|
|
|
|
|
|
|
|
|
doc = _active_document()
|
|
|
|
|
project_uuid = getattr(TerminalObjects.ensure_root_group(doc), "QetProjectUuid", "").strip()
|
|
|
|
|
carrier_group = WiringObjects.ensure_carrier_group(doc, project_uuid)
|
|
|
|
|
imported = list((importer or _import_carrier_objects_from_path)(doc, path) or [])
|
|
|
|
|
if not imported:
|
|
|
|
|
raise ManualWiringPanelError("没有从模型文件导入任何对象。")
|
|
|
|
|
|
|
|
|
|
role_label = _carrier_role_label(carrier_kind)
|
|
|
|
|
container_name = "QETCarrier_{0}".format(TerminalObjects.safe_token(carrier_kind))
|
|
|
|
|
container = doc.addObject("App::DocumentObjectGroup", container_name)
|
|
|
|
|
container.Label = "QET {0}".format(role_label or carrier_kind)
|
|
|
|
|
carrier_group.addObject(container)
|
|
|
|
|
for obj in imported:
|
|
|
|
|
if obj not in getattr(container, "Group", []):
|
|
|
|
|
container.addObject(obj)
|
|
|
|
|
|
|
|
|
|
_set_carrier_properties(container, carrier_kind, source_path=path)
|
|
|
|
|
for obj in imported:
|
|
|
|
|
_set_carrier_properties(obj, carrier_kind, source_path=path)
|
|
|
|
|
_apply_carrier_length(container, length_mm)
|
|
|
|
|
try:
|
|
|
|
|
doc.recompute()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
return container
|
|
|
|
|
project_uuid = getattr(TerminalObjects.ensure_root_group(doc), "QetProjectUuid", "").strip()
|
|
|
|
|
carrier_group = WiringObjects.ensure_carrier_group(doc, project_uuid)
|
|
|
|
|
imported = list((importer or _import_carrier_objects_from_path)(doc, path) or [])
|
|
|
|
|
if not imported:
|
|
|
|
|
raise ManualWiringPanelError("没有从模型文件导入任何对象。")
|
|
|
|
|
|
|
|
|
|
role_label = _carrier_role_label(carrier_kind)
|
|
|
|
|
container_name = "QETCarrier_{0}".format(TerminalObjects.safe_token(carrier_kind))
|
|
|
|
|
container = doc.addObject("App::DocumentObjectGroup", container_name)
|
|
|
|
|
container.Label = "QET {0}".format(role_label or carrier_kind)
|
|
|
|
|
carrier_group.addObject(container)
|
|
|
|
|
for obj in imported:
|
|
|
|
|
if obj not in getattr(container, "Group", []):
|
|
|
|
|
container.addObject(obj)
|
|
|
|
|
|
|
|
|
|
_set_carrier_properties(container, carrier_kind, source_path=path)
|
|
|
|
|
for obj in imported:
|
|
|
|
|
_set_carrier_properties(obj, carrier_kind, source_path=path)
|
|
|
|
|
_apply_carrier_length(container, length_mm)
|
|
|
|
|
try:
|
|
|
|
|
doc.recompute()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
return container
|
|
|
|
|
finally:
|
|
|
|
|
_activate_document(doc)
|
|
|
|
|
|
|
|
|
|
def apply_length_to_selected_carriers(self, length_mm):
|
|
|
|
|
selected = _selected_carrier_objects()
|
|
|
|
|
@ -818,6 +1055,44 @@ class ManualWiringController:
|
|
|
|
|
pass
|
|
|
|
|
return updated
|
|
|
|
|
|
|
|
|
|
def align_selected_contact_faces(self):
|
|
|
|
|
refs = _selected_contact_face_refs()
|
|
|
|
|
if len(refs) < 2:
|
|
|
|
|
raise ManualWiringPanelError("请先选择目标面,再按 Ctrl 选择要移动对象的接触面。")
|
|
|
|
|
if len(refs) > 2:
|
|
|
|
|
raise ManualWiringPanelError("只能选择两个面:第一个是目标面,第二个是要移动对象的接触面。")
|
|
|
|
|
|
|
|
|
|
target = refs[0]
|
|
|
|
|
moving = refs[1]
|
|
|
|
|
moving_object = _contact_transform_object(moving["object"])
|
|
|
|
|
if moving_object is None:
|
|
|
|
|
raise ManualWiringPanelError("没有找到可移动的对象。")
|
|
|
|
|
|
|
|
|
|
rotation = _rotation_for_face_contact(moving["normal"], target["normal"])
|
|
|
|
|
rotated = _rotate_object_about_point(moving_object, rotation, moving["point"])
|
|
|
|
|
translation = _normal_contact_translation(
|
|
|
|
|
target["point"],
|
|
|
|
|
target["normal"],
|
|
|
|
|
moving["point"],
|
|
|
|
|
)
|
|
|
|
|
if translation is None:
|
|
|
|
|
raise ManualWiringPanelError("无法读取目标面的法向,不能执行贴合。")
|
|
|
|
|
_translate_object(moving_object, translation)
|
|
|
|
|
try:
|
|
|
|
|
_active_document().recompute()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
_activate_document(_active_document())
|
|
|
|
|
return {
|
|
|
|
|
"target_object": target["object"],
|
|
|
|
|
"moving_object": moving_object,
|
|
|
|
|
"target_point": target["point"],
|
|
|
|
|
"moving_point": moving["point"],
|
|
|
|
|
"translation": translation,
|
|
|
|
|
"translation_mode": "normal",
|
|
|
|
|
"rotated": rotated,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _clear_preview_objects(self):
|
|
|
|
|
doc = getattr(App, "ActiveDocument", None)
|
|
|
|
|
if doc is None:
|
|
|
|
|
@ -1108,6 +1383,7 @@ class ManualWiringTaskPanel:
|
|
|
|
|
self.mark_duct_button = QtWidgets.QPushButton("标记为线槽")
|
|
|
|
|
self.mark_cabinet_button = QtWidgets.QPushButton("标记为柜面")
|
|
|
|
|
self.mark_rail_button = QtWidgets.QPushButton("标记为导轨")
|
|
|
|
|
self.align_faces_button = QtWidgets.QPushButton("贴合到选中面")
|
|
|
|
|
self.waypoint_button = QtWidgets.QPushButton("添加折点")
|
|
|
|
|
self.delete_waypoint_button = QtWidgets.QPushButton("删除最后折点")
|
|
|
|
|
self.end_button = QtWidgets.QPushButton("设为终点并生成")
|
|
|
|
|
@ -1140,6 +1416,7 @@ class ManualWiringTaskPanel:
|
|
|
|
|
carrier_layout.addWidget(self.mark_cabinet_button)
|
|
|
|
|
carrier_layout.addWidget(self.mark_rail_button)
|
|
|
|
|
layout.addLayout(carrier_layout)
|
|
|
|
|
layout.addWidget(self.align_faces_button)
|
|
|
|
|
layout.addWidget(self.start_button)
|
|
|
|
|
layout.addWidget(self.waypoint_button)
|
|
|
|
|
layout.addWidget(self.delete_waypoint_button)
|
|
|
|
|
@ -1171,6 +1448,7 @@ class ManualWiringTaskPanel:
|
|
|
|
|
self.mark_duct_button.clicked.connect(self.mark_wire_duct)
|
|
|
|
|
self.mark_cabinet_button.clicked.connect(self.mark_cabinet)
|
|
|
|
|
self.mark_rail_button.clicked.connect(self.mark_rail)
|
|
|
|
|
self.align_faces_button.clicked.connect(self.align_selected_contact_faces)
|
|
|
|
|
self.start_button.clicked.connect(self.set_start)
|
|
|
|
|
self.waypoint_button.clicked.connect(self.add_waypoint)
|
|
|
|
|
self.delete_waypoint_button.clicked.connect(self.delete_last_waypoint)
|
|
|
|
|
@ -1323,6 +1601,14 @@ class ManualWiringTaskPanel:
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
self._set_error(str(exc))
|
|
|
|
|
|
|
|
|
|
def align_selected_contact_faces(self):
|
|
|
|
|
try:
|
|
|
|
|
result = self.controller.align_selected_contact_faces()
|
|
|
|
|
moving_label = getattr(result.get("moving_object"), "Label", "") or getattr(result.get("moving_object"), "Name", "")
|
|
|
|
|
self._set_status("已贴合对象:{0}".format(moving_label or "选中对象"))
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
self._set_error(str(exc))
|
|
|
|
|
|
|
|
|
|
def set_start(self):
|
|
|
|
|
try:
|
|
|
|
|
terminal = self.controller.set_start_from_selection()
|
|
|
|
|
|