|
|
|
@ -1,4 +1,5 @@
|
|
|
|
import math
|
|
|
|
import math
|
|
|
|
|
|
|
|
import re
|
|
|
|
from pathlib import Path
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
|
|
import FreeCAD as App
|
|
|
|
import FreeCAD as App
|
|
|
|
@ -15,6 +16,33 @@ class BatchAssemblyError(RuntimeError):
|
|
|
|
pass
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TERMINAL_STRIP_NAME_PROPERTIES = (
|
|
|
|
|
|
|
|
"QetTerminalStripName",
|
|
|
|
|
|
|
|
"QetTerminalBlockName",
|
|
|
|
|
|
|
|
"QetTerminalGroupName",
|
|
|
|
|
|
|
|
"QetStripName",
|
|
|
|
|
|
|
|
"QetParentTerminalBlockName",
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TERMINAL_STRIP_ORDER_PROPERTIES = (
|
|
|
|
|
|
|
|
"QetTerminalStripIndex",
|
|
|
|
|
|
|
|
"QetTerminalIndex",
|
|
|
|
|
|
|
|
"QetTerminalSequence",
|
|
|
|
|
|
|
|
"QetTerminalOrder",
|
|
|
|
|
|
|
|
"QetTerminalNo",
|
|
|
|
|
|
|
|
"QetTerminalDisplay",
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEVICE_PREFIX_PROPERTIES = (
|
|
|
|
|
|
|
|
"QetDeviceTag",
|
|
|
|
|
|
|
|
"QetDeviceName",
|
|
|
|
|
|
|
|
"QetDisplayTag",
|
|
|
|
|
|
|
|
"QetSymbolLabel",
|
|
|
|
|
|
|
|
"QetInstanceId",
|
|
|
|
|
|
|
|
"QetElementUuid",
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _project_uuid(doc):
|
|
|
|
def _project_uuid(doc):
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
root = TerminalObjects.ensure_root_group(doc)
|
|
|
|
root = TerminalObjects.ensure_root_group(doc)
|
|
|
|
@ -28,6 +56,198 @@ def _safe_label(text, fallback):
|
|
|
|
return value or fallback
|
|
|
|
return value or fallback
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _text_values(obj, include_children=False):
|
|
|
|
|
|
|
|
values = []
|
|
|
|
|
|
|
|
for attr_name in ("Label", "Name"):
|
|
|
|
|
|
|
|
value = (getattr(obj, attr_name, "") or "").strip()
|
|
|
|
|
|
|
|
if value:
|
|
|
|
|
|
|
|
values.append(value)
|
|
|
|
|
|
|
|
for prop_name in DEVICE_PREFIX_PROPERTIES + TERMINAL_STRIP_NAME_PROPERTIES:
|
|
|
|
|
|
|
|
value = (getattr(obj, prop_name, "") or "").strip()
|
|
|
|
|
|
|
|
if value:
|
|
|
|
|
|
|
|
values.append(value)
|
|
|
|
|
|
|
|
if include_children:
|
|
|
|
|
|
|
|
for child in list(getattr(obj, "Group", []) or []):
|
|
|
|
|
|
|
|
for attr_name in ("Label", "Name"):
|
|
|
|
|
|
|
|
value = (getattr(child, attr_name, "") or "").strip()
|
|
|
|
|
|
|
|
if value:
|
|
|
|
|
|
|
|
values.append(value)
|
|
|
|
|
|
|
|
return values
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _natural_sort_key(value):
|
|
|
|
|
|
|
|
text = str(value or "")
|
|
|
|
|
|
|
|
key = []
|
|
|
|
|
|
|
|
for part in re.split(r"(\d+)", text):
|
|
|
|
|
|
|
|
if part.isdigit():
|
|
|
|
|
|
|
|
key.append((0, int(part)))
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
key.append((1, part.lower()))
|
|
|
|
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_strip_name_and_order(obj):
|
|
|
|
|
|
|
|
for prop_name in TERMINAL_STRIP_NAME_PROPERTIES:
|
|
|
|
|
|
|
|
strip_name = (getattr(obj, prop_name, "") or "").strip()
|
|
|
|
|
|
|
|
if not strip_name:
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
order = _explicit_order(obj)
|
|
|
|
|
|
|
|
if order is None:
|
|
|
|
|
|
|
|
order = _order_from_texts(_text_values(obj))
|
|
|
|
|
|
|
|
return strip_name, order
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for text in _text_values(obj):
|
|
|
|
|
|
|
|
# Examples from QET trees: UD:1, UD-2, ID_006.
|
|
|
|
|
|
|
|
match = re.match(r"^\s*([A-Za-z][A-Za-z0-9]{0,8})\s*[::_\-]\s*(\d+)\b", text)
|
|
|
|
|
|
|
|
if match:
|
|
|
|
|
|
|
|
return match.group(1), int(match.group(2))
|
|
|
|
|
|
|
|
return "", None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _explicit_order(obj):
|
|
|
|
|
|
|
|
for prop_name in TERMINAL_STRIP_ORDER_PROPERTIES:
|
|
|
|
|
|
|
|
value = (getattr(obj, prop_name, "") or "").strip()
|
|
|
|
|
|
|
|
if not value:
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
match = re.search(r"\d+", value)
|
|
|
|
|
|
|
|
if match:
|
|
|
|
|
|
|
|
return int(match.group(0))
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _order_from_texts(texts):
|
|
|
|
|
|
|
|
for text in texts:
|
|
|
|
|
|
|
|
match = re.search(r"(\d+)(?!.*\d)", str(text or ""))
|
|
|
|
|
|
|
|
if match:
|
|
|
|
|
|
|
|
return int(match.group(1))
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_group_like(obj):
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
return bool(obj and obj.isDerivedFrom("App::DocumentObjectGroup"))
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
return bool(getattr(obj, "Group", None) is not None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _qet_identity(obj):
|
|
|
|
|
|
|
|
instance_id = (getattr(obj, "QetInstanceId", "") or "").strip()
|
|
|
|
|
|
|
|
element_uuid = (getattr(obj, "QetElementUuid", "") or "").strip()
|
|
|
|
|
|
|
|
return instance_id, element_uuid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_qet_device_object(obj):
|
|
|
|
|
|
|
|
if obj is None:
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
name = getattr(obj, "Name", "") or ""
|
|
|
|
|
|
|
|
instance_id, element_uuid = _qet_identity(obj)
|
|
|
|
|
|
|
|
if name.startswith(TerminalObjects.DEVICE_GROUP_PREFIX):
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
return bool(instance_id or element_uuid)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _contains_terminal_slice_geometry(obj):
|
|
|
|
|
|
|
|
text = " ".join(_text_values(obj, include_children=True)).lower()
|
|
|
|
|
|
|
|
return any(
|
|
|
|
|
|
|
|
token in text
|
|
|
|
|
|
|
|
for token in (
|
|
|
|
|
|
|
|
"terminalslice",
|
|
|
|
|
|
|
|
"terminal_slice",
|
|
|
|
|
|
|
|
"terminal slice",
|
|
|
|
|
|
|
|
"get_terminal_slice",
|
|
|
|
|
|
|
|
"端子片",
|
|
|
|
|
|
|
|
"端子排",
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _contains_qet_terminal_group(obj):
|
|
|
|
|
|
|
|
for child in list(getattr(obj, "Group", []) or []):
|
|
|
|
|
|
|
|
if (getattr(child, "QetGroupKind", "") or "").strip() == TerminalObjects.TERMINAL_GROUP_KIND:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
if getattr(child, "Name", "").startswith(TerminalObjects.TERMINAL_GROUP_PREFIX):
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_terminal_strip_device(obj):
|
|
|
|
|
|
|
|
if not _is_qet_device_object(obj):
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
strip_name, _order = _parse_strip_name_and_order(obj)
|
|
|
|
|
|
|
|
if not strip_name:
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
if _contains_terminal_slice_geometry(obj):
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
if _contains_qet_terminal_group(obj):
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _matches_prefix(obj, prefix):
|
|
|
|
|
|
|
|
prefix = (prefix or "").strip().lower()
|
|
|
|
|
|
|
|
if not prefix:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
for text in _text_values(obj):
|
|
|
|
|
|
|
|
if text.lower().startswith(prefix):
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _is_batch_generated(obj):
|
|
|
|
|
|
|
|
return bool((getattr(obj, "QetBatchAssemblyKind", "") or "").strip())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _existing_terminal_strip_devices(doc, strip_name=""):
|
|
|
|
|
|
|
|
wanted = (strip_name or "").strip().lower()
|
|
|
|
|
|
|
|
devices = []
|
|
|
|
|
|
|
|
for obj in list(getattr(doc, "Objects", []) or []):
|
|
|
|
|
|
|
|
if not _is_terminal_strip_device(obj):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
current_strip, order = _parse_strip_name_and_order(obj)
|
|
|
|
|
|
|
|
if wanted and current_strip.lower() != wanted:
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
devices.append((current_strip, order, obj))
|
|
|
|
|
|
|
|
devices.sort(
|
|
|
|
|
|
|
|
key=lambda item: (
|
|
|
|
|
|
|
|
item[0].lower(),
|
|
|
|
|
|
|
|
item[1] if item[1] is not None else 10**9,
|
|
|
|
|
|
|
|
_natural_sort_key(getattr(item[2], "Label", "") or getattr(item[2], "Name", "")),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
return [obj for _strip, _order, obj in devices]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def available_terminal_strip_names(doc):
|
|
|
|
|
|
|
|
names = []
|
|
|
|
|
|
|
|
seen = set()
|
|
|
|
|
|
|
|
for obj in list(getattr(doc, "Objects", []) or []):
|
|
|
|
|
|
|
|
if not _is_terminal_strip_device(obj):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
strip_name, _order = _parse_strip_name_and_order(obj)
|
|
|
|
|
|
|
|
key = strip_name.lower()
|
|
|
|
|
|
|
|
if strip_name and key not in seen:
|
|
|
|
|
|
|
|
seen.add(key)
|
|
|
|
|
|
|
|
names.append(strip_name)
|
|
|
|
|
|
|
|
names.sort(key=_natural_sort_key)
|
|
|
|
|
|
|
|
return names
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _existing_devices_by_prefix(doc, prefix=""):
|
|
|
|
|
|
|
|
devices = []
|
|
|
|
|
|
|
|
for obj in list(getattr(doc, "Objects", []) or []):
|
|
|
|
|
|
|
|
if not _is_qet_device_object(obj):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
if _is_terminal_strip_device(obj):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
if _is_batch_generated(obj):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
if not _matches_prefix(obj, prefix):
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
devices.append(obj)
|
|
|
|
|
|
|
|
devices.sort(key=lambda obj: _natural_sort_key(getattr(obj, "Label", "") or getattr(obj, "Name", "")))
|
|
|
|
|
|
|
|
return devices
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _axis_vector(rail):
|
|
|
|
def _axis_vector(rail):
|
|
|
|
axis = (getattr(rail, "QetCarrierAxis", "") or "x").strip().lower()
|
|
|
|
axis = (getattr(rail, "QetCarrierAxis", "") or "x").strip().lower()
|
|
|
|
if axis == "y":
|
|
|
|
if axis == "y":
|
|
|
|
@ -76,6 +296,103 @@ def _placement_at(rail, point):
|
|
|
|
return App.Placement(point, rotation or App.Rotation())
|
|
|
|
return App.Placement(point, rotation or App.Rotation())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vector_copy(vector):
|
|
|
|
|
|
|
|
return App.Vector(
|
|
|
|
|
|
|
|
float(getattr(vector, "x", 0.0) or 0.0),
|
|
|
|
|
|
|
|
float(getattr(vector, "y", 0.0) or 0.0),
|
|
|
|
|
|
|
|
float(getattr(vector, "z", 0.0) or 0.0),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vector_add(left, right):
|
|
|
|
|
|
|
|
return App.Vector(
|
|
|
|
|
|
|
|
float(getattr(left, "x", 0.0) or 0.0) + float(getattr(right, "x", 0.0) or 0.0),
|
|
|
|
|
|
|
|
float(getattr(left, "y", 0.0) or 0.0) + float(getattr(right, "y", 0.0) or 0.0),
|
|
|
|
|
|
|
|
float(getattr(left, "z", 0.0) or 0.0) + float(getattr(right, "z", 0.0) or 0.0),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vector_sub(left, right):
|
|
|
|
|
|
|
|
return App.Vector(
|
|
|
|
|
|
|
|
float(getattr(left, "x", 0.0) or 0.0) - float(getattr(right, "x", 0.0) or 0.0),
|
|
|
|
|
|
|
|
float(getattr(left, "y", 0.0) or 0.0) - float(getattr(right, "y", 0.0) or 0.0),
|
|
|
|
|
|
|
|
float(getattr(left, "z", 0.0) or 0.0) - float(getattr(right, "z", 0.0) or 0.0),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _placement_base(obj):
|
|
|
|
|
|
|
|
placement = getattr(obj, "Placement", None)
|
|
|
|
|
|
|
|
base = getattr(placement, "Base", None)
|
|
|
|
|
|
|
|
if base is None:
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
return _vector_copy(base)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _placement_controls_children(obj):
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
return bool(obj is not None and obj.isDerivedFrom("App::Part"))
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _iter_transform_children(obj):
|
|
|
|
|
|
|
|
for child in list(getattr(obj, "Group", []) or []):
|
|
|
|
|
|
|
|
yield child
|
|
|
|
|
|
|
|
if not _placement_controls_children(child):
|
|
|
|
|
|
|
|
for nested in _iter_transform_children(child):
|
|
|
|
|
|
|
|
yield nested
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _object_anchor_point(obj):
|
|
|
|
|
|
|
|
if obj is None:
|
|
|
|
|
|
|
|
return App.Vector(0, 0, 0)
|
|
|
|
|
|
|
|
children = list(getattr(obj, "Group", []) or [])
|
|
|
|
|
|
|
|
if _placement_controls_children(obj) or not children:
|
|
|
|
|
|
|
|
return _placement_base(obj) or App.Vector(0, 0, 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
points = []
|
|
|
|
|
|
|
|
for child in _iter_transform_children(obj):
|
|
|
|
|
|
|
|
base = _placement_base(child)
|
|
|
|
|
|
|
|
if base is not None:
|
|
|
|
|
|
|
|
points.append(base)
|
|
|
|
|
|
|
|
if not points:
|
|
|
|
|
|
|
|
return _placement_base(obj) or App.Vector(0, 0, 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return App.Vector(
|
|
|
|
|
|
|
|
sum(point.x for point in points) / len(points),
|
|
|
|
|
|
|
|
sum(point.y for point in points) / len(points),
|
|
|
|
|
|
|
|
sum(point.z for point in points) / len(points),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _translate_placement(obj, delta):
|
|
|
|
|
|
|
|
placement = getattr(obj, "Placement", None)
|
|
|
|
|
|
|
|
base = getattr(placement, "Base", None)
|
|
|
|
|
|
|
|
if placement is None or base is None:
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
new_base = _vector_add(base, delta)
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
placement.Base = new_base
|
|
|
|
|
|
|
|
obj.Placement = placement
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
obj.Placement = App.Placement(new_base, getattr(placement, "Rotation", App.Rotation()))
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _translate_group_children(obj, delta):
|
|
|
|
|
|
|
|
moved = 0
|
|
|
|
|
|
|
|
for child in list(getattr(obj, "Group", []) or []):
|
|
|
|
|
|
|
|
if _translate_placement(child, delta):
|
|
|
|
|
|
|
|
moved += 1
|
|
|
|
|
|
|
|
if not _placement_controls_children(child):
|
|
|
|
|
|
|
|
moved += _translate_group_children(child, delta)
|
|
|
|
|
|
|
|
return moved
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_rail(rail):
|
|
|
|
def _ensure_rail(rail):
|
|
|
|
if rail is None:
|
|
|
|
if rail is None:
|
|
|
|
raise BatchAssemblyError("请先选择一根导轨。")
|
|
|
|
raise BatchAssemblyError("请先选择一根导轨。")
|
|
|
|
@ -222,6 +539,67 @@ def _set_batch_properties(obj, kind, batch_name, host):
|
|
|
|
"Mount host object name",
|
|
|
|
"Mount host object name",
|
|
|
|
getattr(host, "Name", "") or "",
|
|
|
|
getattr(host, "Name", "") or "",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
TerminalObjects.ensure_string_property(
|
|
|
|
|
|
|
|
obj,
|
|
|
|
|
|
|
|
"QetMountHostKind",
|
|
|
|
|
|
|
|
"QET Mount",
|
|
|
|
|
|
|
|
"Mount host kind",
|
|
|
|
|
|
|
|
(getattr(host, "QetCarrierKind", "") or "").strip(),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_layout_properties(obj, kind, batch_name, host, order_index, offset_mm):
|
|
|
|
|
|
|
|
_set_batch_properties(obj, kind, batch_name, host)
|
|
|
|
|
|
|
|
TerminalObjects.ensure_string_property(
|
|
|
|
|
|
|
|
obj,
|
|
|
|
|
|
|
|
"QetBatchAssemblyMode",
|
|
|
|
|
|
|
|
"QET Batch Assembly",
|
|
|
|
|
|
|
|
"Batch assembly mode",
|
|
|
|
|
|
|
|
"layout_existing",
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
TerminalObjects.ensure_string_property(
|
|
|
|
|
|
|
|
obj,
|
|
|
|
|
|
|
|
"QetBatchAssemblyOrder",
|
|
|
|
|
|
|
|
"QET Batch Assembly",
|
|
|
|
|
|
|
|
"Batch assembly order",
|
|
|
|
|
|
|
|
str(int(order_index)),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
TerminalObjects.ensure_string_property(
|
|
|
|
|
|
|
|
obj,
|
|
|
|
|
|
|
|
"QetBatchAssemblyOffsetMm",
|
|
|
|
|
|
|
|
"QET Batch Assembly",
|
|
|
|
|
|
|
|
"Batch assembly offset in millimeters",
|
|
|
|
|
|
|
|
"{0:.6f}".format(float(offset_mm or 0.0)),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_object_placement(obj, placement):
|
|
|
|
|
|
|
|
if obj is not None and getattr(obj, "Group", None) and not _placement_controls_children(obj):
|
|
|
|
|
|
|
|
current = _object_anchor_point(obj)
|
|
|
|
|
|
|
|
target = getattr(placement, "Base", App.Vector(0, 0, 0))
|
|
|
|
|
|
|
|
delta = _vector_sub(target, current)
|
|
|
|
|
|
|
|
moved_children = _translate_group_children(obj, delta)
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
obj.Placement = placement
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
if moved_children:
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
obj.Placement = placement
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
existing = getattr(obj, "Placement", None)
|
|
|
|
|
|
|
|
if existing is not None:
|
|
|
|
|
|
|
|
existing.Base = placement.Base
|
|
|
|
|
|
|
|
existing.Rotation = placement.Rotation
|
|
|
|
|
|
|
|
obj.Placement = existing
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_qet_device_properties(obj, project_uuid, element_uuid, instance_id):
|
|
|
|
def _set_qet_device_properties(obj, project_uuid, element_uuid, instance_id):
|
|
|
|
@ -367,7 +745,17 @@ def _create_terminal(
|
|
|
|
return terminal
|
|
|
|
return terminal
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _batch_report(kind, group, devices, terminals):
|
|
|
|
def _terminal_objects_for_devices(devices):
|
|
|
|
|
|
|
|
terminals = []
|
|
|
|
|
|
|
|
for device in devices or []:
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
terminals.extend(TerminalObjects.collect_terminal_objects(device))
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
return terminals
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _batch_report(kind, group, devices, terminals, source="fallback_created"):
|
|
|
|
return {
|
|
|
|
return {
|
|
|
|
"kind": kind,
|
|
|
|
"kind": kind,
|
|
|
|
"group": group,
|
|
|
|
"group": group,
|
|
|
|
@ -375,9 +763,106 @@ def _batch_report(kind, group, devices, terminals):
|
|
|
|
"terminals": terminals,
|
|
|
|
"terminals": terminals,
|
|
|
|
"created_devices": len(devices),
|
|
|
|
"created_devices": len(devices),
|
|
|
|
"created_terminals": len(terminals),
|
|
|
|
"created_terminals": len(terminals),
|
|
|
|
|
|
|
|
"updated_devices": 0,
|
|
|
|
|
|
|
|
"updated_terminals": 0,
|
|
|
|
|
|
|
|
"source": source,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _layout_existing_objects(
|
|
|
|
|
|
|
|
doc,
|
|
|
|
|
|
|
|
rail,
|
|
|
|
|
|
|
|
objects,
|
|
|
|
|
|
|
|
kind,
|
|
|
|
|
|
|
|
batch_name,
|
|
|
|
|
|
|
|
pitch_mm,
|
|
|
|
|
|
|
|
start_offset_mm,
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
rail = _ensure_rail(rail)
|
|
|
|
|
|
|
|
objects = [obj for obj in objects or [] if obj is not None]
|
|
|
|
|
|
|
|
if not objects:
|
|
|
|
|
|
|
|
return _batch_report(kind, rail, [], [], source="qet_existing")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
base = _base_point(rail)
|
|
|
|
|
|
|
|
axis = _axis_vector(rail)
|
|
|
|
|
|
|
|
updated = []
|
|
|
|
|
|
|
|
for index, obj in enumerate(objects):
|
|
|
|
|
|
|
|
offset = float(start_offset_mm or 0.0) + index * float(pitch_mm or 0.0)
|
|
|
|
|
|
|
|
placement = _placement_at(rail, _point_at(base, axis, offset))
|
|
|
|
|
|
|
|
if _set_object_placement(obj, placement):
|
|
|
|
|
|
|
|
_set_layout_properties(obj, kind, batch_name, rail, index + 1, offset)
|
|
|
|
|
|
|
|
updated.append(obj)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
doc.recompute()
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
terminals = _terminal_objects_for_devices(updated)
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
|
|
"kind": kind,
|
|
|
|
|
|
|
|
"group": rail,
|
|
|
|
|
|
|
|
"devices": updated,
|
|
|
|
|
|
|
|
"terminals": terminals,
|
|
|
|
|
|
|
|
"created_devices": 0,
|
|
|
|
|
|
|
|
"created_terminals": 0,
|
|
|
|
|
|
|
|
"updated_devices": len(updated),
|
|
|
|
|
|
|
|
"updated_terminals": len(terminals),
|
|
|
|
|
|
|
|
"source": "qet_existing",
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def layout_existing_terminal_block(
|
|
|
|
|
|
|
|
doc,
|
|
|
|
|
|
|
|
rail,
|
|
|
|
|
|
|
|
block_name="",
|
|
|
|
|
|
|
|
pitch_mm=5.2,
|
|
|
|
|
|
|
|
start_offset_mm=0.0,
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
if doc is None:
|
|
|
|
|
|
|
|
raise BatchAssemblyError("请先打开 FreeCAD 工程。")
|
|
|
|
|
|
|
|
rail = _ensure_rail(rail)
|
|
|
|
|
|
|
|
devices = _existing_terminal_strip_devices(doc, block_name)
|
|
|
|
|
|
|
|
if not devices:
|
|
|
|
|
|
|
|
return _batch_report("terminal_block", rail, [], [], source="qet_existing")
|
|
|
|
|
|
|
|
batch_name = _safe_label(block_name, _parse_strip_name_and_order(devices[0])[0] or "QET端子排")
|
|
|
|
|
|
|
|
return _layout_existing_objects(
|
|
|
|
|
|
|
|
doc,
|
|
|
|
|
|
|
|
rail,
|
|
|
|
|
|
|
|
devices,
|
|
|
|
|
|
|
|
"terminal_block",
|
|
|
|
|
|
|
|
batch_name,
|
|
|
|
|
|
|
|
pitch_mm,
|
|
|
|
|
|
|
|
start_offset_mm,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def layout_existing_devices(
|
|
|
|
|
|
|
|
doc,
|
|
|
|
|
|
|
|
rail,
|
|
|
|
|
|
|
|
prefix="QF",
|
|
|
|
|
|
|
|
pitch_mm=18.0,
|
|
|
|
|
|
|
|
start_offset_mm=0.0,
|
|
|
|
|
|
|
|
kind="device_batch",
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
if doc is None:
|
|
|
|
|
|
|
|
raise BatchAssemblyError("请先打开 FreeCAD 工程。")
|
|
|
|
|
|
|
|
rail = _ensure_rail(rail)
|
|
|
|
|
|
|
|
devices = _existing_devices_by_prefix(doc, prefix)
|
|
|
|
|
|
|
|
if not devices:
|
|
|
|
|
|
|
|
return _batch_report(kind, rail, [], [], source="qet_existing")
|
|
|
|
|
|
|
|
batch_name = _safe_label(prefix, "QET设备")
|
|
|
|
|
|
|
|
return _layout_existing_objects(
|
|
|
|
|
|
|
|
doc,
|
|
|
|
|
|
|
|
rail,
|
|
|
|
|
|
|
|
devices,
|
|
|
|
|
|
|
|
kind,
|
|
|
|
|
|
|
|
batch_name,
|
|
|
|
|
|
|
|
pitch_mm,
|
|
|
|
|
|
|
|
start_offset_mm,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_terminal_block(
|
|
|
|
def create_terminal_block(
|
|
|
|
doc,
|
|
|
|
doc,
|
|
|
|
rail,
|
|
|
|
rail,
|
|
|
|
|