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...

4433 lines
189 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_route_track_records_carrier_capacity(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",
capacity=3,
)
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
route_track = json.loads(result["wire"].QetRouteTrackJson)
self.assertEqual(3, route_track["segments"][0]["carrier"]["capacity"])
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_set_route_carriers_visibility_toggles_only_route_helpers(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, 0), app.Vector(100, 0, 0)],
project_uuid="project-1",
kind="WireDuct",
)
device = doc.addObject("Part::Feature", "DeviceA")
device.ViewObject.Visibility = True
hidden = routing_network.set_route_carriers_visibility(doc, False)
self.assertFalse(carrier.ViewObject.Visibility)
shown = routing_network.set_route_carriers_visibility(doc, True)
self.assertEqual(1, hidden)
self.assertEqual(1, shown)
self.assertTrue(carrier.ViewObject.Visibility)
self.assertTrue(device.ViewObject.Visibility)
def test_collect_route_carriers_ignores_deleted_object_references(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, 0), app.Vector(100, 0, 0)],
project_uuid="project-1",
kind="WireDuct",
)
class DeletedObjectReference:
Name = "DeletedCarrier"
def __getattr__(self, name):
if name == "QetRoutingRole":
raise RuntimeError("Cannot access attribute 'QetRoutingRole' of deleted object")
raise AttributeError(name)
doc.Objects.append(DeletedObjectReference())
carriers = routing_network.collect_route_carriers(doc)
self.assertEqual([carrier], carriers)
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_route_graph_bridges_adjoining_user_path_to_wire_duct_gap(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(50, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(60, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
kind="UserPath",
)
network = routing_network.build_route_graph(doc, adjoining_duct_tolerance=15.0)
start_key, _start_distance = routing_network.nearest_node(network, app.Vector(0, 0, 20))
end_key, _end_distance = routing_network.nearest_node(network, app.Vector(100, 0, 20))
result = routing_network.shortest_path_with_carriers(network, start_key, end_key)
self.assertEqual(1, network["bridged_segment_count"])
self.assertIsNotNone(result)
self.assertIn("UserPath", result["carrier_kinds"])
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_auto_routing_uses_bridged_user_path_to_wire_duct_gap(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(60, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
kind="UserPath",
)
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"])
self.assertIn("WireDuct", result["route_track"]["carrier_kinds"])
self.assertIn("UserPath", result["route_track"]["carrier_kinds"])
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_uses_selected_route_path_as_user_path(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")
route_path = doc.addObject("Part::Feature", "UserRouteSketch")
route_path.Label = "用户主路径A"
route_path.Shape = FakeShape(
FakeBoundBox(0, 100, 0, 80, 20, 20),
edges=[
FakeEdge(app.Vector(0, 0, 20), app.Vector(0, 80, 20)),
FakeEdge(app.Vector(0, 80, 20), app.Vector(100, 80, 20)),
],
)
gui.Selection = types.SimpleNamespace(
getSelection=lambda: [],
getSelectionEx=lambda: [FakeSelectionItem(obj=route_path)],
)
result = auto_routing_panel.AutoRoutingController().generate_routing_paths()
carriers = routing_network.collect_route_carriers(doc)
user_paths = [item for item in carriers if item.QetRouteCarrierKind == "UserPath"]
self.assertEqual(1, result["user_path_carriers"])
self.assertEqual(1, len(user_paths))
self.assertEqual("UserRouteSketch", user_paths[0].QetRouteSourceName)
self.assertEqual("用户主路径A", user_paths[0].QetRouteSourceLabel)
self.assertEqual("selection", result["source_mode"])
def test_controller_creates_selected_user_paths_without_full_network_generation(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")
route_path = doc.addObject("Part::Feature", "UserRouteSketch")
route_path.Shape = FakeShape(
FakeBoundBox(0, 100, 0, 80, 20, 20),
edges=[FakeEdge(app.Vector(0, 0, 20), app.Vector(100, 0, 20))],
)
gui.Selection = types.SimpleNamespace(
getSelection=lambda: [],
getSelectionEx=lambda: [FakeSelectionItem(obj=route_path)],
)
result = auto_routing_panel.AutoRoutingController().create_user_paths_from_selection()
carriers = routing_network.collect_route_carriers(doc)
self.assertEqual(1, result["user_path_carriers"])
self.assertEqual(1, result["network"]["kinds"]["UserPath"])
self.assertEqual(1, len(carriers))
self.assertEqual("UserPath", carriers[0].QetRouteCarrierKind)
def test_selected_points_object_can_be_used_as_user_path(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")
route_path = doc.addObject("Part::Feature", "PointRoute")
route_path.Points = [
app.Vector(0, 0, 20),
app.Vector(40, 0, 20),
app.Vector(40, 30, 20),
]
gui.Selection = types.SimpleNamespace(
getSelection=lambda: [],
getSelectionEx=lambda: [FakeSelectionItem(obj=route_path)],
)
result = auto_routing_panel.AutoRoutingController().create_user_paths_from_selection()
carriers = routing_network.collect_route_carriers(doc)
self.assertEqual(1, result["user_path_carriers"])
self.assertEqual(
[(0.0, 0.0, 20.0), (40.0, 0.0, 20.0), (40.0, 30.0, 20.0)],
[(point.x, point.y, point.z) for point in carriers[0].Points],
)
def test_selected_user_path_copies_source_capacity(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")
route_path = doc.addObject("Part::Feature", "PointRoute")
route_path.Points = [app.Vector(0, 0, 20), app.Vector(100, 0, 20)]
route_path.QetRouteCarrierCapacity = 5
gui.Selection = types.SimpleNamespace(
getSelection=lambda: [],
getSelectionEx=lambda: [FakeSelectionItem(obj=route_path)],
)
auto_routing_panel.AutoRoutingController().create_user_paths_from_selection()
carrier = routing_network.collect_route_carriers(doc)[0]
self.assertEqual(5, carrier.QetRouteCarrierCapacity)
def test_controller_create_user_paths_reports_removed_stale_source_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")
route_path = doc.addObject("Part::Feature", "UserRouteSketch")
route_path.Shape = FakeShape(
FakeBoundBox(0, 100, 0, 80, 20, 20),
edges=[FakeEdge(app.Vector(0, 0, 20), app.Vector(100, 0, 20))],
)
gui.Selection = types.SimpleNamespace(
getSelection=lambda: [],
getSelectionEx=lambda: [FakeSelectionItem(obj=route_path)],
)
auto_routing_panel.AutoRoutingController().create_user_paths_from_selection()
doc.removeObject("UserRouteSketch")
gui.Selection = types.SimpleNamespace(
getSelection=lambda: [],
getSelectionEx=lambda: [],
)
result = auto_routing_panel.AutoRoutingController().create_user_paths_from_selection()
self.assertEqual(1, result["removed_stale_carriers"])
self.assertEqual(0, result["network"]["carriers"])
self.assertEqual([], routing_network.collect_route_carriers(doc))
def test_terminal_access_uses_terminal_local_route_points_before_main_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")
terminal = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
terminal.addProperty("App::PropertyString", "QetTerminalLocalRoutePointsJson", "QET Routing", "")
terminal.QetTerminalLocalRoutePointsJson = json.dumps([[0, 0, 0], [10, 0, 0], [10, 30, 0]])
routing_network.create_route_carrier(
doc,
[app.Vector(10, 80, 0), app.Vector(110, 80, 0)],
project_uuid="project-1",
kind="UserPath",
)
created = routing_network.create_terminal_access_carriers_from_document(
doc,
project_uuid="project-1",
terminal_exit_length=20.0,
max_distance=100.0,
)
self.assertEqual(1, len(created))
self.assertEqual(
[(0.0, 0.0, 0.0), (10.0, 0.0, 0.0), (10.0, 30.0, 0.0)],
[(p.x, p.y, p.z) for p in created[0].Points[:3]],
)
def test_generate_routing_paths_refreshes_selected_user_path_without_duplicate(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")
route_path = doc.addObject("Part::Feature", "UserRouteSketch")
route_path.Shape = FakeShape(
FakeBoundBox(0, 100, 0, 80, 20, 20),
edges=[FakeEdge(app.Vector(0, 0, 20), app.Vector(100, 0, 20))],
)
gui.Selection = types.SimpleNamespace(
getSelection=lambda: [],
getSelectionEx=lambda: [FakeSelectionItem(obj=route_path)],
)
first = auto_routing_panel.AutoRoutingController().generate_routing_paths()
route_path.Shape = FakeShape(
FakeBoundBox(0, 200, 0, 80, 20, 20),
edges=[FakeEdge(app.Vector(0, 0, 20), app.Vector(200, 0, 20))],
)
second = auto_routing_panel.AutoRoutingController().generate_routing_paths()
user_paths = [
item
for item in routing_network.collect_route_carriers(doc)
if item.QetRouteCarrierKind == "UserPath"
]
self.assertEqual(1, first["user_path_carriers"])
self.assertEqual(0, second["user_path_carriers"])
self.assertEqual(1, len(user_paths))
self.assertEqual([(0.0, 0.0, 20.0), (200.0, 0.0, 20.0)], [(p.x, p.y, p.z) for p in user_paths[0].Points])
def test_eplan_connection_route_can_use_generated_user_path(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(200, 0, 0))
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(200, 0, 20)],
project_uuid="project-1",
kind="UserPath",
)
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
self.assertEqual("Routed", result["route_status"])
self.assertIn("UserPath", result["route_track"]["carrier_kinds"])
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_check_routing_path_network_warns_for_long_terminal_access(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, "TerminalLongAccess", "terminal-long-access", app.Vector(0, 0, 0))
routing_network.create_route_carrier(
doc,
[app.Vector(900, 0, 20), app.Vector(1000, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_terminal_access_carriers_from_document(
doc,
project_uuid="project-1",
terminal_exit_length=20.0,
max_distance=1000.0,
)
result = auto_routing.check_eplan_routing_path_network(
doc,
project_uuid="project-1",
options={"terminal_access_max_distance": 1000.0},
)
diagnostic_group = doc.getObject("QETWiring_05_Diagnostics")
payload = json.loads(diagnostic_group.Group[0].QetDiagnosticJson)
message = auto_routing.format_routing_path_network_report(result["diagnostic"])
self.assertFalse(result["ok"])
self.assertEqual(1, len(payload["long_terminal_accesses"]))
self.assertEqual("terminal-long-access", payload["long_terminal_accesses"][0]["terminal_uuid"])
self.assertEqual(900.0, payload["long_terminal_accesses"][0]["terminal_access_length_mm"])
self.assertIn("端子接入过长", message)
self.assertIn("900.0 mm", message)
def test_check_routing_path_network_warns_for_invalid_terminal_local_route_points(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 = _terminal(doc, terminal_objects, "TerminalInvalidLocalPath", "terminal-invalid-local-path", app.Vector(0, 0, 0))
terminal.addProperty("App::PropertyString", "QetTerminalLocalRoutePointsJson", "QET Routing", "")
terminal.QetTerminalLocalRoutePointsJson = "{not-valid-json"
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_terminal_access_carriers_from_document(
doc,
project_uuid="project-1",
terminal_exit_length=20.0,
max_distance=1000.0,
)
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)
message = auto_routing.format_routing_path_network_report(result["diagnostic"])
self.assertFalse(result["ok"])
self.assertEqual(1, len(payload["invalid_terminal_local_routes"]))
self.assertEqual(
"terminal-invalid-local-path",
payload["invalid_terminal_local_routes"][0]["terminal_uuid"],
)
self.assertEqual(
"QetTerminalLocalRoutePointsJson",
payload["invalid_terminal_local_routes"][0]["property_name"],
)
self.assertIn("端子局部路径无效", message)
self.assertIn("terminal-invalid-local-path", message)
def test_check_routing_path_network_uses_terminal_local_route_end_for_connectivity(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 = _terminal(doc, terminal_objects, "TerminalLocalEndOnDuct", "terminal-local-end-on-duct", app.Vector(0, 0, 0))
terminal.addProperty("App::PropertyString", "QetTerminalLocalRoutePointsJson", "QET Routing", "")
terminal.QetTerminalLocalRoutePointsJson = json.dumps([[0, 0, 0], [1000, 0, 0]])
routing_network.create_route_carrier(
doc,
[app.Vector(1000, 0, 0), app.Vector(1100, 0, 0)],
project_uuid="project-1",
kind="WireDuct",
)
created = routing_network.create_terminal_access_carriers_from_document(
doc,
project_uuid="project-1",
terminal_exit_length=20.0,
max_distance=100.0,
)
result = auto_routing.check_eplan_routing_path_network(
doc,
project_uuid="project-1",
options={"terminal_access_max_distance": 100.0},
)
self.assertEqual([], created)
self.assertEqual([], result["diagnostic"]["unconnected_terminals"])
self.assertNotIn(
"unconnected_terminals",
[issue.get("code") for issue in result["diagnostic"]["issues"]],
)
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_check_routing_path_network_warns_when_network_is_empty(self):
_install_fake_freecad()
terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
doc = FakeDocument()
terminal_objects.ensure_root_group(doc, "project-1")
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)
message = auto_routing.format_routing_path_network_report(result["diagnostic"])
self.assertFalse(result["ok"])
self.assertEqual("empty_routing_path_network", payload["issues"][0]["code"])
self.assertEqual(0, payload["summary"]["segments"])
self.assertIn("布线路径网络为空", message)
def test_check_routing_path_network_warns_for_invalid_route_carrier_geometry(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)],
label="坏用户路径",
project_uuid="project-1",
kind="UserPath",
)
carrier.Points = [app.Vector(0, 0, 20)]
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)
message = auto_routing.format_routing_path_network_report(result["diagnostic"])
self.assertFalse(result["ok"])
self.assertEqual(1, len(payload["invalid_route_carriers"]))
self.assertEqual("UserPath", payload["invalid_route_carriers"][0]["carrier"]["kind"])
self.assertEqual(1, payload["invalid_route_carriers"][0]["point_count"])
self.assertIn("路径对象几何无效", message)
self.assertIn("坏用户路径", message)
def test_check_routing_path_network_warns_when_only_routing_range_is_available(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)],
project_uuid="project-1",
kind="RoutingRange",
)
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)
message = auto_routing.format_routing_path_network_report(result["diagnostic"])
self.assertFalse(result["ok"])
self.assertEqual(1, payload["routing_range_only_network"]["routing_range_carriers"])
self.assertEqual(
0,
payload["routing_range_only_network"]["primary_route_carriers"],
)
self.assertIn("routing_range_only_network", [issue.get("code") for issue in payload["issues"]])
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_auto_routing_controller_exposes_lane_spacing(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, "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",
)
app._qet_exchange_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",
},
],
}
controller = auto_routing_panel.AutoRoutingController()
controller.set_lane_spacing(14.0)
report = controller.route_eplan_connections()
self.assertEqual(14.0, controller.routing_options()["lane_spacing"])
self.assertEqual(14.0, report["routes"][1]["lane"]["spacing_mm"])
self.assertEqual(14.0, report["routes"][1]["lane"]["offset_mm"])
def test_auto_routing_controller_exposes_lane_axis(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, "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",
)
app._qet_exchange_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",
},
],
}
controller = auto_routing_panel.AutoRoutingController()
controller.set_lane_spacing(8.0)
controller.set_lane_axis("z")
report = controller.route_eplan_connections()
self.assertEqual("z", controller.routing_options()["lane_axis"])
self.assertEqual("z", report["routes"][1]["lane"]["axis"])
self.assertEqual(8.0, report["routes"][1]["lane"]["offset_mm"])
def test_auto_routing_panel_command_button_style_keeps_text_visible(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, _auto_routing = _reload_modules()
auto_routing_panel = importlib.import_module("AutoRoutingPanel")
class FakeButton:
def __init__(self):
self.text = ""
self.tooltip = ""
self.minimum_height = 0
self.stylesheet = ""
def setText(self, text):
self.text = text
def setToolTip(self, tooltip):
self.tooltip = tooltip
def setMinimumHeight(self, height):
self.minimum_height = height
def setStyleSheet(self, stylesheet):
self.stylesheet = stylesheet
button = FakeButton()
auto_routing_panel._style_command_button(button, "生成布线连接", "按导线任务布线")
self.assertEqual("生成布线连接", button.text)
self.assertEqual("按导线任务布线", button.tooltip)
self.assertGreaterEqual(button.minimum_height, 28)
self.assertIn("color", button.stylesheet)
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_from_payload_skips_resolved_tasks_without_route_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")
_terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-{0}".format(index),
"start_terminal_uuid": "terminal-start",
"end_terminal_uuid": "terminal-end",
}
for index in range(3)
],
}
original_route = auto_routing.route_eplan_connection_between_terminals
def fail_if_called(*_args, **_kwargs):
raise AssertionError("batch route must not call per-wire routing without route carriers")
auto_routing.route_eplan_connection_between_terminals = fail_if_called
try:
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
finally:
auto_routing.route_eplan_connection_between_terminals = original_route
self.assertEqual(0, report["routed"])
self.assertEqual(3, report["skipped_missing_route_network"])
self.assertEqual(3, report["route_status_counts"]["MissingRouteNetwork"])
self.assertEqual([], report["errors"])
self.assertEqual([], wiring_objects.iter_routed_wire_objects(doc))
def test_route_eplan_connection_tasks_marks_task_missing_route_network_when_skipped(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))
task = wiring_objects.create_wire_task(
doc,
"project-1",
"wire-missing-network",
"N1",
"terminal-start",
"terminal-end",
"instance-a",
"instance-b",
)
task.RouteStatus = "Routed"
report = auto_routing.route_eplan_connection_tasks(doc)
self.assertEqual(0, report["routed"])
self.assertEqual(1, report["skipped_missing_route_network"])
self.assertEqual("MissingRouteNetwork", task.RouteStatus)
def test_eplan_connection_route_prefers_wire_duct_over_shorter_routing_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(300, 0, 0))
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(300, 0, 20)],
project_uuid="project-1",
kind="RoutingRange",
)
routing_network.create_route_carrier(
doc,
[
app.Vector(0, 0, 20),
app.Vector(0, 1200, 20),
app.Vector(300, 1200, 20),
app.Vector(300, 0, 20),
],
project_uuid="project-1",
kind="WireDuct",
)
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
self.assertIn("WireDuct", result["route_track"]["carrier_kinds"])
self.assertNotIn("RoutingRange", result["route_track"]["carrier_kinds"])
def test_route_eplan_connections_from_payload_skips_tasks_when_carriers_have_no_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, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
broken_carrier = doc.addObject("Part::Feature", "BrokenCarrier")
terminal_objects.ensure_string_property(
broken_carrier,
"QetRoutingRole",
"QET Routing",
"Routing role marker",
"RoutingCarrier",
)
terminal_objects.ensure_string_property(
broken_carrier,
"QetRouteCarrierKind",
"QET Routing",
"Route carrier kind",
"WireDuct",
)
terminal_objects.ensure_bool_property(
broken_carrier,
"CanRouteWire",
"QET Routing",
"Whether routing connections can use this path",
True,
)
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-a",
"start_terminal_uuid": "terminal-start",
"end_terminal_uuid": "terminal-end",
},
],
}
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
self.assertEqual(1, report["route_network_carriers"])
self.assertEqual(0, report["route_network_segments"])
self.assertEqual(0, report["route_network_nodes"])
self.assertEqual(0, report["routed"])
self.assertEqual(1, report["skipped_missing_route_network"])
self.assertEqual(1, report["route_status_counts"]["MissingRouteNetwork"])
self.assertEqual([], report["errors"])
self.assertEqual([], wiring_objects.iter_routed_wire_objects(doc))
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_writes_compact_batch_diagnostic(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, 20, 0))
_terminal(doc, terminal_objects, "TerminalEndB", "terminal-end-b", app.Vector(100, 20, 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, 20, 20), app.Vector(100, 20, 20)],
project_uuid="project-1",
kind="WireDuct",
)
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-a",
"wire_label": "N1",
"start_terminal_uuid": "terminal-start-a",
"end_terminal_uuid": "terminal-end-a",
},
{
"wire_id": "wire-b",
"wire_label": "N2",
"start_terminal_uuid": "terminal-start-b",
"end_terminal_uuid": "terminal-end-b",
},
],
}
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
diagnostic_group = doc.getObject("QETWiring_05_Diagnostics")
diagnostic_payload = json.loads(diagnostic_group.Group[0].QetDiagnosticJson)
self.assertEqual(2, len(report["routes"]))
self.assertNotIn("routes", diagnostic_payload)
self.assertEqual(2, diagnostic_payload["route_sample_count"])
self.assertEqual(2, len(diagnostic_payload["route_samples"]))
self.assertEqual("wire-a", diagnostic_payload["route_samples"][0]["wire_uuid"])
self.assertEqual("Routed", diagnostic_payload["route_samples"][0]["route_status"])
def test_route_eplan_connections_batch_diagnostic_includes_quality_warnings(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="RoutingRange",
)
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-surface",
"wire_label": "N-SURFACE",
"start_terminal_uuid": "terminal-start",
"end_terminal_uuid": "terminal-end",
}
],
}
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
diagnostic_group = doc.getObject("QETWiring_05_Diagnostics")
diagnostic_payload = json.loads(diagnostic_group.Group[0].QetDiagnosticJson)
self.assertEqual(1, report["routed"])
self.assertEqual(1, diagnostic_payload["route_quality_warning_count"])
self.assertEqual(
"wire-surface",
diagnostic_payload["route_quality_warning_samples"][0]["wire_uuid"],
)
self.assertEqual(
["RoutingRange"],
diagnostic_payload["route_quality_warning_samples"][0]["carrier_kinds"],
)
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_eplan_connections_hides_route_carriers_after_routing_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")
_terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
_terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
carrier = 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(
doc,
payload=payload,
update_network=False,
)
self.assertEqual(1, report["routed"])
self.assertEqual(1, report["hidden_route_carriers"])
self.assertFalse(carrier.ViewObject.Visibility)
def test_route_eplan_connections_batch_recomputes_once_after_created_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")
_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, 10, 0))
_terminal(doc, terminal_objects, "TerminalEndB", "terminal-end-b", app.Vector(100, 10, 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, 10, 20), app.Vector(100, 10, 20)],
project_uuid="project-1",
kind="WireDuct",
)
recompute_count = {"value": 0}
def count_recompute():
recompute_count["value"] += 1
doc.recompute = count_recompute
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)
self.assertEqual(2, report["routed"])
self.assertEqual(1, recompute_count["value"])
def test_route_eplan_connections_replaces_existing_routed_wires_for_same_batch(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-repeat",
"start_terminal_uuid": "terminal-start",
"end_terminal_uuid": "terminal-end",
},
],
}
first = auto_routing.route_eplan_connections_from_payload(doc, payload)
second = auto_routing.route_eplan_connections_from_payload(doc, payload)
routed_wires = list(wiring_objects.iter_routed_wire_objects(doc))
self.assertEqual(1, first["routed"])
self.assertEqual(1, second["routed"])
self.assertEqual(1, second["replaced_routed_connections"])
self.assertEqual(1, len(routed_wires))
self.assertEqual("wire-repeat", routed_wires[0].QetWireUuid)
def test_clear_routing_connections_resets_task_status_and_batch_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",
)
task = wiring_objects.create_wire_task(
doc,
"project-1",
"wire-clear",
"N1",
"terminal-start",
"terminal-end",
"instance-a",
"instance-b",
)
report = auto_routing.route_eplan_connection_tasks(doc)
diagnostic_group = doc.getObject("QETWiring_05_Diagnostics")
self.assertEqual(1, report["routed"])
self.assertEqual("Routed", task.RouteStatus)
self.assertEqual(1, len(list(getattr(diagnostic_group, "Group", []) or [])))
removed = auto_routing.clear_routing_connections(doc)
self.assertEqual(1, removed)
self.assertEqual("Task", task.RouteStatus)
self.assertEqual([], wiring_objects.iter_routed_wire_objects(doc))
self.assertEqual([], list(getattr(diagnostic_group, "Group", []) or []))
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_report_includes_replaced_routed_connection_count(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 1,
"collision_warnings": 0,
"skipped_missing_terminal": 0,
"replaced_routed_connections": 2,
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("已替换旧布线连接2 条。", message)
def test_route_report_includes_hidden_route_carrier_count(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 1,
"collision_warnings": 0,
"skipped_missing_terminal": 0,
"hidden_route_carriers": 3,
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("已隐藏走线路径辅助对象3 条。", message)
def test_route_report_warns_when_routes_use_surface_or_auxiliary_paths(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": [
{
"wire_label": "N1",
"route_track": {
"carrier_kinds": {
"TerminalAccess": 2,
"WireDuct": 1,
"RoutingRange": 2,
},
},
},
{
"wire_label": "N2",
"route_track": {
"carrier_kinds": {
"TerminalAccess": 2,
"WireDuct": 3,
},
},
},
],
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("路径质量提示1 条导线使用布线面/辅助路径", message)
self.assertIn("示例 N1 使用布线面。", message)
def test_route_report_warns_when_parallel_lanes_exceed_track_capacity(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"routed": 3,
"collision_warnings": 0,
"skipped_missing_terminal": 0,
"routes": [
{
"lane": {"index": 2, "spacing_mm": 10.0},
"route_track": {
"segments": [
{"carrier": {"kind": "WireDuct", "capacity": 2}},
{"carrier": {"kind": "WireDuct", "capacity": 4}},
]
},
}
],
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("容量提示:最大并行线数 3路径最小容量 2。", message)
def test_route_report_capacity_pressure_is_checked_per_route(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": 2, "spacing_mm": 10.0},
"route_track": {
"segments": [
{"carrier": {"kind": "WireDuct", "capacity": 4}},
]
},
},
{
"lane": {"index": 0, "spacing_mm": 10.0},
"route_track": {
"segments": [
{"carrier": {"kind": "WireDuct", "capacity": 1}},
]
},
},
],
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertNotIn("容量提示", 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_report_includes_routing_path_network_diagnostic(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="RoutingRange",
)
payload = {
"project_uuid": "project-1",
"wires": [
{
"wire_id": "wire-range-only",
"wire_label": "N-RANGE",
"start_terminal_uuid": "terminal-start",
"end_terminal_uuid": "terminal-end",
}
],
}
report = auto_routing.route_eplan_connections(
doc,
payload=payload,
options={"hide_route_carriers_after_route": False},
project_uuid="project-1",
)
message = auto_routing.format_eplan_connection_route_report(report)
self.assertEqual(1, report["routed"])
self.assertFalse(report["routing_path_network_diagnostic"]["ok"])
self.assertIn(
"routing_range_only_network",
report["routing_path_network_diagnostic"]["issue_codes"],
)
self.assertIn("路径网络检查提示", message)
self.assertIn("仅使用布线面兜底", message)
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")
self.assertEqual(2, len(list(getattr(routed_group, "Group", []) or [])))
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_calls_out_missing_route_network(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
report = {
"total_wires": 3,
"routed": 0,
"collision_warnings": 0,
"skipped_missing_terminal": 0,
"skipped_missing_route_network": 3,
"route_status_counts": {
"MissingRouteNetwork": 3,
},
}
message = auto_routing.format_eplan_connection_route_report(report)
self.assertIn("缺少布线路径网络 3 条", message)
self.assertIn("请先生成线槽、布线面或布线路径网络", 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)
self.assertFalse(indexed["qet-terminal-p1"].ViewObject.Visibility)
self.assertFalse(indexed["qet-terminal-p2"].ViewObject.Visibility)
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)
self.assertFalse(indexed["qet-terminal-p1"].ViewObject.Visibility)
self.assertFalse(indexed["qet-terminal-p2"].ViewObject.Visibility)
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()