You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
LightWork3D/tests/python/freecad_exchange_auto_routi...

3191 lines
136 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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_eplan_connection_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_eplan_connection_between_terminals(doc, start, end)
def test_eplan_connection_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_eplan_connection_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_eplan_connection_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_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="wire-1",
wire_label="N4111",
options={"wire_style_id": "42"},
)
wire = result["wire"]
payload = json.loads(wire.QetRouteDiagnosticsJson)
self.assertGreater(float(wire.QetRouteLengthMm), 0.0)
self.assertEqual("42", wire.QetWireStyleId)
self.assertEqual("42", payload["wire_style_id"])
self.assertGreater(payload["length_mm"], 0.0)
self.assertTrue(payload["route_track"]["segments"])
self.assertEqual("WireDuct", payload["route_track"]["segments"][0]["carrier"]["kind"])
self.assertTrue(json.loads(wire.QetRouteTrackJson)["carrier_names"])
def test_route_track_preserves_generated_carrier_source_metadata(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")
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))
duct = doc.addObject("Part::Feature", "WireDuctA")
duct.Label = "线槽A"
duct.Shape = FakeShape(FakeBoundBox(0, 100, -5, 5, 15, 25))
auto_routing_panel.AutoRoutingController().generate_routing_paths()
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
route_track = json.loads(result["wire"].QetRouteTrackJson)
wire_duct_carriers = [
segment["carrier"]
for segment in route_track["segments"]
if segment["carrier"]["kind"] == "WireDuct"
]
self.assertTrue(wire_duct_carriers)
self.assertEqual("WireDuctA", wire_duct_carriers[0].get("source_name"))
self.assertEqual("线槽A", wire_duct_carriers[0].get("source_label"))
self.assertEqual("WireDuct", wire_duct_carriers[0].get("source_kind"))
def test_network_eplan_connection_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_eplan_connection_between_terminals(
doc,
start,
end,
route_index=0,
wire_uuid="wire-1",
options={"lane_spacing": 12.0, "lane_axis": "y"},
)
second = auto_routing.route_eplan_connection_between_terminals(
doc,
start,
end,
route_index=1,
wire_uuid="wire-2",
options={"lane_spacing": 12.0, "lane_axis": "y"},
)
third = auto_routing.route_eplan_connection_between_terminals(
doc,
start,
end,
route_index=2,
wire_uuid="wire-3",
options={"lane_spacing": 12.0, "lane_axis": "y"},
)
payload = json.loads(second["wire"].QetRouteDiagnosticsJson)
third_payload = json.loads(third["wire"].QetRouteDiagnosticsJson)
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.assertTrue(any(abs(point.y + 12.0) <= 0.001 for point in third["points"][1:-1]))
self.assertEqual(1, payload["lane"]["index"])
self.assertEqual("y", payload["lane"]["axis"])
self.assertEqual(12.0, payload["lane"]["offset_mm"])
self.assertEqual(2, third_payload["lane"]["index"])
self.assertEqual(-12.0, third_payload["lane"]["offset_mm"])
def test_network_eplan_connection_route_removes_collinear_network_points(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(50, 0, 20),
app.Vector(100, 0, 20),
],
project_uuid="project-1",
kind="WireDuct",
)
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
point_tuples = [(point.x, point.y, point.z) for point in result["points"]]
self.assertNotIn((50.0, 0.0, 20.0), point_tuples)
self.assertEqual(
[
(0.0, 0.0, 0.0),
(0.0, 0.0, 20.0),
(100.0, 0.0, 20.0),
(100.0, 0.0, 0.0),
],
point_tuples,
)
def test_eplan_connection_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_eplan_connection_between_terminals(doc, start_old, end_old, wire_uuid="wire-1")
auto_routing.route_eplan_connection_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_eplan_connection_route_keeps_existing_wire_when_replacement_fails(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(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_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="wire-1",
)["wire"]
routing_network.clear_route_carriers(doc)
with self.assertRaises(auto_routing.AutoRoutingError):
auto_routing.route_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="wire-1",
)
routed_wires = list(wiring_objects.iter_routed_wire_objects(doc))
self.assertEqual([first], routed_wires)
self.assertIsNotNone(doc.getObject(first.Name))
def test_eplan_connection_route_keeps_existing_wire_when_new_geometry_creation_fails(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_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="wire-1",
)["wire"]
original_create_wire_geometry = auto_routing._create_wire_geometry
def failing_create_wire_geometry(_doc, _name, _points):
raise RuntimeError("create failed")
auto_routing._create_wire_geometry = failing_create_wire_geometry
try:
with self.assertRaises(RuntimeError):
auto_routing.route_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="wire-1",
)
finally:
auto_routing._create_wire_geometry = original_create_wire_geometry
routed_wires = list(wiring_objects.iter_routed_wire_objects(doc))
self.assertEqual([first], routed_wires)
self.assertIsNotNone(doc.getObject(first.Name))
def test_eplan_connection_route_cleans_up_half_created_wire_when_draft_fallback_fails(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_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="wire-1",
)["wire"]
part_module = sys.modules["Part"]
draft_module = sys.modules.get("Draft")
if draft_module is None:
draft_module = types.ModuleType("Draft")
sys.modules["Draft"] = draft_module
original_make_polygon = part_module.makePolygon
original_make_wire = getattr(draft_module, "make_wire", None)
original_add_object = doc.addObject
def failing_make_polygon(*args, **kwargs):
raise RuntimeError("part unavailable")
def half_created_make_wire(points, closed=False, placement=None, face=None, support=None, bs2wire=False):
obj = doc.addObject("Part::FeaturePython", "Wire")
obj.Points = list(points)
raise RuntimeError("draft failed")
def failing_add_object(type_name, name):
if type_name == "App::FeaturePython":
raise RuntimeError("fallback failed")
return original_add_object(type_name, name)
part_module.makePolygon = failing_make_polygon
draft_module.make_wire = half_created_make_wire
doc.addObject = failing_add_object
try:
with self.assertRaises(RuntimeError):
auto_routing.route_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="wire-1",
)
finally:
part_module.makePolygon = original_make_polygon
if original_make_wire is None:
delattr(draft_module, "make_wire")
else:
draft_module.make_wire = original_make_wire
doc.addObject = original_add_object
routed_wires = list(wiring_objects.iter_routed_wire_objects(doc))
self.assertEqual([first], routed_wires)
self.assertEqual(0, len([obj for obj in doc.Objects if obj.Name == "Wire"]))
def test_eplan_connection_route_keeps_existing_wire_when_old_replacement_removal_fails(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_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="wire-1",
)["wire"]
original_remove = auto_routing._remove_routing_connection_objects
def failing_remove(target_doc, objects):
if first in list(objects or []):
return 0
return original_remove(target_doc, objects)
auto_routing._remove_routing_connection_objects = failing_remove
try:
with self.assertRaises(auto_routing.AutoRoutingError):
auto_routing.route_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="wire-1",
)
finally:
auto_routing._remove_routing_connection_objects = original_remove
routed_wires = list(wiring_objects.iter_routed_wire_objects(doc))
self.assertEqual([first], routed_wires)
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_carrier_exposes_capacity_property_for_auto_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")
carrier = routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
self.assertIn("QetRouteCarrierCapacity", carrier.PropertiesList)
self.assertEqual(1, carrier.QetRouteCarrierCapacity)
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_eplan_connection_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_eplan_connection_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_route_graph_bridges_adjoining_wire_duct_gap_with_eplan_tolerance(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(50, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(54, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
network = routing_network.build_route_graph(doc)
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
self.assertEqual(1, network["bridged_segment_count"])
self.assertEqual("network-dijkstra-v1", result["algorithm"])
self.assertEqual("Routed", result["route_status"])
def test_auto_routing_respects_adjoining_duct_tolerance_option(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(44, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(56, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
result = auto_routing.route_eplan_connection_between_terminals(
doc,
start,
end,
options={"adjoining_duct_tolerance": 15.0},
)
self.assertEqual("Routed", result["route_status"])
self.assertEqual(1, result["network"]["bridged_segments"])
def test_connect_point_to_network_replaces_bridged_edge_without_stale_reverse_edge(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(50, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(54, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
network = routing_network.build_route_graph(doc)
original_keys = set(network["nodes"].keys())
bridge_keys = {
key
for key, point in network["nodes"].items()
if point.x in {50.0, 54.0}
}
projected_key, _distance, mode = routing_network.connect_point_to_network(network, app.Vector(52, 0, 20))
new_keys = set(network["nodes"].keys()) - original_keys
stale_bridge_edges = [
(left_key, right_key)
for left_key, neighbors in network["edges"].items()
for right_key, _weight, _carrier in neighbors
if left_key in bridge_keys and right_key in bridge_keys
]
self.assertEqual("segment_projection", mode)
self.assertEqual(projected_key, next(iter(new_keys)))
self.assertEqual([], stale_bridge_edges)
self.assertEqual(4, network["segment_count"])
def test_eplan_connection_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_eplan_connection_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_eplan_connection_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_detect_support_surface_refreshes_routing_range_geometry(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))
created = routing_network.create_surface_carriers_from_document(
doc,
project_uuid="project-1",
spacing=60.0,
offset=5.0,
margin=0.0,
)
panel.Shape = FakeShape(FakeBoundBox(20, 140, 0, 5, 0, 100))
created_again = routing_network.create_surface_carriers_from_document(
doc,
project_uuid="project-1",
spacing=60.0,
offset=5.0,
margin=0.0,
)
carriers = routing_network.collect_route_carriers(doc)
x_values = [
point.x
for carrier in carriers
if getattr(carrier, "QetRouteCarrierKind", "") == "RoutingRange"
for point in carrier.Points
]
self.assertEqual(6, len(created))
self.assertEqual(0, len(created_again))
self.assertEqual(6, len([carrier for carrier in carriers if carrier.QetRouteCarrierKind == "RoutingRange"]))
self.assertEqual(20.0, min(x_values))
self.assertEqual(140.0, max(x_values))
def test_auto_detect_support_surface_adds_missing_routing_range_lanes_after_resize(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))
created = routing_network.create_surface_carriers_from_document(
doc,
project_uuid="project-1",
spacing=60.0,
offset=5.0,
margin=0.0,
)
panel.Shape = FakeShape(FakeBoundBox(0, 180, 0, 5, 0, 120))
created_again = routing_network.create_surface_carriers_from_document(
doc,
project_uuid="project-1",
spacing=60.0,
offset=5.0,
margin=0.0,
)
carriers = [
carrier
for carrier in routing_network.collect_route_carriers(doc)
if getattr(carrier, "QetRouteCarrierKind", "") == "RoutingRange"
]
x_values = [point.x for carrier in carriers for point in carrier.Points]
z_values = [point.z for carrier in carriers for point in carrier.Points]
self.assertEqual(6, len(created))
self.assertEqual(1, len(created_again))
self.assertEqual(7, len(carriers))
self.assertEqual(180.0, max(x_values))
self.assertEqual(120.0, max(z_values))
def test_auto_detect_support_surface_removes_stale_routing_range_lanes_after_resize(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))
created = routing_network.create_surface_carriers_from_document(
doc,
project_uuid="project-1",
spacing=60.0,
offset=5.0,
margin=0.0,
)
panel.Shape = FakeShape(FakeBoundBox(0, 60, 0, 5, 0, 60))
created_again = routing_network.create_surface_carriers_from_document(
doc,
project_uuid="project-1",
spacing=60.0,
offset=5.0,
margin=0.0,
)
carriers = [
carrier
for carrier in routing_network.collect_route_carriers(doc)
if getattr(carrier, "QetRouteCarrierKind", "") == "RoutingRange"
]
x_values = [point.x for carrier in carriers for point in carrier.Points]
z_values = [point.z for carrier in carriers for point in carrier.Points]
self.assertEqual(6, len(created))
self.assertEqual(0, len(created_again))
self.assertEqual(4, len(carriers))
self.assertEqual(60.0, max(x_values))
self.assertEqual(60.0, max(z_values))
def test_auto_detect_support_surface_removes_carriers_and_obstacle_mode_when_source_invalid(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))
created = routing_network.create_surface_carriers_from_document(
doc,
project_uuid="project-1",
spacing=60.0,
offset=5.0,
margin=0.0,
)
panel.Shape = FakeShape(FakeBoundBox(0, 120, 0, 120, 0, 120))
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.assertEqual([], routing_network.collect_route_carriers(doc))
self.assertEqual("", getattr(panel, "QetRoutingObstacleMode", ""))
self.assertEqual("", getattr(panel, "QetRouteCarrierNamesJson", ""))
def test_eplan_connection_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_eplan_connection_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_prepare_layout_space_auto_detects_support_surface_sources(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["support_surface_sources"], 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_routing_paths_does_not_duplicate_selected_wire_duct_carriers(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)],
)
first = auto_routing_panel.AutoRoutingController().generate_routing_paths()
second = auto_routing_panel.AutoRoutingController().generate_routing_paths()
carriers = routing_network.collect_route_carriers(doc)
self.assertEqual(1, first["selected_wire_duct_carriers"])
self.assertEqual(0, second["selected_wire_duct_carriers"])
self.assertEqual(
1,
len([item for item in carriers if item.QetRouteCarrierKind == "WireDuct"]),
)
self.assertEqual(
2,
len([item for item in carriers if item.QetRouteCarrierKind == "WireDuctOpenEnd"]),
)
def test_generate_routing_paths_refreshes_selected_wire_duct_geometry(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)],
)
auto_routing_panel.AutoRoutingController().generate_routing_paths()
duct.Shape = FakeShape(FakeBoundBox(0, 220, -10, 10, 0, 20))
second = auto_routing_panel.AutoRoutingController().generate_routing_paths()
carriers = routing_network.collect_route_carriers(doc)
main = [item for item in carriers if item.QetRouteCarrierKind == "WireDuct"][0]
open_end_x_values = sorted(
point.x
for item in carriers
if item.QetRouteCarrierKind == "WireDuctOpenEnd"
for point in item.Points
)
self.assertEqual(0, second["selected_wire_duct_carriers"])
self.assertEqual([(20.0, 0.0, 10.0), (200.0, 0.0, 10.0)], [(p.x, p.y, p.z) for p in main.Points])
self.assertEqual([20.0, 20.0, 200.0, 200.0], open_end_x_values)
def test_generate_routing_paths_removes_generated_wire_duct_carriers_after_source_deleted(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")
duct = doc.addObject("Part::Feature", "WireDuctA")
duct.Label = "Wire Duct A"
duct.Shape = FakeShape(FakeBoundBox(0, 160, -10, 10, 0, 20))
auto_routing_panel.AutoRoutingController().generate_routing_paths()
generated = [
item
for item in routing_network.collect_route_carriers(doc)
if getattr(item, "QetRouteSourceName", "") == "WireDuctA"
]
doc.removeObject("WireDuctA")
auto_routing_panel.AutoRoutingController().generate_routing_paths()
self.assertEqual(3, len(generated))
self.assertEqual([], routing_network.collect_route_carriers(doc))
def test_prepare_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["support_surface_sources"], 0)
self.assertEqual("document", result["source_mode"])
def test_generate_routing_path_network_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_routing_paths()
result_again = auto_routing_panel.AutoRoutingController().generate_routing_paths()
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["wire_duct_open_end_carriers"])
self.assertEqual(2, result["terminal_access_carriers"])
self.assertEqual(0, result_again["wire_duct_carriers"])
self.assertEqual(0, result_again["wire_duct_open_end_carriers"])
self.assertEqual(2, result_again["terminal_access_carriers"])
self.assertEqual(2, len(access_carriers))
self.assertGreater(result["network"]["segments"], 0)
def test_generate_routing_path_network_connects_terminal_access_to_nearest_segment_point(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, "TerminalMid", "terminal-mid", app.Vector(50, 30, 0))
duct = doc.addObject("Part::Feature", "WireDuctA")
duct.Label = "Wire Duct A"
duct.Shape = FakeShape(FakeBoundBox(0, 100, -5, 5, 15, 25))
auto_routing_panel.AutoRoutingController().generate_routing_paths()
access_carriers = [
carrier
for carrier in routing_network.collect_route_carriers(doc)
if getattr(carrier, "QetRouteCarrierKind", "") == "TerminalAccess"
]
self.assertEqual(1, len(access_carriers))
end_point = access_carriers[0].Points[-1]
self.assertEqual((50.0, 0.0, 20.0), (end_point.x, end_point.y, end_point.z))
def test_eplan_connection_route_enters_network_at_segment_projection(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(50, 0, 0))
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(150, 0, 0))
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(200, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
self.assertEqual("segment_projection", result["network"]["entry_point_mode"])
self.assertEqual("segment_projection", result["network"]["exit_point_mode"])
self.assertNotIn(0.0, [point.x for point in result["points"][1:-1]])
self.assertNotIn(200.0, [point.x for point in result["points"][1:-1]])
self.assertLess(result["length_mm"], 150.0)
def test_generate_routing_path_network_adds_wiring_cut_out_carrier(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")
cut_out = doc.addObject("Part::Feature", "WiringCutoutA")
cut_out.Label = "Wiring Cut-Out A"
cut_out.Shape = FakeShape(FakeBoundBox(45, 55, -2, 2, 15, 25))
result = auto_routing_panel.AutoRoutingController().generate_routing_paths()
cut_out_carriers = [
carrier
for carrier in routing_network.collect_route_carriers(doc)
if getattr(carrier, "QetRouteCarrierKind", "") == "WiringCutOut"
]
self.assertEqual(1, result["wiring_cut_out_carriers"])
self.assertEqual(1, len(cut_out_carriers))
self.assertEqual("PassThrough", cut_out.QetRoutingObstacleMode)
def test_generate_routing_path_network_refreshes_wiring_cut_out_geometry(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")
cut_out = doc.addObject("Part::Feature", "WiringCutoutA")
cut_out.Label = "Wiring Cut-Out A"
cut_out.Shape = FakeShape(FakeBoundBox(45, 55, -2, 2, 15, 25))
first = auto_routing_panel.AutoRoutingController().generate_routing_paths()
cut_out.Shape = FakeShape(FakeBoundBox(65, 75, -2, 2, 15, 25))
second = auto_routing_panel.AutoRoutingController().generate_routing_paths()
cut_out_carriers = [
carrier
for carrier in routing_network.collect_route_carriers(doc)
if getattr(carrier, "QetRouteCarrierKind", "") == "WiringCutOut"
]
self.assertEqual(1, first["wiring_cut_out_carriers"])
self.assertEqual(0, second["wiring_cut_out_carriers"])
self.assertEqual(1, len(cut_out_carriers))
self.assertEqual([(70.0, -22.0, 20.0), (70.0, 22.0, 20.0)], [(p.x, p.y, p.z) for p in cut_out_carriers[0].Points])
def test_wiring_cut_out_source_bridge_extension_controls_generated_path_length(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")
cut_out = doc.addObject("Part::Feature", "WiringCutoutA")
cut_out.Label = "过线孔A"
cut_out.Shape = FakeShape(FakeBoundBox(45, 55, -2, 2, 15, 25))
cut_out.QetWiringCutOutBridgeExtensionMm = 8.0
auto_routing_panel.AutoRoutingController().generate_routing_paths()
cut_out_carriers = [
carrier
for carrier in routing_network.collect_route_carriers(doc)
if getattr(carrier, "QetRouteCarrierKind", "") == "WiringCutOut"
]
self.assertEqual(1, len(cut_out_carriers))
self.assertIn("QetWiringCutOutBridgeExtensionMm", cut_out.PropertiesList)
self.assertEqual(8.0, cut_out.QetWiringCutOutBridgeExtensionMm)
self.assertEqual([(50.0, -10.0, 20.0), (50.0, 10.0, 20.0)], [(p.x, p.y, p.z) for p in cut_out_carriers[0].Points])
def test_wiring_cut_out_bridges_nearby_ducts_on_both_sides_of_panel(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")
start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, -20, 0))
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 20, 0))
routing_network.create_route_carrier(
doc,
[app.Vector(0, -20, 20), app.Vector(50, -20, 20)],
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(50, 20, 20), app.Vector(100, 20, 20)],
project_uuid="project-1",
kind="WireDuct",
)
cut_out = doc.addObject("Part::Feature", "WiringCutoutA")
cut_out.Label = "过线孔A"
cut_out.Shape = FakeShape(FakeBoundBox(45, 55, -2, 2, 15, 25))
auto_routing_panel.AutoRoutingController().generate_routing_paths()
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
self.assertEqual("Routed", result["route_status"])
self.assertIn("WiringCutOut", result["route_track"]["carrier_kinds"])
self.assertEqual(0, result["collision_count"])
def test_check_routing_path_network_writes_diagnostic_for_unconnected_terminal(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")
_terminal(doc, terminal_objects, "TerminalFar", "terminal-far", app.Vector(5000, 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.check_eplan_routing_path_network(doc, project_uuid="project-1")
diagnostic_group = doc.getObject("QETWiring_05_Diagnostics")
payload = json.loads(diagnostic_group.Group[0].QetDiagnosticJson)
self.assertFalse(result["ok"])
self.assertEqual("RoutingPathNetwork", diagnostic_group.Group[0].QetDiagnosticKind)
self.assertEqual(1, len(payload["unconnected_terminals"]))
self.assertEqual("terminal-far", payload["unconnected_terminals"][0]["terminal_uuid"])
self.assertEqual(1000.0, payload["unconnected_terminals"][0]["terminal_access_max_distance_mm"])
message = auto_routing.format_routing_path_network_report(result["diagnostic"])
self.assertIn("端子未接入", message)
self.assertIn("terminal-far", message)
self.assertIn("4900.0 mm", message)
self.assertIn("端子接入最大距离 1000.0 mm", message)
self.assertIn("补一段线槽/辅助路径", message)
def test_format_routing_path_network_report_tolerates_malformed_samples(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
diagnostic = {
"issues": [{"code": "external_issue", "count": 1}],
"unconnected_terminals": ["bad-terminal-sample"],
"possible_breaks": ["bad-break-sample"],
"isolated_components": ["bad-component-sample"],
}
message = auto_routing.format_routing_path_network_report(diagnostic)
self.assertIn("布线路径网络检查发现", message)
self.assertIn("首个问题external_issue", message)
def test_format_routing_path_network_report_calls_out_wire_duct_break_point(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")
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
label="线槽A",
project_uuid="project-1",
kind="WireDuct",
)
result = auto_routing.check_eplan_routing_path_network(doc, project_uuid="project-1")
message = auto_routing.format_routing_path_network_report(result["diagnostic"])
self.assertIn("线槽端点疑似断开", message)
self.assertIn("线槽A", message)
self.assertIn("(0.0, 0.0, 20.0)", message)
self.assertIn("补齐相邻线槽", message)
def test_format_routing_path_network_report_includes_bridged_segment_count(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
diagnostic = {
"summary": {
"carriers": 5,
"segments": 6,
"nodes": 5,
"bridged_segments": 1,
},
"issues": [],
"ok": True,
}
message = auto_routing.format_routing_path_network_report(diagnostic)
self.assertIn("桥接 1 段", message)
def test_check_routing_path_network_uses_adjoining_duct_tolerance_option(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")
for index, points in enumerate(
(
[app.Vector(0, 0, 20), app.Vector(44, 0, 20)],
[app.Vector(56, 0, 20), app.Vector(100, 0, 20)],
[app.Vector(100, 0, 20), app.Vector(100, 100, 20)],
[app.Vector(100, 100, 20), app.Vector(0, 100, 20)],
[app.Vector(0, 100, 20), app.Vector(0, 0, 20)],
),
start=1,
):
routing_network.create_route_carrier(
doc,
points,
label="线槽{0}".format(index),
project_uuid="project-1",
kind="WireDuct",
)
result = auto_routing.check_eplan_routing_path_network(
doc,
project_uuid="project-1",
options={"adjoining_duct_tolerance": 15.0},
)
self.assertTrue(result["ok"])
self.assertEqual(1, result["diagnostic"]["summary"]["bridged_segments"])
self.assertEqual([], result["diagnostic"]["possible_breaks"])
def test_generate_routing_path_network_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_routing_paths()
self.assertEqual(1, result["wire_duct_carriers"])
self.assertEqual(2, result["wire_duct_open_end_carriers"])
self.assertEqual(0, result["terminal_access_carriers"])
def test_auto_routing_controller_exposes_terminal_access_max_distance(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))
controller = auto_routing_panel.AutoRoutingController()
controller.set_terminal_access_max_distance(6000.0)
result = controller.generate_routing_paths()
self.assertEqual(1, result["terminal_access_carriers"])
self.assertEqual(6000.0, controller.routing_options()["terminal_access_max_distance"])
def test_auto_routing_controller_exposes_terminal_exit_length(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(50, 0, 0))
duct = doc.addObject("Part::Feature", "WireDuctA")
duct.Label = "Wire Duct A"
duct.Shape = FakeShape(FakeBoundBox(0, 100, -5, 5, 15, 25))
controller = auto_routing_panel.AutoRoutingController()
controller.set_terminal_exit_length(40.0)
controller.generate_routing_paths()
access_carriers = [
carrier
for carrier in routing_network.collect_route_carriers(doc)
if getattr(carrier, "QetRouteCarrierKind", "") == "TerminalAccess"
]
self.assertEqual(1, len(access_carriers))
self.assertEqual(
(50.0, 0.0, 40.0),
tuple(getattr(access_carriers[0].Points[0], axis) for axis in ("x", "y", "z")),
)
self.assertEqual(40.0, controller.routing_options()["terminal_exit_length"])
def test_route_eplan_connections_prepares_layout_space_like_eplan_route(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_eplan_connections()
self.assertEqual(1, report["routed"])
self.assertEqual("eplan-route-v1", report["routing_method"])
self.assertTrue(report["routing_path_network_updated"])
self.assertEqual(1, report["prepared_layout"]["wire_duct_carriers"])
self.assertEqual(1, report["routing_path_network"]["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_routing_controller_passes_adjoining_duct_tolerance_to_batch_route(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))
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(44, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(56, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
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(
options={"adjoining_duct_tolerance": 15.0}
).route_eplan_connections()
self.assertEqual(1, report["routed"])
self.assertEqual(1, report["routes"][0]["network"]["bridged_segments"])
def test_auto_routing_controller_summary_uses_adjoining_duct_tolerance(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")
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(44, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(56, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
summary = auto_routing_panel.AutoRoutingController(
options={"adjoining_duct_tolerance": 15.0}
).summary()
self.assertIn("桥接1", summary)
def test_eplan_connection_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_eplan_connection_between_terminals(doc, start, end)
def test_route_eplan_connection_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_eplan_connection_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(3, len(created))
carrier = [item for item in created if item.QetRouteCarrierKind == "WireDuct"][0]
open_ends = [item for item in created if item.QetRouteCarrierKind == "WireDuctOpenEnd"]
self.assertEqual("WireDuct", carrier.QetRouteCarrierKind)
self.assertEqual(2, len(open_ends))
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_wire_duct_source_end_margin_controls_generated_centerline_length(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))
duct.QetWireDuctEndMarginMm = 5.0
created = routing_network.create_wire_duct_carriers_from_document(
doc,
project_uuid="project-1",
)
carrier = [item for item in created if item.QetRouteCarrierKind == "WireDuct"][0]
self.assertIn("QetWireDuctEndMarginMm", duct.PropertiesList)
self.assertEqual(5.0, duct.QetWireDuctEndMarginMm)
self.assertEqual([(5.0, 0.0, 15.0), (115.0, 0.0, 15.0)], [(p.x, p.y, p.z) for p in carrier.Points])
def test_wire_duct_source_capacity_is_copied_to_generated_carriers(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))
duct.QetRouteCarrierCapacity = 4
created = routing_network.create_wire_duct_carriers_from_selection(
doc,
[FakeSelectionItem(obj=duct)],
project_uuid="project-1",
margin=20.0,
)
self.assertIn("QetRouteCarrierCapacity", duct.PropertiesList)
self.assertTrue(all(item.QetRouteCarrierCapacity == 4 for item in created))
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(3, len(created))
self.assertEqual(0, len(created_again))
self.assertEqual(1, len([item for item in created if item.QetRouteCarrierKind == "WireDuct"]))
self.assertEqual(2, len([item for item in created if item.QetRouteCarrierKind == "WireDuctOpenEnd"]))
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_eplan_connection_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_eplan_connection_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_eplan_connection_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_eplan_connection_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_eplan_connection_between_terminals(doc, start, end)
self.assertEqual("CollisionWarning", result["route_status"])
self.assertEqual("CollisionWarning", result["wire"].RouteStatus)
self.assertEqual(1, result["collision_count"])
self.assertEqual("HardIntersection", result["collisions"][0]["collision_kind"])
def test_eplan_connection_route_marks_clearance_warning_against_expanded_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", "NearObstacle")
obstacle.Shape = FakeShape(FakeBoundBox(40, 60, 3, 6, 90, 110))
result = auto_routing.route_eplan_connection_between_terminals(
doc,
start,
end,
options={"obstacle_clearance": 5.0},
)
self.assertEqual("CollisionWarning", result["route_status"])
self.assertEqual(1, result["collision_count"])
self.assertEqual("ClearanceWarning", result["collisions"][0]["collision_kind"])
self.assertEqual(3.0, result["collisions"][0]["obstacle_bbox"]["ymin"])
self.assertEqual(-2.0, result["collisions"][0]["collision_bbox"]["ymin"])
def test_eplan_connection_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_eplan_connection_between_terminals(doc, start, end)
self.assertEqual("Routed", result["route_status"])
self.assertEqual(0, result["collision_count"])
def test_eplan_connection_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_eplan_connection_between_terminals(doc, start, end)
self.assertEqual("Routed", result["route_status"])
self.assertEqual(0, result["collision_count"])
def test_route_eplan_connections_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_eplan_connections_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_eplan_connections_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_eplan_connections_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("RoutingConnectionBatch", diagnostic.QetDiagnosticKind)
self.assertIn("terminal-missing", diagnostic.QetDiagnosticJson)
def test_route_eplan_connections_reports_total_connection_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_eplan_connections_from_payload(doc, payload)
message = auto_routing.format_eplan_connection_route_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_report_includes_route_source_sample_when_available(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 1,
"collision_warnings": 0,
"skipped_missing_terminal": 0,
"routes": [
{
"wire_label": "N4111",
"route_track": {
"segments": [
{"carrier": {"kind": "TerminalAccess", "source_label": "QF1:A1"}},
{"carrier": {"kind": "WireDuct", "source_label": "线槽A"}},
{"carrier": {"kind": "WiringCutOut", "source_label": "过线孔A"}},
{"carrier": {"kind": "WireDuct", "source_label": "线槽A"}},
]
},
}
],
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("路径示例:导线 N4111 经过 QF1:A1、线槽A、过线孔A。", message)
def test_route_report_includes_network_bridge_and_blocked_segment_counts(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 1,
"collision_warnings": 0,
"skipped_missing_terminal": 0,
"routes": [
{
"network": {
"bridged_segments": 1,
"blocked_segments": 2,
},
}
],
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("路径网络:自动桥接 1 段相邻线槽,避障屏蔽 2 段。", message)
def test_route_report_includes_parallel_lane_summary(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 2,
"collision_warnings": 0,
"skipped_missing_terminal": 0,
"routes": [
{"lane": {"index": 0, "axis": "y", "spacing_mm": 10.0, "offset_mm": 0.0}},
{"lane": {"index": 2, "axis": "y", "spacing_mm": 10.0, "offset_mm": -10.0}},
],
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("并行错位:最大 lane 2间距 10.0 mm。", message)
def test_route_eplan_connections_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_eplan_connections_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"])
self.assertEqual("WireDuct", route["route_track"]["segments"][0]["carrier"]["kind"])
def test_route_eplan_connections_preserves_endpoint_metadata_on_routed_wire(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_element_uuid": "device-a",
"start_terminal_uuid": "terminal-start",
"start_terminal_display": "A1",
"end_element_uuid": "device-b",
"end_terminal_uuid": "terminal-end",
"end_terminal_display": "B1",
}
],
}
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
routed_group = doc.getObject("QETWiring_04_Routed")
wire = routed_group.Group[0]
diagnostics = json.loads(wire.QetRouteDiagnosticsJson)
self.assertEqual("device-a", wire.QetStartElementUuid)
self.assertEqual("A1", wire.QetStartTerminalDisplay)
self.assertEqual("device-b", wire.QetEndElementUuid)
self.assertEqual("B1", wire.QetEndTerminalDisplay)
self.assertEqual("device-a", report["routes"][0]["start_element_uuid"])
self.assertEqual("B1", report["routes"][0]["end_terminal_display"])
self.assertEqual("A1", diagnostics["endpoint_metadata"]["start_terminal_display"])
def test_route_eplan_connection_tasks_preserve_task_endpoint_labels_on_routed_wire(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",
)
task = wiring_objects.create_wire_task(
doc,
"project-1",
"wire-1",
"N4111",
"terminal-start",
"terminal-end",
"instance-a",
"instance-b",
)
terminal_objects.ensure_string_property(task, "QetStartDeviceLabel", "QET Wiring", "", "QF1")
terminal_objects.ensure_string_property(task, "QetEndDeviceLabel", "QET Wiring", "", "X1")
terminal_objects.ensure_string_property(task, "QetEndpointLabel", "QET Wiring", "", "QF1:A1 -> X1:B1")
report = auto_routing.route_eplan_connection_tasks(doc)
routed_group = doc.getObject("QETWiring_04_Routed")
wire = routed_group.Group[0]
diagnostics = json.loads(wire.QetRouteDiagnosticsJson)
self.assertEqual("QF1", wire.QetStartDeviceLabel)
self.assertEqual("X1", wire.QetEndDeviceLabel)
self.assertEqual("QF1:A1 -> X1:B1", wire.QetEndpointLabel)
self.assertEqual("QF1:A1 -> X1:B1", report["routes"][0]["endpoint_label"])
self.assertEqual("QF1", diagnostics["endpoint_metadata"]["start_device_label"])
def test_route_eplan_connections_records_wire_identity_for_errors(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))
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-bad",
"wire_label": "N500",
"start_terminal_uuid": "terminal-start",
"end_terminal_uuid": "terminal-start",
}
],
}
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
self.assertEqual(1, len(report["errors"]))
self.assertIn("error_samples", report)
self.assertEqual("wire-bad", report["error_samples"][0]["wire_uuid"])
self.assertEqual("N500", report["error_samples"][0]["wire_label"])
self.assertEqual("terminal-start", report["error_samples"][0]["start_terminal_uuid"])
self.assertEqual("terminal-start", report["error_samples"][0]["end_terminal_uuid"])
self.assertIn("different", report["error_samples"][0]["error"])
def test_route_eplan_connections_report_includes_readable_error_sample(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))
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-bad",
"wire_label": "N500",
"start_element_uuid": "device-a",
"start_terminal_uuid": "terminal-start",
"start_terminal_display": "A1",
"end_element_uuid": "device-a",
"end_terminal_uuid": "terminal-start",
"end_terminal_display": "A1",
}
],
}
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
message = auto_routing.format_eplan_connection_route_report(report)
self.assertEqual("device-a", report["error_samples"][0]["start_element_uuid"])
self.assertIn("错误示例:导线 N500", message)
self.assertIn("device-a/A1 (terminal-start) -> device-a/A1 (terminal-start)", message)
def test_route_eplan_connections_counts_route_statuses_for_summary(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, "RouteStart", "route-start", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "RouteEnd", "route-end", app.Vector(100, 0, 0))
_terminal(doc, terminal_objects, "CollisionStart", "collision-start", app.Vector(0, 100, 0))
_terminal(doc, terminal_objects, "CollisionEnd", "collision-end", app.Vector(100, 100, 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, 100, 100), app.Vector(100, 100, 100)],
project_uuid="project-1",
kind="WireDuct",
)
obstacle = doc.addObject("Part::Feature", "CollisionObstacle")
obstacle.Shape = FakeShape(FakeBoundBox(40, 60, 90, 110, 90, 110))
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-ok",
"start_terminal_uuid": "route-start",
"end_terminal_uuid": "route-end",
},
{
"wire_id": "wire-collision",
"start_terminal_uuid": "collision-start",
"end_terminal_uuid": "collision-end",
},
{
"wire_id": "wire-error",
"start_terminal_uuid": "route-start",
"end_terminal_uuid": "route-start",
},
],
}
report = auto_routing.route_eplan_connections_from_payload(
doc,
payload,
options={"avoid_obstacles": False},
)
message = auto_routing.format_eplan_connection_route_report(report)
self.assertEqual(1, report["route_status_counts"]["Routed"])
self.assertEqual(1, report["route_status_counts"]["CollisionWarning"])
self.assertEqual(1, report["route_status_counts"]["Error"])
self.assertIn("结果状态", message)
self.assertIn("正常 1 条", message)
self.assertIn("碰撞告警 1 条", message)
self.assertIn("错误 1 条", message)
def test_route_eplan_connections_lane_index_is_per_terminal_pair(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, "TerminalStartA", "terminal-start-a", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndA", "terminal-end-a", app.Vector(100, 0, 0))
_terminal(doc, terminal_objects, "TerminalStartB", "terminal-start-b", app.Vector(0, 100, 0))
_terminal(doc, terminal_objects, "TerminalEndB", "terminal-end-b", app.Vector(100, 100, 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, 100, 20), app.Vector(100, 100, 20)],
project_uuid="project-1",
kind="WireDuct",
)
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-a",
"start_terminal_uuid": "terminal-start-a",
"end_terminal_uuid": "terminal-end-a",
},
{
"wire_id": "wire-b",
"start_terminal_uuid": "terminal-start-b",
"end_terminal_uuid": "terminal-end-b",
},
{
"wire_id": "wire-a-repeat",
"start_terminal_uuid": "terminal-start-a",
"end_terminal_uuid": "terminal-end-a",
},
],
}
report = auto_routing.route_eplan_connections_from_payload(
doc,
payload,
options={"lane_spacing": 10.0, "lane_axis": "y"},
)
self.assertEqual(0, report["routes"][0]["lane"]["index"])
self.assertEqual(0, report["routes"][1]["lane"]["index"])
self.assertEqual(1, report["routes"][2]["lane"]["index"])
def test_route_eplan_connections_lane_index_increments_for_shared_route_segments(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, "TerminalStartA", "terminal-start-a", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndA", "terminal-end-a", app.Vector(100, 0, 0))
_terminal(doc, terminal_objects, "TerminalStartB", "terminal-start-b", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndB", "terminal-end-b", 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-a",
"start_terminal_uuid": "terminal-start-a",
"end_terminal_uuid": "terminal-end-a",
},
{
"wire_id": "wire-b",
"start_terminal_uuid": "terminal-start-b",
"end_terminal_uuid": "terminal-end-b",
},
],
}
report = auto_routing.route_eplan_connections_from_payload(
doc,
payload,
options={"lane_spacing": 10.0, "lane_axis": "y"},
)
self.assertEqual(0, report["routes"][0]["lane"]["index"])
self.assertEqual(1, report["routes"][1]["lane"]["index"])
routed_group = doc.getObject("QETWiring_04_Routed")
second_wire = [
wire
for wire in list(getattr(routed_group, "Group", []) or [])
if getattr(wire, "QetWireUuid", "") == "wire-b"
][0]
self.assertTrue(any(abs(point.y - 10.0) <= 0.001 for point in second_wire.Points[1:-1]))
def test_route_eplan_connections_auto_lane_axis_offsets_perpendicular_to_shared_segment(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, "TerminalStartA", "terminal-start-a", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndA", "terminal-end-a", app.Vector(0, 100, 0))
_terminal(doc, terminal_objects, "TerminalStartB", "terminal-start-b", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndB", "terminal-end-b", app.Vector(0, 100, 0))
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(0, 100, 20)],
project_uuid="project-1",
kind="WireDuct",
)
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-a",
"start_terminal_uuid": "terminal-start-a",
"end_terminal_uuid": "terminal-end-a",
},
{
"wire_id": "wire-b",
"start_terminal_uuid": "terminal-start-b",
"end_terminal_uuid": "terminal-end-b",
},
],
}
report = auto_routing.route_eplan_connections_from_payload(
doc,
payload,
options={"lane_spacing": 10.0},
)
self.assertEqual(1, report["routes"][1]["lane"]["index"])
self.assertEqual("x", report["routes"][1]["lane"]["axis"])
routed_group = doc.getObject("QETWiring_04_Routed")
second_wire = [
wire
for wire in list(getattr(routed_group, "Group", []) or [])
if getattr(wire, "QetWireUuid", "") == "wire-b"
][0]
self.assertTrue(any(abs(point.x - 10.0) <= 0.001 for point in second_wire.Points[1:-1]))
self.assertFalse(all(abs(point.x) <= 0.001 for point in second_wire.Points[1:-1]))
def test_route_eplan_connections_prefers_unused_alternate_route_segments(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, "TerminalStartA", "terminal-start-a", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndA", "terminal-end-a", app.Vector(100, 0, 0))
_terminal(doc, terminal_objects, "TerminalStartB", "terminal-start-b", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndB", "terminal-end-b", app.Vector(100, 0, 0))
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
label="Direct Duct",
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(0, 40, 20)],
label="Left Bridge",
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(0, 40, 20), app.Vector(100, 40, 20)],
label="Alternate Duct",
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(100, 40, 20), app.Vector(100, 0, 20)],
label="Right Bridge",
project_uuid="project-1",
kind="WireDuct",
)
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-a",
"start_terminal_uuid": "terminal-start-a",
"end_terminal_uuid": "terminal-end-a",
},
{
"wire_id": "wire-b",
"start_terminal_uuid": "terminal-start-b",
"end_terminal_uuid": "terminal-end-b",
},
],
}
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
first_labels = [
segment["carrier"]["label"]
for segment in report["routes"][0]["route_track"]["segments"]
]
second_labels = [
segment["carrier"]["label"]
for segment in report["routes"][1]["route_track"]["segments"]
]
self.assertIn("Direct Duct", first_labels)
self.assertIn("Alternate Duct", second_labels)
self.assertNotIn("Direct Duct", second_labels)
def test_route_eplan_connections_respects_route_segment_capacity_before_detouring(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, "TerminalStartA", "terminal-start-a", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndA", "terminal-end-a", app.Vector(100, 0, 0))
_terminal(doc, terminal_objects, "TerminalStartB", "terminal-start-b", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndB", "terminal-end-b", app.Vector(100, 0, 0))
_terminal(doc, terminal_objects, "TerminalStartC", "terminal-start-c", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEndC", "terminal-end-c", app.Vector(100, 0, 0))
direct = routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
label="Direct Duct",
project_uuid="project-1",
kind="WireDuct",
)
direct.QetRouteCarrierCapacity = 2
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(0, 40, 20)],
label="Left Bridge",
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(0, 40, 20), app.Vector(100, 40, 20)],
label="Alternate Duct",
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(100, 40, 20), app.Vector(100, 0, 20)],
label="Right Bridge",
project_uuid="project-1",
kind="WireDuct",
)
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-a",
"start_terminal_uuid": "terminal-start-a",
"end_terminal_uuid": "terminal-end-a",
},
{
"wire_id": "wire-b",
"start_terminal_uuid": "terminal-start-b",
"end_terminal_uuid": "terminal-end-b",
},
{
"wire_id": "wire-c",
"start_terminal_uuid": "terminal-start-c",
"end_terminal_uuid": "terminal-end-c",
},
],
}
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
route_labels = [
[segment["carrier"]["label"] for segment in route["route_track"]["segments"]]
for route in report["routes"]
]
self.assertIn("Direct Duct", route_labels[0])
self.assertIn("Direct Duct", route_labels[1])
self.assertIn("Alternate Duct", route_labels[2])
self.assertNotIn("Direct Duct", route_labels[2])
def test_route_eplan_connections_prefers_unused_segments_occupied_by_existing_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)],
label="Direct Duct",
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(0, 40, 20)],
label="Left Bridge",
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(0, 40, 20), app.Vector(100, 40, 20)],
label="Alternate Duct",
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(100, 40, 20), app.Vector(100, 0, 20)],
label="Right Bridge",
project_uuid="project-1",
kind="WireDuct",
)
auto_routing.route_eplan_connection_between_terminals(
doc,
start,
end,
wire_uuid="existing-wire",
)
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "new-wire",
"start_terminal_uuid": "terminal-start",
"end_terminal_uuid": "terminal-end",
}
],
}
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
route_labels = [
segment["carrier"]["label"]
for segment in report["routes"][0]["route_track"]["segments"]
]
self.assertIn("Alternate Duct", route_labels)
self.assertNotIn("Direct Duct", route_labels)
def test_route_eplan_connections_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_eplan_connections_from_payload(doc, payload)
message = auto_routing.format_eplan_connection_route_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("HardIntersection", report["collision_samples"][0]["collision_kind"])
self.assertEqual({"x": 0.0, "y": 0.0, "z": 100.0}, report["collision_samples"][0]["segment_start"])
self.assertEqual({"x": 100.0, "y": 0.0, "z": 100.0}, report["collision_samples"][0]["segment_end"])
self.assertEqual(40.0, report["collision_samples"][0]["obstacle_bbox"]["xmin"])
self.assertEqual(35.0, report["collision_samples"][0]["collision_bbox"]["xmin"])
self.assertEqual("Middle Obstacle", report["routes"][0]["collision_samples"][0]["obstacle_label"])
self.assertIn("碰撞示例", message)
self.assertIn("Middle Obstacle", message)
def test_route_eplan_connections_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_eplan_connections_from_payload(doc, payload)
message = auto_routing.format_eplan_connection_route_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_eplan_connections_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_eplan_connection_route_report(report)
self.assertIn("routed=1", message)
self.assertIn("线槽路径 2 条", message)
self.assertIn("首个错误:没有可用的线槽/路由路径网络", message)
self.assertIn("缺失示例terminal-a -> terminal-b", message)
def test_route_eplan_connections_report_includes_readable_missing_endpoint_labels(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 0,
"collision_warnings": 0,
"skipped_missing_terminal": 1,
"missing_endpoint_samples": [
{
"start_terminal_uuid": "terminal-a",
"start_element_uuid": "device-a",
"start_terminal_display": "A1",
"end_terminal_uuid": "terminal-b",
"end_element_uuid": "device-b",
"end_terminal_display": "B1",
}
],
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("缺失示例device-a/A1 (terminal-a) -> device-b/B1 (terminal-b)", message)
def test_route_eplan_connections_report_identifies_which_endpoint_is_missing(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 0,
"collision_warnings": 0,
"skipped_missing_terminal": 1,
"missing_endpoint_samples": [
{
"start_terminal_uuid": "terminal-start",
"start_found": True,
"end_terminal_uuid": "terminal-missing",
"end_found": False,
}
],
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("缺失示例terminal-start -> terminal-missing", message)
self.assertIn("缺失:终点", message)
def test_route_eplan_connections_report_calls_out_clearance_collision_kind(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 1,
"collision_warnings": 1,
"skipped_missing_terminal": 0,
"collision_samples": [
{
"wire_label": "N4111",
"obstacle_label": "柜体侧板",
"collision_kind": "ClearanceWarning",
}
],
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("安全间隙", message)
self.assertIn("柜体侧板", message)
def test_route_eplan_connections_report_ignores_non_numeric_status_counts(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 1,
"collision_warnings": 0,
"skipped_missing_terminal": 0,
"route_status_counts": {
"Routed": "1",
"ExternalStatus": "not-a-number",
},
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("正常 1 条", message)
self.assertNotIn("ExternalStatus", 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_eplan_connections_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,
)
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
)
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_eplan_connections_from_payload(
doc,
payload,
)
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_eplan_connection_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()