From 20bcaf18e4aa7074b002af9dace80a745ea50524 Mon Sep 17 00:00:00 2001 From: Zhaowenlong Date: Tue, 26 May 2026 10:54:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E7=AB=AF=E5=AD=90?= =?UTF-8?q?=E5=A4=8D=E7=94=A8=E5=92=8C=E6=89=8B=E5=8A=A8=E5=B8=83=E7=BA=BF?= =?UTF-8?q?=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mod/FreeCADExchange/ManualWiring.py | 89 +++++++++---- src/Mod/FreeCADExchange/ManualWiringPanel.py | 29 ++++- src/Mod/FreeCADExchange/TerminalImport.py | 55 ++++++-- src/Mod/FreeCADExchange/WiringImport.py | 53 ++++++-- ...eecad_exchange_manual_wiring_panel_test.py | 22 ++-- .../freecad_exchange_manual_wiring_test.py | 121 +++++++++++++++++- ...nge_terminal_import_template_slots_test.py | 95 ++++++++++++++ .../freecad_exchange_wiring_import_test.py | 6 + tests/python/freecad_exchange_wiring_test.py | 21 ++- 9 files changed, 424 insertions(+), 67 deletions(-) diff --git a/src/Mod/FreeCADExchange/ManualWiring.py b/src/Mod/FreeCADExchange/ManualWiring.py index 8e28150..38a3b20 100644 --- a/src/Mod/FreeCADExchange/ManualWiring.py +++ b/src/Mod/FreeCADExchange/ManualWiring.py @@ -149,18 +149,14 @@ def _orthogonal_segment_points(start_point, end_point, preferred_axis=None): if _vector_close(start_point, end_point): return [start_point] - axis_order = [] - if preferred_axis in {"x", "y", "z"}: - axis_order.append(preferred_axis) - - remaining = sorted( + axis_order = sorted( ("x", "y", "z"), key=lambda axis: abs(_axis_value(end_point, axis) - _axis_value(start_point, axis)), reverse=True, ) - for axis in remaining: - if axis not in axis_order: - axis_order.append(axis) + if preferred_axis in {"x", "y", "z"} and preferred_axis in axis_order: + axis_order = [axis for axis in axis_order if axis != preferred_axis] + axis_order.append(preferred_axis) points = [start_point] current = start_point @@ -220,6 +216,24 @@ def _manual_waypoints_payload(waypoints): return payload +def _append_unique_point(points, point): + if not points or not _vector_close(points[-1], point): + points.append(point) + + +def _append_orthogonal_segment(points, target_point, preferred_axis=None): + if not points: + points.append(target_point) + return + segment = _orthogonal_segment_points( + points[-1], + target_point, + preferred_axis=preferred_axis, + ) + for point in segment[1:]: + _append_unique_point(points, point) + + def _terminal_points(start_terminal, end_terminal, waypoints=None, terminal_exit_length=0.0): start_origin = TerminalObjects.terminal_origin(start_terminal) end_origin = TerminalObjects.terminal_origin(end_terminal) @@ -228,7 +242,8 @@ def _terminal_points(start_terminal, end_terminal, waypoints=None, terminal_exit points = [start_origin] normalized_waypoints = [] if exit_length > 0: - points.append( + _append_unique_point( + points, _offset_point( start_origin, _terminal_exit_direction(start_terminal), @@ -241,8 +256,11 @@ def _terminal_points(start_terminal, end_terminal, waypoints=None, terminal_exit if waypoint is None: continue normalized_waypoints.append(waypoint) - if not _vector_close(points[-1], waypoint["point"]): - points.append(waypoint["point"]) + _append_orthogonal_segment( + points, + waypoint["point"], + preferred_axis=waypoint.get("support_axis"), + ) if exit_length > 0: end_exit = _offset_point( @@ -250,10 +268,10 @@ def _terminal_points(start_terminal, end_terminal, waypoints=None, terminal_exit _terminal_exit_direction(end_terminal), exit_length, ) - if not _vector_close(points[-1], end_exit): - points.append(end_exit) - if len(points) < 2 or not _vector_close(points[-1], end_origin): - points.append(end_origin) + _append_orthogonal_segment(points, end_exit) + _append_unique_point(points, end_origin) + else: + _append_orthogonal_segment(points, end_origin) return points, normalized_waypoints @@ -374,6 +392,17 @@ def _wire_parent_group(doc, project_uuid, start_terminal, end_terminal, fallback return fallback_group +def _is_legacy_wire_group(group): + if group is None: + return False + if getattr(group, "Name", "").startswith(TerminalObjects.WIRE_GROUP_PREFIX): + return True + return ( + (getattr(group, "QetGroupKind", "") or "").strip() + == TerminalObjects.WIRE_GROUP_KIND + ) + + def create_manual_wire( doc, start_terminal, @@ -437,10 +466,6 @@ def create_manual_wire( manual_waypoints=normalized_waypoints, ) - routed_group = WiringObjects.ensure_routed_group(doc, project_uuid) - if wire_obj not in getattr(routed_group, "Group", []): - routed_group.addObject(wire_obj) - if parent_group is None: try: parent_group = _wire_parent_group( @@ -452,25 +477,33 @@ def create_manual_wire( ) except Exception: parent_group = None - if parent_group is not None and wire_obj not in getattr(parent_group, "Group", []): + + if parent_group is not None and not _is_legacy_wire_group(parent_group): parent_group.addObject(wire_obj) - if parent_group is not None: + elif parent_group is not None: try: - if ( - getattr(parent_group, "Name", "").startswith(TerminalObjects.WIRE_GROUP_PREFIX) - or (getattr(parent_group, "QetGroupKind", "") or "").strip() - == TerminalObjects.WIRE_GROUP_KIND - ): - parent_group.ViewObject.Visibility = False + parent_group.ViewObject.Visibility = False except Exception: pass + routed_group = WiringObjects.ensure_routed_group(doc, project_uuid) + if wire_obj not in getattr(routed_group, "Group", []): + routed_group.addObject(wire_obj) + + try: + wire_obj.ViewObject.Visibility = True + except Exception: + pass + try: + routed_group.ViewObject.Visibility = True + except Exception: + pass try: wire_obj.ViewObject.LineWidth = 2.0 except Exception: pass try: - wire_obj.ViewObject.LineColor = (0.0, 0.6, 1.0) + wire_obj.ViewObject.LineColor = (1.0, 0.1, 0.0) except Exception: pass diff --git a/src/Mod/FreeCADExchange/ManualWiringPanel.py b/src/Mod/FreeCADExchange/ManualWiringPanel.py index d3c3488..a1eaf5e 100644 --- a/src/Mod/FreeCADExchange/ManualWiringPanel.py +++ b/src/Mod/FreeCADExchange/ManualWiringPanel.py @@ -381,6 +381,10 @@ class ManualWiringController: self.preview_objects = [] self.last_wire = None + def set_terminal_exit_length(self, value): + self.terminal_exit_length = max(float(value or 0.0), 0.0) + return self.terminal_exit_length + def _clear_preview_objects(self): doc = getattr(App, "ActiveDocument", None) if doc is None: @@ -523,9 +527,10 @@ class ManualWiringController: ) if len(self.waypoints) > 3: waypoint_text += ";..." - return "任务:{0};起点:{1};折点:{2} 个;最近导线:{3};折点明细:{4}".format( + return "任务:{0};起点:{1};出线:{2:.1f} mm;折点:{3} 个;最近导线:{4};折点明细:{5}".format( task_text, start_text, + self.terminal_exit_length, len(self.waypoints), wire_text, waypoint_text, @@ -546,6 +551,12 @@ class ManualWiringTaskPanel: self.task_list = QtWidgets.QListWidget() self.use_task_button = QtWidgets.QPushButton("选择导线任务") self.reload_tasks_button = QtWidgets.QPushButton("刷新任务") + self.exit_length_input = QtWidgets.QDoubleSpinBox() + self.exit_length_input.setRange(0.0, 1000.0) + self.exit_length_input.setDecimals(1) + self.exit_length_input.setSingleStep(5.0) + self.exit_length_input.setSuffix(" mm") + self.exit_length_input.setValue(self.controller.terminal_exit_length) self.start_button = QtWidgets.QPushButton("设为起点") self.waypoint_button = QtWidgets.QPushButton("添加折点") self.delete_waypoint_button = QtWidgets.QPushButton("删除最后折点") @@ -556,6 +567,10 @@ class ManualWiringTaskPanel: layout.addWidget(self.task_list) layout.addWidget(self.use_task_button) layout.addWidget(self.reload_tasks_button) + exit_layout = QtWidgets.QHBoxLayout() + exit_layout.addWidget(QtWidgets.QLabel("端子出线长度")) + exit_layout.addWidget(self.exit_length_input) + layout.addLayout(exit_layout) layout.addWidget(self.start_button) layout.addWidget(self.waypoint_button) layout.addWidget(self.delete_waypoint_button) @@ -573,6 +588,7 @@ class ManualWiringTaskPanel: self.task_objects = [] self.use_task_button.clicked.connect(self.use_selected_task) self.reload_tasks_button.clicked.connect(self._refresh_task_list) + self.exit_length_input.valueChanged.connect(self.set_exit_length) 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) @@ -636,6 +652,13 @@ class ManualWiringTaskPanel: except Exception as exc: self._set_error(str(exc)) + def set_exit_length(self, value): + try: + self.controller.set_terminal_exit_length(value) + self._set_status(self.controller.state_text()) + except Exception as exc: + self._set_error(str(exc)) + def set_start(self): try: terminal = self.controller.set_start_from_selection() @@ -665,6 +688,10 @@ class ManualWiringTaskPanel: def set_end_and_generate(self): try: + try: + self.controller.set_terminal_exit_length(self.exit_length_input.value()) + except Exception: + pass wire = self.controller.set_end_from_selection_and_generate() self._refresh_task_list() self._set_status("已生成导线:{0}".format(getattr(wire, "Name", ""))) diff --git a/src/Mod/FreeCADExchange/TerminalImport.py b/src/Mod/FreeCADExchange/TerminalImport.py index 91368a1..48a5e09 100644 --- a/src/Mod/FreeCADExchange/TerminalImport.py +++ b/src/Mod/FreeCADExchange/TerminalImport.py @@ -109,6 +109,18 @@ def _terminal_existing_index(container): return index +def _terminal_existing_local_by_slot(container): + index = {} + for obj in TerminalObjects.collect_terminal_objects(container): + terminal_uuid = getattr(obj, "QetTerminalUuid", "").strip() + if not TerminalObjects.is_local_terminal_uuid(terminal_uuid): + continue + slot_name = _normalize_slot_name(getattr(obj, "QetTemplateSlotName", "")) + if slot_name and slot_name not in index: + index[slot_name] = obj + return index + + def _device_key(device_group): return getattr(device_group, "QetInstanceId", "").strip() or getattr( device_group, "QetElementUuid", "" @@ -331,7 +343,9 @@ def import_terminals_from_payload(payload, scene_path=""): terminal_group = _terminal_container_for_device(doc, device_group, project_uuid) existing_by_uuid = _terminal_existing_index(terminal_group) + existing_local_by_slot = _terminal_existing_local_by_slot(terminal_group) used_uuids = set() + used_objects = set() used_slot_names = set() template_slots = TemplateSemantics.collect_terminal_hints(device_group) fallback_slots = TemplateSemantics.resolve_terminal_slots( @@ -379,16 +393,34 @@ def import_terminals_from_payload(payload, scene_path=""): terminal_obj = existing_by_uuid.get(terminal_uuid) if terminal_obj is None: - terminal_obj = _create_terminal_object( - doc, - terminal_uuid, - slot, - terminal_group, - project_uuid, - device_element_uuid, - device_instance_id, - ) - report["imported_terminals"] += 1 + terminal_obj = existing_local_by_slot.get(slot_name) + if terminal_obj is not None: + TerminalObjects.set_terminal_semantics( + terminal_obj, + project_uuid, + device_element_uuid, + terminal_uuid, + device_instance_id, + label=_terminal_slot_label(slot, terminal_uuid), + slot_name=slot.get("name", ""), + ) + try: + terminal_obj.Placement = _slot_placement(slot) + except Exception: + pass + _ensure_visible(terminal_obj) + report["updated_terminals"] += 1 + else: + terminal_obj = _create_terminal_object( + doc, + terminal_uuid, + slot, + terminal_group, + project_uuid, + device_element_uuid, + device_instance_id, + ) + report["imported_terminals"] += 1 else: TerminalObjects.set_terminal_semantics( terminal_obj, @@ -410,6 +442,7 @@ def import_terminals_from_payload(payload, scene_path=""): terminal_group.addObject(terminal_obj) used_uuids.add(terminal_uuid) + used_objects.add(terminal_obj) source_obj = slot.get("source_object") if source_obj is not None: _hide_object(source_obj) @@ -418,6 +451,8 @@ def import_terminals_from_payload(payload, scene_path=""): for terminal_uuid, terminal_obj in list(existing_by_uuid.items()): if terminal_uuid in used_uuids: continue + if terminal_obj in used_objects: + continue report["warnings"].append( "Removed stale terminal {0} from device {1}.".format( terminal_uuid, device_element_uuid diff --git a/src/Mod/FreeCADExchange/WiringImport.py b/src/Mod/FreeCADExchange/WiringImport.py index 0bf5112..edf39dc 100644 --- a/src/Mod/FreeCADExchange/WiringImport.py +++ b/src/Mod/FreeCADExchange/WiringImport.py @@ -32,10 +32,30 @@ def _conductor_uuids(item): return [str(value).strip() for value in values if str(value).strip()] -def _normalize_wire_entry(item, index): +def _device_display_map(payload): + labels = {} + for item in payload.get("devices", []) or []: + if not isinstance(item, dict): + continue + element_uuid = _string_value(item, "element_uuid") + display_tag = _string_value(item, "display_tag") + if element_uuid and display_tag: + labels[element_uuid] = display_tag + return labels + + +def _endpoint_text(device_label, terminal_display, terminal_uuid): + terminal = terminal_display or terminal_uuid + if device_label and terminal: + return "{0}:{1}".format(device_label, terminal) + return device_label or terminal or "未命名端子" + + +def _normalize_wire_entry(item, index, device_labels=None): if not isinstance(item, dict): raise WiringImportError("Wire entry #{0} must be an object.".format(index)) + device_labels = device_labels or {} wire_uuid = ( _string_value(item, "wire_id") or _string_value(item, "wire_uuid") @@ -55,6 +75,16 @@ def _normalize_wire_entry(item, index): wire_mark = _string_value(item, "wire_mark") wire_label = _string_value(item, "wire_label") or wire_mark or wire_uuid + start_element_uuid = _string_value(item, "start_element_uuid") + end_element_uuid = _string_value(item, "end_element_uuid") + start_terminal_display = _string_value(item, "start_terminal_display") + end_terminal_display = _string_value(item, "end_terminal_display") + start_device_label = device_labels.get(start_element_uuid, "") + end_device_label = device_labels.get(end_element_uuid, "") + endpoint_label = "{0} -> {1}".format( + _endpoint_text(start_device_label, start_terminal_display, start_terminal_uuid), + _endpoint_text(end_device_label, end_terminal_display, end_terminal_uuid), + ) return { "wire_uuid": wire_uuid, "wire_label": wire_label, @@ -62,14 +92,17 @@ def _normalize_wire_entry(item, index): "group_uuid": _string_value(item, "group_uuid"), "wire_mark": wire_mark, "wire_mark_is_manual": _bool_value(item, "wire_mark_is_manual"), - "start_element_uuid": _string_value(item, "start_element_uuid"), + "start_element_uuid": start_element_uuid, "start_terminal_uuid": start_terminal_uuid, "start_instance_id": _string_value(item, "start_instance_id"), - "start_terminal_display": _string_value(item, "start_terminal_display"), - "end_element_uuid": _string_value(item, "end_element_uuid"), + "start_terminal_display": start_terminal_display, + "start_device_label": start_device_label, + "end_element_uuid": end_element_uuid, "end_terminal_uuid": end_terminal_uuid, "end_instance_id": _string_value(item, "end_instance_id"), - "end_terminal_display": _string_value(item, "end_terminal_display"), + "end_terminal_display": end_terminal_display, + "end_device_label": end_device_label, + "endpoint_label": endpoint_label, "conductor_uuids": _conductor_uuids(item), } @@ -86,9 +119,7 @@ def _unique_object_name(doc, base_name): def _task_label(entry): mark = entry["wire_mark"] or entry["wire_label"] or entry["wire_uuid"] - start = entry["start_terminal_display"] or entry["start_terminal_uuid"] - end = entry["end_terminal_display"] or entry["end_terminal_uuid"] - return "{0} {1} -> {2}".format(mark, start, end) + return "{0} {1}".format(mark, entry["endpoint_label"]) def _find_task_by_wire_uuid(task_group, wire_uuid): @@ -116,6 +147,9 @@ def _set_task_extra_properties(task, entry): _ensure_string_property(task, "QetEndElementUuid", entry["end_element_uuid"]) _ensure_string_property(task, "QetStartTerminalDisplay", entry["start_terminal_display"]) _ensure_string_property(task, "QetEndTerminalDisplay", entry["end_terminal_display"]) + _ensure_string_property(task, "QetStartDeviceLabel", entry["start_device_label"]) + _ensure_string_property(task, "QetEndDeviceLabel", entry["end_device_label"]) + _ensure_string_property(task, "QetEndpointLabel", entry["endpoint_label"]) _ensure_string_property( task, "QetConductorUuidsJson", @@ -191,9 +225,10 @@ def import_wire_tasks_from_payload(payload, doc=None): "warnings": [], } + device_labels = _device_display_map(payload) for index, item in enumerate(wires): try: - entry = _normalize_wire_entry(item, index) + entry = _normalize_wire_entry(item, index, device_labels=device_labels) except WiringImportError as exc: report["skipped_invalid"] += 1 report["warnings"].append(str(exc)) diff --git a/tests/python/freecad_exchange_manual_wiring_panel_test.py b/tests/python/freecad_exchange_manual_wiring_panel_test.py index 585c500..0b84880 100644 --- a/tests/python/freecad_exchange_manual_wiring_panel_test.py +++ b/tests/python/freecad_exchange_manual_wiring_panel_test.py @@ -351,18 +351,20 @@ class ManualWiringPanelTest(unittest.TestCase): self.assertIn(wire, routed_group.Group) self.assertEqual("terminal-start", getattr(wire, "QetStartTerminalUuid", "")) self.assertEqual("terminal-end", getattr(wire, "QetEndTerminalUuid", "")) - self.assertEqual(5, len(getattr(wire, "Points", []))) self.assertEqual( - (1.0, 12.0, 3.0), - ( - wire.Points[1].x, - wire.Points[1].y, - wire.Points[1].z, - ), + [ + (1.0, 2.0, 3.0), + (1.0, 12.0, 3.0), + (1.0, 12.0, 30.0), + (1.0, 20.0, 30.0), + (10.0, 20.0, 30.0), + (10.0, 20.0, 17.0), + (10.0, 8.0, 17.0), + (9.0, 8.0, 17.0), + (9.0, 8.0, 7.0), + ], + [(point.x, point.y, point.z) for point in getattr(wire, "Points", [])], ) - self.assertEqual((10.0, 20.0, 30.0), (wire.Points[2].x, wire.Points[2].y, wire.Points[2].z)) - self.assertEqual((9.0, 8.0, 17.0), (wire.Points[3].x, wire.Points[3].y, wire.Points[3].z)) - self.assertEqual((9.0, 8.0, 7.0), (wire.Points[4].x, wire.Points[4].y, wire.Points[4].z)) def test_controller_generates_wire_from_selected_task(self): selection_state = _install_fake_freecad() diff --git a/tests/python/freecad_exchange_manual_wiring_test.py b/tests/python/freecad_exchange_manual_wiring_test.py index 1311f69..521af3c 100644 --- a/tests/python/freecad_exchange_manual_wiring_test.py +++ b/tests/python/freecad_exchange_manual_wiring_test.py @@ -200,9 +200,116 @@ class ManualWiringGroupTest(unittest.TestCase): self.assertEqual(110.0, wire.Shape[0].x) self.assertEqual(320.0, wire.Shape[-1].x) - def test_manual_wire_is_added_to_device_wire_group(self): + def test_manual_wire_routes_orthogonally_between_terminal_exit_points(self): _install_fake_freecad() - device_import, manual_wiring, terminal_objects = _reload_modules() + _device_import, manual_wiring, terminal_objects = _reload_modules() + app = sys.modules["FreeCAD"] + + doc = FakeDocument() + terminal_objects.ensure_root_group(doc, "project-1") + + start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart") + start_terminal.Placement = app.Placement(app.Vector(0, 0, 0), app.Rotation()) + terminal_objects.set_terminal_semantics( + start_terminal, + "project-1", + "device-start", + "terminal-start", + "instance-start", + label="Start", + ) + + end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd") + end_terminal.Placement = app.Placement(app.Vector(10, 20, 30), app.Rotation()) + terminal_objects.set_terminal_semantics( + end_terminal, + "project-1", + "device-end", + "terminal-end", + "instance-end", + label="End", + ) + + wire = manual_wiring.create_manual_wire( + doc, + start_terminal, + end_terminal, + terminal_exit_length=10.0, + ) + + points = [(point.x, point.y, point.z) for point in wire.Shape] + self.assertEqual( + [ + (0.0, 0.0, 0.0), + (0.0, 0.0, 10.0), + (0.0, 0.0, 40.0), + (0.0, 20.0, 40.0), + (10.0, 20.0, 40.0), + (10.0, 20.0, 30.0), + ], + points, + ) + + def test_manual_wire_reaches_face_anchor_normal_axis_last(self): + _install_fake_freecad() + _device_import, manual_wiring, terminal_objects = _reload_modules() + app = sys.modules["FreeCAD"] + + doc = FakeDocument() + terminal_objects.ensure_root_group(doc, "project-1") + + start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart") + start_terminal.Placement = app.Placement(app.Vector(0, 0, 0), app.Rotation()) + terminal_objects.set_terminal_semantics( + start_terminal, + "project-1", + "device-start", + "terminal-start", + "instance-start", + label="Start", + ) + + end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd") + end_terminal.Placement = app.Placement(app.Vector(100, 0, 0), app.Rotation()) + terminal_objects.set_terminal_semantics( + end_terminal, + "project-1", + "device-end", + "terminal-end", + "instance-end", + label="End", + ) + + wire = manual_wiring.create_manual_wire( + doc, + start_terminal, + end_terminal, + waypoints=[ + { + "point": app.Vector(40, 20, -100), + "support_axis": "z", + "anchor_kind": "face", + } + ], + terminal_exit_length=20.0, + ) + + points = [(point.x, point.y, point.z) for point in wire.Shape] + self.assertEqual( + [ + (0.0, 0.0, 0.0), + (0.0, 0.0, 20.0), + (40.0, 0.0, 20.0), + (40.0, 20.0, 20.0), + (40.0, 20.0, -100.0), + ], + points[:5], + ) + + def test_manual_wire_is_visible_in_routed_group_not_hidden_legacy_group(self): + _install_fake_freecad() + _device_import, manual_wiring, terminal_objects = _reload_modules() + wiring_objects = __import__("WiringObjects") doc = FakeDocument() root = terminal_objects.ensure_root_group(doc, "project-1") @@ -240,6 +347,10 @@ class ManualWiringGroupTest(unittest.TestCase): label="Start", ) end_terminal = FakeObject("TerminalEnd", "Part::LocalCoordinateSystem") + end_terminal.Placement = sys.modules["FreeCAD"].Placement( + sys.modules["FreeCAD"].Vector(10, 0, 0), + sys.modules["FreeCAD"].Rotation(), + ) terminal_objects.set_terminal_semantics( end_terminal, "project-1", @@ -255,9 +366,13 @@ class ManualWiringGroupTest(unittest.TestCase): device_group, terminal_objects.WIRE_GROUP_KIND, ) + routed_group = wiring_objects.ensure_routed_group(doc, "project-1") self.assertIsNotNone(wire_group) - self.assertIn(wire, wire_group.Group) + self.assertFalse(wire_group.ViewObject.Visibility) + self.assertIn(wire, routed_group.Group) + self.assertTrue(wire.ViewObject.Visibility) + self.assertNotIn(wire, wire_group.Group) self.assertNotIn(wire, root.Group) diff --git a/tests/python/freecad_exchange_terminal_import_template_slots_test.py b/tests/python/freecad_exchange_terminal_import_template_slots_test.py index 2685e36..781330d 100644 --- a/tests/python/freecad_exchange_terminal_import_template_slots_test.py +++ b/tests/python/freecad_exchange_terminal_import_template_slots_test.py @@ -285,6 +285,101 @@ class TerminalImportTemplateSlotPolicyTest(unittest.TestCase): self.assertEqual(20.0, terminals["terminal-p2"].Placement.Base.x) self.assertEqual(10.0, terminals["terminal-p1"].Placement.Base.x) + def test_import_rebinds_existing_local_terminal_on_matching_template_slot(self): + _install_fake_freecad() + terminal_import, terminal_objects, device_import = _reload_modules() + app = sys.modules["FreeCAD"] + + doc = FakeDocument() + device_import._ensure_document = lambda scene_path: doc + root = device_import._ensure_root_group(doc, project_uuid="project-1") + device = doc.addObject("App::Part", "QETDevice_device_a") + root.addObject(device) + terminal_objects.ensure_string_property( + device, + "QetProjectUuid", + "QET Exchange", + "Project UUID", + "project-1", + ) + terminal_objects.ensure_string_property( + device, + "QetElementUuid", + "QET Exchange", + "Element UUID", + "device-a", + ) + terminal_objects.ensure_string_property( + device, + "QetInstanceId", + "QET Exchange", + "Instance ID", + "instance-a", + ) + + slot = doc.addObject("Part::LocalCoordinateSystem", "Terminal_P1") + slot.Placement = app.Placement(app.Vector(15, 0, 0), app.Rotation()) + slot.addProperty("App::PropertyString", "Role", "QET Template", "") + slot.Role = "Terminal" + slot.addProperty("App::PropertyBool", "CanWire", "QET Template", "") + slot.CanWire = True + slot.addProperty("App::PropertyString", "QetTemplateSlotName", "QET Template", "") + slot.QetTemplateSlotName = "P1" + device.addObject(slot) + + terminal_group = terminal_objects.ensure_terminal_group( + doc, + device, + project_uuid="project-1", + instance_id="instance-a", + ) + local_terminal = terminal_objects.create_lcs_object( + doc, + "QETTerminal_instance_a_P1", + placement=app.Placement(app.Vector(1, 0, 0), app.Rotation()), + label="P1", + ) + terminal_group.addObject(local_terminal) + terminal_objects.set_terminal_semantics( + local_terminal, + "project-1", + "device-a", + "local:instance-a:P1", + "instance-a", + label="P1", + slot_name="P1", + ) + + report = terminal_import.import_terminals_from_payload( + { + "project_uuid": "project-1", + "devices": [ + { + "element_uuid": "device-a", + "instance_id": "instance-a", + } + ], + "terminals": [ + { + "terminal_uuid": "terminal-real-p1", + "element_uuid": "device-a", + "instance_id": "instance-a", + "slot_name_hint": "P1", + } + ], + } + ) + + terminals = terminal_objects.collect_terminal_objects(terminal_group) + + self.assertEqual(0, report["imported_terminals"]) + self.assertEqual(1, report["updated_terminals"]) + self.assertEqual(1, len(terminals)) + self.assertIs(local_terminal, terminals[0]) + self.assertEqual("terminal-real-p1", local_terminal.QetTerminalUuid) + self.assertEqual("instance-a", local_terminal.QetInstanceId) + self.assertEqual(15.0, local_terminal.Placement.Base.x) + if __name__ == "__main__": unittest.main() diff --git a/tests/python/freecad_exchange_wiring_import_test.py b/tests/python/freecad_exchange_wiring_import_test.py index 967af66..4397cb5 100644 --- a/tests/python/freecad_exchange_wiring_import_test.py +++ b/tests/python/freecad_exchange_wiring_import_test.py @@ -103,6 +103,10 @@ class WiringImportTest(unittest.TestCase): terminal_objects.ensure_root_group(doc, "project-1") payload = { "project_uuid": "project-1", + "devices": [ + {"element_uuid": "device-a", "display_tag": "TAa"}, + {"element_uuid": "device-b", "display_tag": "PEN001"}, + ], "wires": [ { "wire_id": "wire-1", @@ -140,6 +144,8 @@ class WiringImportTest(unittest.TestCase): self.assertEqual("device-b", task.QetEndElementUuid) self.assertEqual("A1", task.QetStartTerminalDisplay) self.assertEqual("B1", task.QetEndTerminalDisplay) + self.assertEqual("TAa:A1 -> PEN001:B1", task.QetEndpointLabel) + self.assertIn("TAa:A1 -> PEN001:B1", task.Label) self.assertIn("conductor-1", task.QetConductorUuidsJson) self.assertEqual("Task", task.RouteType) self.assertEqual("Task", task.RouteStatus) diff --git a/tests/python/freecad_exchange_wiring_test.py b/tests/python/freecad_exchange_wiring_test.py index 351b5a7..85a73ba 100644 --- a/tests/python/freecad_exchange_wiring_test.py +++ b/tests/python/freecad_exchange_wiring_test.py @@ -204,7 +204,7 @@ class WiringTest(unittest.TestCase): self.assertIsNotNone(doc.getObject("QETWiring_04_Routed")) self.assertFalse(legacy_group.ViewObject.Visibility) - def test_create_manual_wire_preserves_manual_waypoints_as_direct_segments(self): + def test_create_manual_wire_preserves_manual_waypoints_as_orthogonal_segments(self): _install_fake_freecad() terminal_objects, wiring_objects, manual_wiring, _write_back = _reload_modules() app = sys.modules["FreeCAD"] @@ -313,12 +313,21 @@ class WiringTest(unittest.TestCase): self.assertEqual("Manual", getattr(wire, "RouteType", "")) self.assertEqual("terminal-start", getattr(wire, "QetStartTerminalUuid", "")) self.assertEqual("terminal-end", getattr(wire, "QetEndTerminalUuid", "")) - self.assertEqual(5, len(getattr(wire, "Points", []))) - self.assertEqual((1.0, 22.0, 3.0), (wire.Points[1].x, wire.Points[1].y, wire.Points[1].z)) + self.assertEqual( + [ + (1.0, 2.0, 3.0), + (1.0, 22.0, 3.0), + (1.0, 5.0, 3.0), + (1.0, 5.0, 6.0), + (4.0, 5.0, 6.0), + (4.0, 5.0, 27.0), + (9.0, 5.0, 27.0), + (9.0, 8.0, 27.0), + (9.0, 8.0, 7.0), + ], + [(point.x, point.y, point.z) for point in getattr(wire, "Points", [])], + ) self.assertTrue(any(point.x == 4.0 and point.y == 5.0 and point.z == 6.0 for point in wire.Points)) - self.assertEqual((4.0, 5.0, 6.0), (wire.Points[2].x, wire.Points[2].y, wire.Points[2].z)) - self.assertEqual((9.0, 8.0, 27.0), (wire.Points[3].x, wire.Points[3].y, wire.Points[3].z)) - self.assertEqual((9.0, 8.0, 7.0), (wire.Points[4].x, wire.Points[4].y, wire.Points[4].z)) self.assertIn("QetManualWaypointsJson", getattr(wire, "PropertiesList", [])) self.assertIn('"support_axis": "x"', getattr(wire, "QetManualWaypointsJson", ""))