import importlib import json import sys import types import unittest from pathlib import Path REPO_ROOT = Path(__file__).resolve().parents[2] MODULE_DIR = REPO_ROOT / "src" / "Mod" / "FreeCADExchange" if str(MODULE_DIR) not in sys.path: sys.path.insert(0, str(MODULE_DIR)) def _install_fake_freecad(): class Vector: def __init__(self, x=0.0, y=0.0, z=0.0): self.x = float(x) self.y = float(y) self.z = float(z) class Rotation: def multVec(self, vector): return vector class Placement: def __init__(self, base=None, rotation=None): self.Base = base or Vector() self.Rotation = rotation or Rotation() def multVec(self, vector): return Vector( self.Base.x + vector.x, self.Base.y + vector.y, self.Base.z + vector.z, ) fake_freecad = types.ModuleType("FreeCAD") fake_freecad.Vector = Vector fake_freecad.Rotation = Rotation fake_freecad.Placement = Placement fake_freecad.ActiveDocument = None fake_freecad.Console = types.SimpleNamespace( PrintMessage=lambda *args, **kwargs: None, PrintWarning=lambda *args, **kwargs: None, PrintError=lambda *args, **kwargs: None, ) sys.modules["FreeCAD"] = fake_freecad fake_gui = types.ModuleType("FreeCADGui") fake_gui.addCommand = lambda *args, **kwargs: None fake_gui.SendMsgToActiveView = lambda *args, **kwargs: None fake_gui.Selection = types.SimpleNamespace( getSelection=lambda: [], getSelectionEx=lambda: [], ) sys.modules["FreeCADGui"] = fake_gui fake_part = types.ModuleType("Part") fake_part.makePolygon = lambda points: tuple(points) sys.modules["Part"] = fake_part class FakeViewObject: def __init__(self): self.Visibility = True self.LineWidth = None self.LineColor = None self.DrawStyle = None class FakeObject: def __init__(self, name, type_id, doc=None): self.Name = name self.Label = name self.TypeId = type_id self.Document = doc self.PropertiesList = [] self.Group = [] self.ViewObject = FakeViewObject() self.Shape = None self.Points = [] self.Placement = sys.modules["FreeCAD"].Placement() self.InList = [] def isDerivedFrom(self, type_name): if self.TypeId == type_name: return True if type_name == "App::DocumentObjectGroup": return self.TypeId == "App::DocumentObjectGroup" if type_name == "App::LocalCoordinateSystem": return self.TypeId in {"Part::LocalCoordinateSystem", "PartDesign::CoordinateSystem"} return False def addProperty(self, prop_type, prop_name, group_name, description): if prop_name not in self.PropertiesList: self.PropertiesList.append(prop_name) def addObject(self, child): if child not in self.Group: self.Group.append(child) if self not in child.InList: child.InList.append(self) class FakeDocument: def __init__(self): self.Objects = [] self.Name = "FakeDoc" def addObject(self, type_name, name): obj = FakeObject(name, type_name, doc=self) self.Objects.append(obj) return obj def getObject(self, name): for obj in self.Objects: if obj.Name == name: return obj return None def removeObject(self, name): self.Objects = [obj for obj in self.Objects if obj.Name != name] def recompute(self): return None class FakeBoundBox: def __init__(self, xmin, xmax, ymin, ymax, zmin, zmax): self.XMin = xmin self.XMax = xmax self.YMin = ymin self.YMax = ymax self.ZMin = zmin self.ZMax = zmax class FakeShape: def __init__(self, bbox, edges=None, faces=None): self.BoundBox = bbox self.Edges = edges or [] self.Faces = faces or [] self.Solids = [] self.Shells = [] class FakeVertex: def __init__(self, point): self.Point = point class FakeEdge: ShapeType = "Edge" def __init__(self, start, end): self.Vertexes = [FakeVertex(start), FakeVertex(end)] class FakeFace: ShapeType = "Face" def __init__(self, bbox, normal, vertices=None, center=None): self.BoundBox = bbox self._normal = normal self.Vertexes = [FakeVertex(point) for point in (vertices or [])] self.CenterOfMass = center def normalAt(self, _u, _v): return self._normal class FakeSelectionItem: def __init__(self, sub_objects=None, obj=None): self.SubObjects = sub_objects or [] self.PickedPoints = [] self.Object = obj def _reload_modules(): for name in [ "TerminalObjects", "TemplateSemantics", "WiringObjects", "RoutingNetwork", "AutoRouting", "AutoRoutingPanel", ]: sys.modules.pop(name, None) terminal_objects = importlib.import_module("TerminalObjects") wiring_objects = importlib.import_module("WiringObjects") routing_network = importlib.import_module("RoutingNetwork") auto_routing = importlib.import_module("AutoRouting") return terminal_objects, wiring_objects, routing_network, auto_routing def _terminal(doc, terminal_objects, name, terminal_uuid, point): app = sys.modules["FreeCAD"] terminal = doc.addObject("Part::LocalCoordinateSystem", name) terminal.Placement = app.Placement(point, app.Rotation()) terminal_objects.set_terminal_semantics( terminal, "project-1", "element-{0}".format(terminal_uuid), terminal_uuid, "instance-{0}".format(terminal_uuid), label=terminal_uuid, ) return terminal class AutoRoutingTest(unittest.TestCase): def test_auto_route_selected_terminals_requires_supported_route_by_default(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 20, 0)) with self.assertRaises(auto_routing.AutoRoutingError): auto_routing.route_between_terminals(doc, start, end) def test_auto_route_can_still_use_explicit_floating_fallback_for_debug(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 20, 0)) result = auto_routing.route_between_terminals( doc, start, end, options={"allow_floating_fallback": True}, ) wire = result["wire"] self.assertEqual("orthogonal-v1", result["algorithm"]) self.assertEqual("AutoSuggested", wire.RouteType) self.assertEqual("Auto", wire.RouteMode) self.assertEqual("Routed", wire.RouteStatus) self.assertGreaterEqual(len(wire.Points), 4) self.assertIn(wire, doc.getObject("QETWiring_04_Routed").Group) def test_auto_route_prefers_user_route_carrier_network(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(120, 0, 0)) routing_network.create_route_carrier( doc, [ app.Vector(0, 0, 20), app.Vector(0, 30, 20), app.Vector(120, 30, 20), app.Vector(120, 0, 20), ], project_uuid="project-1", ) result = auto_routing.route_between_terminals(doc, start, end) self.assertEqual("network-dijkstra-v1", result["algorithm"]) self.assertEqual("Routed", result["route_status"]) self.assertTrue(any(point.y == 30.0 for point in result["points"])) def test_auto_route_stores_length_and_wire_style_diagnostics(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], project_uuid="project-1", kind="WireDuct", ) result = auto_routing.route_between_terminals( doc, start, end, wire_uuid="wire-1", wire_label="N4111", options={"wire_style_id": "42"}, ) wire = result["wire"] payload = json.loads(wire.QetAutoRouteDiagnosticsJson) self.assertGreater(float(wire.QetAutoRouteLengthMm), 0.0) self.assertEqual("42", wire.QetWireStyleId) self.assertEqual("42", payload["wire_style_id"]) self.assertGreater(payload["length_mm"], 0.0) def test_network_auto_route_offsets_lane_by_route_index(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], project_uuid="project-1", kind="WireDuct", ) first = auto_routing.route_between_terminals( doc, start, end, route_index=0, wire_uuid="wire-1", options={"lane_spacing": 12.0, "lane_axis": "y"}, ) second = auto_routing.route_between_terminals( doc, start, end, route_index=1, wire_uuid="wire-2", options={"lane_spacing": 12.0, "lane_axis": "y"}, ) payload = json.loads(second["wire"].QetAutoRouteDiagnosticsJson) self.assertTrue(any(abs(point.y - 0.0) <= 0.001 for point in first["points"][1:-1])) self.assertTrue(any(abs(point.y - 12.0) <= 0.001 for point in second["points"][1:-1])) self.assertEqual(1, payload["lane"]["index"]) self.assertEqual("y", payload["lane"]["axis"]) self.assertEqual(12.0, payload["lane"]["offset_mm"]) def test_auto_route_replaces_existing_wire_uuid_when_endpoints_change(self): _install_fake_freecad() terminal_objects, wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start_old = _terminal(doc, terminal_objects, "TerminalStartOld", "terminal-start-old", app.Vector(0, 0, 0)) end_old = _terminal(doc, terminal_objects, "TerminalEndOld", "terminal-end-old", app.Vector(100, 0, 0)) start_new = _terminal(doc, terminal_objects, "TerminalStartNew", "terminal-start-new", app.Vector(0, 40, 0)) end_new = _terminal(doc, terminal_objects, "TerminalEndNew", "terminal-end-new", app.Vector(100, 40, 0)) routing_network.create_route_carrier( doc, [ app.Vector(0, 0, 20), app.Vector(100, 0, 20), app.Vector(100, 40, 20), app.Vector(0, 40, 20), ], project_uuid="project-1", kind="WireDuct", ) auto_routing.route_between_terminals(doc, start_old, end_old, wire_uuid="wire-1") auto_routing.route_between_terminals(doc, start_new, end_new, wire_uuid="wire-1") routed_wires = list(wiring_objects.iter_routed_wire_objects(doc)) self.assertEqual(1, len(routed_wires)) self.assertEqual("terminal-start-new", routed_wires[0].QetStartTerminalUuid) self.assertEqual("terminal-end-new", routed_wires[0].QetEndTerminalUuid) def test_route_carrier_styles_make_generated_objects_distinguishable(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") wire_duct = routing_network.create_route_carrier( doc, [app.Vector(0, 0, 0), app.Vector(100, 0, 0)], project_uuid="project-1", kind="WireDuct", ) routing_range = routing_network.create_route_carrier( doc, [app.Vector(0, 10, 0), app.Vector(100, 10, 0)], project_uuid="project-1", kind="RoutingRange", ) terminal_access = routing_network.create_route_carrier( doc, [app.Vector(0, 20, 0), app.Vector(100, 20, 0)], project_uuid="project-1", kind="TerminalAccess", ) self.assertEqual((1.0, 0.55, 0.0), wire_duct.ViewObject.LineColor) self.assertEqual(4.0, wire_duct.ViewObject.LineWidth) self.assertEqual((0.0, 0.65, 0.35), routing_range.ViewObject.LineColor) self.assertEqual("Solid", routing_range.ViewObject.DrawStyle) self.assertEqual((0.65, 0.2, 1.0), terminal_access.ViewObject.LineColor) self.assertEqual("Solid", terminal_access.ViewObject.DrawStyle) def test_route_graph_connects_crossing_carriers_at_intersection(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(50, 50, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 0), app.Vector(100, 0, 0)], project_uuid="project-1", kind="WireDuct", ) routing_network.create_route_carrier( doc, [app.Vector(50, -50, 0), app.Vector(50, 50, 0)], project_uuid="project-1", kind="WireDuct", ) network = routing_network.build_route_graph(doc) result = auto_routing.route_between_terminals(doc, start, end) self.assertEqual(5, len(network["nodes"])) self.assertEqual("network-dijkstra-v1", result["algorithm"]) self.assertIn((50.0, 0.0, 0.0), [(point.x, point.y, point.z) for point in result["points"]]) def test_route_graph_connects_overlapping_collinear_carriers(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(120, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 0), app.Vector(80, 0, 0)], project_uuid="project-1", kind="WireDuct", ) routing_network.create_route_carrier( doc, [app.Vector(40, 0, 0), app.Vector(120, 0, 0)], project_uuid="project-1", kind="WireDuct", ) network = routing_network.build_route_graph(doc) result = auto_routing.route_between_terminals(doc, start, end) self.assertEqual("network-dijkstra-v1", result["algorithm"]) self.assertIn((40.0, 0.0, 0.0), [(point.x, point.y, point.z) for point in result["points"]]) self.assertIn((80.0, 0.0, 0.0), [(point.x, point.y, point.z) for point in result["points"]]) self.assertGreaterEqual(network["segment_count"], 3) def test_auto_route_prefers_wire_duct_over_auxiliary_range(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(120, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(120, 0, 20)], project_uuid="project-1", kind="RoutingRange", ) routing_network.create_route_carrier( doc, [ app.Vector(0, 0, 20), app.Vector(0, 40, 20), app.Vector(120, 40, 20), app.Vector(120, 0, 20), ], project_uuid="project-1", kind="WireDuct", ) result = auto_routing.route_between_terminals(doc, start, end) self.assertEqual("network-dijkstra-v1", result["algorithm"]) self.assertTrue(any(point.y == 40.0 for point in result["points"])) def test_surface_carrier_grid_supports_backplate_routing(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(120, 0, 0)) face = FakeFace( FakeBoundBox(0, 120, -20, 120, -1, -1), app.Vector(0, 0, 1), ) created = routing_network.create_surface_carriers_from_selection( doc, [FakeSelectionItem([face])], project_uuid="project-1", spacing=60.0, offset=5.0, margin=0.0, ) result = auto_routing.route_between_terminals(doc, start, end) self.assertGreater(len(created), 0) self.assertEqual("RoutingRange", getattr(created[0], "QetRouteCarrierKind", "")) self.assertEqual("network-dijkstra-v1", result["algorithm"]) self.assertTrue(any(point.z == 4.0 for point in result["points"])) def test_auto_detect_support_surface_creates_routing_range(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") panel = doc.addObject("Part::Feature", "MountingPlateA") panel.Label = "安装板A" panel.Shape = FakeShape(FakeBoundBox(0, 120, 0, 5, 0, 100)) cabinet = doc.addObject("Part::Feature", "Cabinet") cabinet.Label = "3D机柜" cabinet.Shape = FakeShape(FakeBoundBox(0, 300, 0, 80, 0, 400)) duct = doc.addObject("Part::Feature", "WireDuctA") duct.Label = "Wire Duct A" duct.Shape = FakeShape(FakeBoundBox(0, 120, -10, 10, 5, 25)) created = routing_network.create_surface_carriers_from_document( doc, project_uuid="project-1", spacing=60.0, offset=5.0, margin=0.0, ) created_again = routing_network.create_surface_carriers_from_document( doc, project_uuid="project-1", spacing=60.0, offset=5.0, margin=0.0, ) self.assertEqual(6, len(created)) self.assertEqual(0, len(created_again)) self.assertTrue(all(carrier.QetRouteCarrierKind == "RoutingRange" for carrier in created)) self.assertEqual("RoutingRange", panel.QetRoutingSourceKind) self.assertEqual("SupportSurface", panel.QetRoutingObstacleMode) self.assertFalse(hasattr(cabinet, "QetRoutingSourceKind")) self.assertFalse(hasattr(duct, "QetRoutingSourceKind")) def test_auto_route_can_use_auto_detected_support_surface(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 10, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(120, 10, 0)) panel = doc.addObject("Part::Feature", "MountingPlateA") panel.Label = "Mounting Plate A" panel.Shape = FakeShape(FakeBoundBox(0, 120, 0, 5, 0, 100)) created = routing_network.create_surface_carriers_from_document( doc, project_uuid="project-1", spacing=60.0, offset=5.0, margin=0.0, ) result = auto_routing.route_between_terminals(doc, start, end) self.assertGreater(len(created), 0) self.assertEqual("network-dijkstra-v1", result["algorithm"]) self.assertEqual("Routed", result["route_status"]) self.assertEqual(0, result["collision_count"]) self.assertTrue(any(point.y == 10.0 for point in result["points"])) def test_generate_layout_space_auto_detects_support_surface(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, _auto_routing = _reload_modules() auto_routing_panel = importlib.import_module("AutoRoutingPanel") app = sys.modules["FreeCAD"] doc = FakeDocument() app.ActiveDocument = doc terminal_objects.ensure_root_group(doc, "project-1") panel = doc.addObject("Part::Feature", "MountingPlateA") panel.Label = "Mounting Plate A" panel.Shape = FakeShape(FakeBoundBox(0, 120, 0, 5, 0, 100)) result = auto_routing_panel.AutoRoutingController().generate_layout_space() self.assertGreater(result["surface_carriers"], 0) self.assertEqual("document", result["source_mode"]) def test_generate_routing_paths_uses_selected_wire_duct_entity(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, _auto_routing = _reload_modules() auto_routing_panel = importlib.import_module("AutoRoutingPanel") app = sys.modules["FreeCAD"] gui = sys.modules["FreeCADGui"] doc = FakeDocument() app.ActiveDocument = doc terminal_objects.ensure_root_group(doc, "project-1") duct = doc.addObject("Part::Feature", "UnlabeledLongDuct") duct.Shape = FakeShape(FakeBoundBox(0, 160, -10, 10, 0, 20)) gui.Selection = types.SimpleNamespace( getSelection=lambda: [], getSelectionEx=lambda: [FakeSelectionItem(obj=duct)], ) result = auto_routing_panel.AutoRoutingController().generate_routing_paths() self.assertEqual(1, result["wire_duct_carriers"]) self.assertEqual("selection", result["source_mode"]) def test_generate_layout_space_uses_whole_document_not_selected_face_workflow(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, _auto_routing = _reload_modules() auto_routing_panel = importlib.import_module("AutoRoutingPanel") app = sys.modules["FreeCAD"] gui = sys.modules["FreeCADGui"] doc = FakeDocument() app.ActiveDocument = doc terminal_objects.ensure_root_group(doc, "project-1") panel = doc.addObject("Part::Feature", "MountingPlateA") panel.Label = "Mounting Plate A" panel.Shape = FakeShape(FakeBoundBox(0, 120, 0, 5, 0, 100)) gui.Selection = types.SimpleNamespace( getSelection=lambda: [], getSelectionEx=lambda: [FakeSelectionItem(obj=panel)], ) result = auto_routing_panel.AutoRoutingController().generate_layout_space() self.assertGreater(result["surface_carriers"], 0) self.assertEqual("document", result["source_mode"]) def test_generate_layout_space_adds_terminal_access_to_route_network(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() auto_routing_panel = importlib.import_module("AutoRoutingPanel") app = sys.modules["FreeCAD"] doc = FakeDocument() app.ActiveDocument = doc terminal_objects.ensure_root_group(doc, "project-1") _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) duct = doc.addObject("Part::Feature", "WireDuctA") duct.Label = "Wire Duct A" duct.Shape = FakeShape(FakeBoundBox(0, 100, -5, 5, 15, 25)) result = auto_routing_panel.AutoRoutingController().generate_layout_space() result_again = auto_routing_panel.AutoRoutingController().generate_layout_space() access_carriers = [ carrier for carrier in routing_network.collect_route_carriers(doc) if getattr(carrier, "QetRouteCarrierKind", "") == "TerminalAccess" ] self.assertEqual(1, result["wire_duct_carriers"]) self.assertEqual(2, result["terminal_access_carriers"]) self.assertEqual(0, result_again["wire_duct_carriers"]) self.assertEqual(2, result_again["terminal_access_carriers"]) self.assertEqual(2, len(access_carriers)) self.assertGreater(result["network"]["segments"], 0) def test_generate_layout_space_skips_far_terminal_access_to_protect_view_bbox(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, _auto_routing = _reload_modules() auto_routing_panel = importlib.import_module("AutoRoutingPanel") app = sys.modules["FreeCAD"] doc = FakeDocument() app.ActiveDocument = doc terminal_objects.ensure_root_group(doc, "project-1") _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) duct = doc.addObject("Part::Feature", "WireDuctFar") duct.Label = "Wire Duct Far" duct.Shape = FakeShape(FakeBoundBox(5000, 5100, -5, 5, 15, 25)) result = auto_routing_panel.AutoRoutingController().generate_layout_space() self.assertEqual(1, result["wire_duct_carriers"]) self.assertEqual(0, result["terminal_access_carriers"]) def test_route_all_prepares_layout_space_like_one_click_routing(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, _auto_routing = _reload_modules() auto_routing_panel = importlib.import_module("AutoRoutingPanel") app = sys.modules["FreeCAD"] doc = FakeDocument() app.ActiveDocument = doc terminal_objects.ensure_root_group(doc, "project-1") _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) duct = doc.addObject("Part::Feature", "WireDuctA") duct.Label = "Wire Duct A" duct.Shape = FakeShape(FakeBoundBox(0, 100, -5, 5, 15, 25)) app._qet_exchange_payload = { "project_uuid": "project-1", "wires": [ { "wire_id": "wire-1", "start_terminal_uuid": "terminal-start", "end_terminal_uuid": "terminal-end", } ], } report = auto_routing_panel.AutoRoutingController().route_all() self.assertEqual(1, report["routed"]) self.assertEqual(1, report["prepared_layout"]["wire_duct_carriers"]) self.assertEqual(2, report["prepared_layout"]["terminal_access_carriers"]) diagnostic_group = doc.getObject("QETWiring_05_Diagnostics") self.assertIsNotNone(diagnostic_group) self.assertEqual(1, len(diagnostic_group.Group)) diagnostic_payload = json.loads(diagnostic_group.Group[0].QetDiagnosticJson) self.assertEqual(1, diagnostic_payload["prepared_layout"]["wire_duct_carriers"]) self.assertEqual(2, diagnostic_payload["prepared_layout"]["terminal_access_carriers"]) def test_auto_route_rejects_far_network_entry_to_avoid_huge_render_bbox(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(5000, 0, 20), app.Vector(5100, 0, 20)], project_uuid="project-1", kind="WireDuct", ) with self.assertRaises(auto_routing.AutoRoutingError): auto_routing.route_between_terminals(doc, start, end) def test_route_between_terminals_fails_without_network(self): _install_fake_freecad() terminal_objects, wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() app.ActiveDocument = doc terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(120, 30, 0)) with self.assertRaises(auto_routing.AutoRoutingError): auto_routing.route_between_terminals(doc, start, end) self.assertEqual(0, len(wiring_objects.iter_routed_wire_objects(doc))) def test_surface_carrier_grid_uses_actual_rotated_face_plane(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") normal = app.Vector(0, 1, 1) vertices = [ app.Vector(0, 0, 0), app.Vector(100, 0, 0), app.Vector(0, 50, -50), app.Vector(100, 50, -50), ] face = FakeFace( FakeBoundBox(0, 100, 0, 50, -50, 0), normal, vertices=vertices, center=app.Vector(50, 25, -25), ) created = routing_network.create_surface_carriers_from_selection( doc, [FakeSelectionItem([face])], project_uuid="project-1", spacing=50.0, offset=10.0, margin=0.0, ) self.assertGreater(len(created), 0) first_point = created[0].Points[0] for carrier in created: for point in carrier.Points: # The rotated face is y + z = 0; after a 10 mm normal offset, # all generated points must stay on one parallel plane. self.assertAlmostEqual(first_point.y + first_point.z, point.y + point.z, places=6) def test_route_path_creation_ignores_whole_solid_object_edges(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") solid = doc.addObject("Part::Feature", "CabinetSolid") solid.Shape = FakeShape( FakeBoundBox(0, 100, 0, 100, 0, 10), edges=[FakeEdge(app.Vector(0, 0, 0), app.Vector(100, 0, 0))], faces=[object()], ) created = routing_network.create_carriers_from_selection( doc, [FakeSelectionItem(obj=solid)], project_uuid="project-1", ) self.assertEqual([], created) def test_route_path_creation_projects_line_to_selected_face(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") face = FakeFace( FakeBoundBox(0, 100, 0, 100, 0, 0), app.Vector(0, 0, 1), ) draft_line = doc.addObject("Part::Feature", "DraftLine") draft_line.Shape = FakeShape( FakeBoundBox(10, 90, 10, 90, 25, 35), edges=[FakeEdge(app.Vector(10, 10, 25), app.Vector(90, 90, 35))], ) created = routing_network.create_carriers_from_selection( doc, [ FakeSelectionItem([face]), FakeSelectionItem(obj=draft_line), ], project_uuid="project-1", ) self.assertEqual(1, len(created)) self.assertEqual([2.0, 2.0], [point.z for point in created[0].Points]) def test_wire_duct_entity_generates_centerline_and_marks_source_pass_through(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") duct = doc.addObject("Part::Feature", "WireDuct") duct.Shape = FakeShape(FakeBoundBox(0, 120, -10, 10, 5, 25)) created = routing_network.create_wire_duct_carriers_from_selection( doc, [FakeSelectionItem(obj=duct)], project_uuid="project-1", margin=20.0, ) self.assertEqual(1, len(created)) carrier = created[0] self.assertEqual("WireDuct", carrier.QetRouteCarrierKind) self.assertEqual("PassThrough", duct.QetRoutingObstacleMode) self.assertEqual([(20.0, 0.0, 15.0), (100.0, 0.0, 15.0)], [(p.x, p.y, p.z) for p in carrier.Points]) def test_auto_detect_wire_ducts_ignores_cabinet_models(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") duct = doc.addObject("Part::Feature", "WireDuctA") duct.Label = "线槽A" duct.Shape = FakeShape(FakeBoundBox(0, 120, -10, 10, 5, 25)) cabinet = doc.addObject("Part::Feature", "Cabinet") cabinet.Label = "3D机柜" cabinet.Shape = FakeShape(FakeBoundBox(0, 300, 0, 80, 0, 400)) created = routing_network.create_wire_duct_carriers_from_document( doc, project_uuid="project-1", ) created_again = routing_network.create_wire_duct_carriers_from_document( doc, project_uuid="project-1", ) self.assertEqual(1, len(created)) self.assertEqual(0, len(created_again)) self.assertEqual("WireDuct", created[0].QetRouteCarrierKind) self.assertEqual("PassThrough", duct.QetRoutingObstacleMode) self.assertFalse(hasattr(cabinet, "QetRoutingObstacleMode")) def test_wire_duct_source_is_not_reported_as_collision(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(120, 0, 0)) duct = doc.addObject("Part::Feature", "WireDuct") duct.Shape = FakeShape(FakeBoundBox(-10, 130, -10, 10, 15, 25)) routing_network.create_wire_duct_carriers_from_selection( doc, [FakeSelectionItem(obj=duct)], project_uuid="project-1", margin=0.0, ) result = auto_routing.route_between_terminals(doc, start, end) self.assertEqual("network-dijkstra-v1", result["algorithm"]) self.assertEqual("Routed", result["route_status"]) self.assertEqual(0, result["collision_count"]) def test_auto_route_uses_alternate_carrier_to_avoid_obstacle(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], project_uuid="project-1", kind="WireDuct", ) routing_network.create_route_carrier( doc, [ app.Vector(0, 0, 20), app.Vector(0, 50, 20), app.Vector(100, 50, 20), app.Vector(100, 0, 20), ], project_uuid="project-1", kind="WireDuct", ) obstacle = doc.addObject("Part::Feature", "CabinetObstacle") obstacle.Shape = FakeShape(FakeBoundBox(40, 60, -10, 10, 15, 25)) result = auto_routing.route_between_terminals(doc, start, end) self.assertEqual("network-dijkstra-v1", result["algorithm"]) self.assertEqual("Routed", result["route_status"]) self.assertEqual(0, result["collision_count"]) self.assertTrue(result["network"]["obstacle_aware"]) self.assertGreaterEqual(result["network"]["blocked_segments"], 1) self.assertIn(50.0, [point.y for point in result["points"]]) def test_auto_route_marks_collision_warning_against_obstacle_bbox(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) _routing_network.create_route_carrier( doc, [app.Vector(0, 0, 100), app.Vector(100, 0, 100)], project_uuid="project-1", ) obstacle = doc.addObject("Part::Feature", "Obstacle") obstacle.Shape = FakeShape(FakeBoundBox(40, 60, -10, 10, 90, 110)) result = auto_routing.route_between_terminals(doc, start, end) self.assertEqual("CollisionWarning", result["route_status"]) self.assertEqual("CollisionWarning", result["wire"].RouteStatus) self.assertEqual(1, result["collision_count"]) def test_auto_route_ignores_terminal_exit_segment_collision(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], project_uuid="project-1", ) terminal_body = doc.addObject("Part::Feature", "UngroupedTerminalBody") terminal_body.Shape = FakeShape(FakeBoundBox(-5, 5, -5, 5, -5, 15)) result = auto_routing.route_between_terminals(doc, start, end) self.assertEqual("Routed", result["route_status"]) self.assertEqual(0, result["collision_count"]) def test_auto_route_ignores_endpoint_device_body_as_obstacle(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) device = doc.addObject("App::DocumentObjectGroup", "QETDeviceStart") device.QetInstanceId = start.QetInstanceId device.addObject(start) body = doc.addObject("Part::Feature", "StartDeviceBody") body.Shape = FakeShape(FakeBoundBox(-5, 5, -5, 5, -5, 15)) device.addObject(body) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], project_uuid="project-1", ) result = auto_routing.route_between_terminals(doc, start, end) self.assertEqual("Routed", result["route_status"]) self.assertEqual(0, result["collision_count"]) def test_route_all_from_payload_skips_missing_terminal(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) payload = { "wires": [ { "wire_id": "wire-1", "start_terminal_uuid": "terminal-start", "end_terminal_uuid": "terminal-missing", } ] } report = auto_routing.route_all_from_payload(doc, payload) self.assertEqual(0, report["routed"]) self.assertEqual(1, report["skipped_missing_terminal"]) self.assertEqual(1, report["available_terminals"]) self.assertEqual(0, report["local_terminals"]) self.assertEqual(["terminal-missing"], report["missing_endpoint_uuids"]) self.assertEqual("terminal-start", report["missing_endpoint_samples"][0]["start_terminal_uuid"]) self.assertTrue(report["missing_endpoint_samples"][0]["start_found"]) self.assertFalse(report["missing_endpoint_samples"][0]["end_found"]) def test_route_all_writes_diagnostic_object_for_missing_terminal(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) payload = { "project_uuid": "project-1", "wires": [ { "wire_id": "wire-1", "start_terminal_uuid": "terminal-start", "end_terminal_uuid": "terminal-missing", } ], } report = auto_routing.route_all_from_payload(doc, payload) diagnostic_group = doc.getObject("QETWiring_05_Diagnostics") self.assertEqual(1, report["skipped_missing_terminal"]) self.assertIsNotNone(diagnostic_group) self.assertEqual(1, len(diagnostic_group.Group)) diagnostic = diagnostic_group.Group[0] self.assertEqual("AutoRouteBatch", diagnostic.QetDiagnosticKind) self.assertIn("terminal-missing", diagnostic.QetDiagnosticJson) def test_route_all_reports_total_auto_route_length(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], project_uuid="project-1", kind="WireDuct", ) payload = { "project_uuid": "project-1", "wires": [ { "wire_id": "wire-1", "start_terminal_uuid": "terminal-start", "end_terminal_uuid": "terminal-end", } ], } report = auto_routing.route_all_from_payload(doc, payload) message = auto_routing.format_route_all_report(report) self.assertGreater(report["total_length_mm"], 0.0) self.assertEqual(report["total_length_mm"], report["routes"][0]["length_mm"]) self.assertIn("总长度", message) def test_route_all_report_keeps_route_identity_and_diagnostics(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], project_uuid="project-1", kind="WireDuct", ) payload = { "project_uuid": "project-1", "wires": [ { "wire_id": "wire-1", "wire_label": "N4111", "wire_style_id": "42", "start_terminal_uuid": "terminal-start", "end_terminal_uuid": "terminal-end", } ], } report = auto_routing.route_all_from_payload( doc, payload, options={"lane_spacing": 12.0, "lane_axis": "y"}, ) route = report["routes"][0] self.assertEqual("wire-1", route["wire_uuid"]) self.assertEqual("N4111", route["wire_label"]) self.assertEqual("42", route["wire_style_id"]) self.assertEqual("terminal-start", route["start_terminal_uuid"]) self.assertEqual("terminal-end", route["end_terminal_uuid"]) self.assertEqual(0, route["lane"]["index"]) self.assertEqual("network-dijkstra-v1", route["algorithm"]) self.assertEqual(1, route["network"]["carriers"]) def test_route_all_report_includes_collision_samples(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 100), app.Vector(100, 0, 100)], project_uuid="project-1", ) obstacle = doc.addObject("Part::Feature", "MiddleObstacle") obstacle.Label = "Middle Obstacle" obstacle.Shape = FakeShape(FakeBoundBox(40, 60, -10, 10, 90, 110)) payload = { "project_uuid": "project-1", "wires": [ { "wire_id": "wire-1", "wire_label": "N4111", "start_terminal_uuid": "terminal-start", "end_terminal_uuid": "terminal-end", } ], } report = auto_routing.route_all_from_payload(doc, payload) message = auto_routing.format_route_all_report(report) self.assertEqual(1, report["collision_warnings"]) self.assertEqual("wire-1", report["collision_samples"][0]["wire_uuid"]) self.assertEqual("N4111", report["collision_samples"][0]["wire_label"]) self.assertEqual("MiddleObstacle", report["collision_samples"][0]["obstacle_name"]) self.assertEqual("Middle Obstacle", report["routes"][0]["collision_samples"][0]["obstacle_label"]) self.assertIn("碰撞示例", message) self.assertIn("Middle Obstacle", message) def test_route_all_report_calls_out_local_unbound_terminals(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") _terminal( doc, terminal_objects, "LocalTerminal", "local:instance-1:p1", app.Vector(0, 0, 0), ) payload = { "wires": [ { "wire_id": "wire-1", "start_terminal_uuid": "qet-terminal-start", "end_terminal_uuid": "qet-terminal-end", } ] } report = auto_routing.route_all_from_payload(doc, payload) message = auto_routing.format_route_all_report(report) self.assertEqual(0, report["routed"]) self.assertEqual(1, report["available_terminals"]) self.assertEqual(1, report["local_terminals"]) self.assertIn("端子匹配失败", message) self.assertIn("local:", message) def test_route_all_report_includes_network_and_first_error(self): _install_fake_freecad() _terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() report = { "total_wires": 2, "routed": 1, "collision_warnings": 1, "skipped_missing_terminal": 1, "prepared_layout": { "wire_duct_carriers": 2, "surface_carriers": 4, "terminal_access_carriers": 6, }, "missing_endpoint_samples": [ { "start_terminal_uuid": "terminal-a", "end_terminal_uuid": "terminal-b", } ], "errors": ["没有可用的线槽/路由路径网络"], } message = auto_routing.format_route_all_report(report) self.assertIn("routed=1", message) self.assertIn("线槽路径 2 条", message) self.assertIn("首个错误:没有可用的线槽/路由路径网络", message) self.assertIn("缺失示例:terminal-a -> terminal-b", message) def test_bind_wire_task_terminals_from_payload_does_not_create_wires(self): _install_fake_freecad() terminal_objects, wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() root = terminal_objects.ensure_root_group(doc, "project-1") device = doc.addObject("App::DocumentObjectGroup", "QETDevice_device_a") root.addObject(device) terminal_objects.ensure_string_property(device, "QetElementUuid", "QET Exchange", "", "device-a") terminal_objects.ensure_string_property(device, "QetInstanceId", "QET Exchange", "", "instance-a") terminal_objects.ensure_string_property(device, "QetProjectUuid", "QET Exchange", "", "project-1") terminal_group = terminal_objects.ensure_terminal_group( doc, device, project_uuid="project-1", instance_id="instance-a", ) for slot_name, point in ( ("P1", app.Vector(0, 0, 0)), ("P2", app.Vector(100, 0, 0)), ): terminal = terminal_objects.create_lcs_object( doc, "QETTerminal_instance_a_{0}".format(slot_name), placement=app.Placement(point, app.Rotation()), label=slot_name, ) terminal_group.addObject(terminal) terminal_objects.set_terminal_semantics( terminal, "project-1", "device-a", "local:instance-a:{0}".format(slot_name), "instance-a", label=slot_name, slot_name=slot_name, ) payload = { "project_uuid": "project-1", "wires": [ { "wire_id": "wire-1", "start_element_uuid": "device-a", "start_instance_id": "instance-a", "start_terminal_uuid": "qet-terminal-p1", "start_terminal_display": "P1", "end_element_uuid": "device-a", "end_instance_id": "instance-a", "end_terminal_uuid": "qet-terminal-p2", "end_terminal_display": "P2", } ], } report = auto_routing.bind_wire_task_terminals_from_payload(doc, payload) indexed = auto_routing.index_terminals(doc) self.assertEqual(2, report["bound"]) self.assertEqual(0, report["created"]) self.assertEqual(0, report["local_terminals"]) self.assertEqual([], wiring_objects.iter_routed_wire_objects(doc)) self.assertEqual("qet", indexed["qet-terminal-p1"].QetTerminalBindingMode) def test_route_all_rebinds_local_template_terminals_from_wire_endpoints(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() root = terminal_objects.ensure_root_group(doc, "project-1") device = doc.addObject("App::DocumentObjectGroup", "QETDevice_device_a") root.addObject(device) terminal_objects.ensure_string_property(device, "QetElementUuid", "QET Exchange", "", "device-a") terminal_objects.ensure_string_property(device, "QetInstanceId", "QET Exchange", "", "instance-a") terminal_objects.ensure_string_property(device, "QetProjectUuid", "QET Exchange", "", "project-1") terminal_group = terminal_objects.ensure_terminal_group( doc, device, project_uuid="project-1", instance_id="instance-a", ) for slot_name, point in ( ("P1", app.Vector(0, 0, 0)), ("P2", app.Vector(100, 0, 0)), ): terminal = terminal_objects.create_lcs_object( doc, "QETTerminal_instance_a_{0}".format(slot_name), placement=app.Placement(point, app.Rotation()), label=slot_name, ) terminal_group.addObject(terminal) terminal_objects.set_terminal_semantics( terminal, "project-1", "device-a", "local:instance-a:{0}".format(slot_name), "instance-a", label=slot_name, slot_name=slot_name, ) payload = { "project_uuid": "project-1", "wires": [ { "wire_id": "wire-1", "start_element_uuid": "device-a", "start_instance_id": "instance-a", "start_terminal_uuid": "qet-terminal-p1", "start_terminal_display": "P1", "end_element_uuid": "device-a", "end_instance_id": "instance-a", "end_terminal_uuid": "qet-terminal-p2", "end_terminal_display": "P2", } ], } report = auto_routing.route_all_from_payload( doc, payload, options={"allow_floating_fallback": True}, ) indexed = auto_routing.index_terminals(doc) self.assertEqual(1, report["routed"]) self.assertEqual(2, report["auto_bound_terminals"]) self.assertEqual(0, report["local_terminals"]) self.assertIn("qet-terminal-p1", indexed) self.assertIn("qet-terminal-p2", indexed) self.assertEqual("qet", indexed["qet-terminal-p1"].QetTerminalBindingMode) def test_clear_route_carriers_keeps_routed_wires(self): _install_fake_freecad() terminal_objects, wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], project_uuid="project-1", ) wire = auto_routing.route_between_terminals(doc, start, end)["wire"] removed = routing_network.clear_route_carriers(doc) self.assertEqual(1, removed) self.assertEqual([], routing_network.collect_route_carriers(doc)) self.assertIn(wire, wiring_objects.ensure_routed_group(doc, "project-1").Group) if __name__ == "__main__": unittest.main()