|
|
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_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_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_route_carrier_styles_make_generated_objects_distinguishable(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
|
|
|
wire_duct = routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 0), app.Vector(100, 0, 0)],
|
|
|
project_uuid="project-1",
|
|
|
kind="WireDuct",
|
|
|
)
|
|
|
routing_range = routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 10, 0), app.Vector(100, 10, 0)],
|
|
|
project_uuid="project-1",
|
|
|
kind="RoutingRange",
|
|
|
)
|
|
|
terminal_access = routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 20, 0), app.Vector(100, 20, 0)],
|
|
|
project_uuid="project-1",
|
|
|
kind="TerminalAccess",
|
|
|
)
|
|
|
|
|
|
self.assertEqual((1.0, 0.55, 0.0), wire_duct.ViewObject.LineColor)
|
|
|
self.assertEqual(4.0, wire_duct.ViewObject.LineWidth)
|
|
|
self.assertEqual((0.0, 0.65, 0.35), routing_range.ViewObject.LineColor)
|
|
|
self.assertEqual("Solid", routing_range.ViewObject.DrawStyle)
|
|
|
self.assertEqual((0.65, 0.2, 1.0), terminal_access.ViewObject.LineColor)
|
|
|
self.assertEqual("Solid", terminal_access.ViewObject.DrawStyle)
|
|
|
|
|
|
def test_route_graph_connects_crossing_carriers_at_intersection(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(50, 50, 0))
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 0), app.Vector(100, 0, 0)],
|
|
|
project_uuid="project-1",
|
|
|
kind="WireDuct",
|
|
|
)
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(50, -50, 0), app.Vector(50, 50, 0)],
|
|
|
project_uuid="project-1",
|
|
|
kind="WireDuct",
|
|
|
)
|
|
|
|
|
|
network = routing_network.build_route_graph(doc)
|
|
|
result = auto_routing.route_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_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_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_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_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_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"])
|
|
|
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("补一段线槽/辅助路径", message)
|
|
|
|
|
|
def test_format_routing_path_network_report_tolerates_malformed_samples(self):
|
|
|
_install_fake_freecad()
|
|
|
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
|
|
|
diagnostic = {
|
|
|
"issues": [{"code": "external_issue", "count": 1}],
|
|
|
"unconnected_terminals": ["bad-terminal-sample"],
|
|
|
"possible_breaks": ["bad-break-sample"],
|
|
|
"isolated_components": ["bad-component-sample"],
|
|
|
}
|
|
|
|
|
|
message = auto_routing.format_routing_path_network_report(diagnostic)
|
|
|
|
|
|
self.assertIn("布线路径网络检查发现", message)
|
|
|
self.assertIn("首个问题:external_issue", message)
|
|
|
|
|
|
def test_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_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_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_auto_detect_wire_ducts_ignores_cabinet_models(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules()
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
duct = doc.addObject("Part::Feature", "WireDuctA")
|
|
|
duct.Label = "线槽A"
|
|
|
duct.Shape = FakeShape(FakeBoundBox(0, 120, -10, 10, 5, 25))
|
|
|
cabinet = doc.addObject("Part::Feature", "Cabinet")
|
|
|
cabinet.Label = "3D机柜"
|
|
|
cabinet.Shape = FakeShape(FakeBoundBox(0, 300, 0, 80, 0, 400))
|
|
|
|
|
|
created = routing_network.create_wire_duct_carriers_from_document(
|
|
|
doc,
|
|
|
project_uuid="project-1",
|
|
|
)
|
|
|
created_again = routing_network.create_wire_duct_carriers_from_document(
|
|
|
doc,
|
|
|
project_uuid="project-1",
|
|
|
)
|
|
|
|
|
|
self.assertEqual(3, len(created))
|
|
|
self.assertEqual(0, len(created_again))
|
|
|
self.assertEqual(1, len([item for item in created if item.QetRouteCarrierKind == "WireDuct"]))
|
|
|
self.assertEqual(2, len([item for item in created if item.QetRouteCarrierKind == "WireDuctOpenEnd"]))
|
|
|
self.assertEqual("PassThrough", duct.QetRoutingObstacleMode)
|
|
|
self.assertFalse(hasattr(cabinet, "QetRoutingObstacleMode"))
|
|
|
|
|
|
def test_wire_duct_source_is_not_reported_as_collision(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(120, 0, 0))
|
|
|
duct = doc.addObject("Part::Feature", "WireDuct")
|
|
|
duct.Shape = FakeShape(FakeBoundBox(-10, 130, -10, 10, 15, 25))
|
|
|
routing_network.create_wire_duct_carriers_from_selection(
|
|
|
doc,
|
|
|
[FakeSelectionItem(obj=duct)],
|
|
|
project_uuid="project-1",
|
|
|
margin=0.0,
|
|
|
)
|
|
|
|
|
|
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
|
|
|
|
|
|
self.assertEqual("network-dijkstra-v1", result["algorithm"])
|
|
|
self.assertEqual("Routed", result["route_status"])
|
|
|
self.assertEqual(0, result["collision_count"])
|
|
|
|
|
|
def test_eplan_connection_route_uses_alternate_carrier_to_avoid_obstacle(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
|
|
|
project_uuid="project-1",
|
|
|
kind="WireDuct",
|
|
|
)
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[
|
|
|
app.Vector(0, 0, 20),
|
|
|
app.Vector(0, 50, 20),
|
|
|
app.Vector(100, 50, 20),
|
|
|
app.Vector(100, 0, 20),
|
|
|
],
|
|
|
project_uuid="project-1",
|
|
|
kind="WireDuct",
|
|
|
)
|
|
|
obstacle = doc.addObject("Part::Feature", "CabinetObstacle")
|
|
|
obstacle.Shape = FakeShape(FakeBoundBox(40, 60, -10, 10, 15, 25))
|
|
|
|
|
|
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
|
|
|
|
|
|
self.assertEqual("network-dijkstra-v1", result["algorithm"])
|
|
|
self.assertEqual("Routed", result["route_status"])
|
|
|
self.assertEqual(0, result["collision_count"])
|
|
|
self.assertTrue(result["network"]["obstacle_aware"])
|
|
|
self.assertGreaterEqual(result["network"]["blocked_segments"], 1)
|
|
|
self.assertIn(50.0, [point.y for point in result["points"]])
|
|
|
|
|
|
def test_eplan_connection_route_marks_collision_warning_against_obstacle_bbox(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 100), app.Vector(100, 0, 100)],
|
|
|
project_uuid="project-1",
|
|
|
)
|
|
|
obstacle = doc.addObject("Part::Feature", "Obstacle")
|
|
|
obstacle.Shape = FakeShape(FakeBoundBox(40, 60, -10, 10, 90, 110))
|
|
|
|
|
|
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
|
|
|
|
|
|
self.assertEqual("CollisionWarning", result["route_status"])
|
|
|
self.assertEqual("CollisionWarning", result["wire"].RouteStatus)
|
|
|
self.assertEqual(1, result["collision_count"])
|
|
|
self.assertEqual("HardIntersection", result["collisions"][0]["collision_kind"])
|
|
|
|
|
|
def test_eplan_connection_route_marks_clearance_warning_against_expanded_obstacle_bbox(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 100), app.Vector(100, 0, 100)],
|
|
|
project_uuid="project-1",
|
|
|
)
|
|
|
obstacle = doc.addObject("Part::Feature", "NearObstacle")
|
|
|
obstacle.Shape = FakeShape(FakeBoundBox(40, 60, 3, 6, 90, 110))
|
|
|
|
|
|
result = auto_routing.route_eplan_connection_between_terminals(
|
|
|
doc,
|
|
|
start,
|
|
|
end,
|
|
|
options={"obstacle_clearance": 5.0},
|
|
|
)
|
|
|
|
|
|
self.assertEqual("CollisionWarning", result["route_status"])
|
|
|
self.assertEqual(1, result["collision_count"])
|
|
|
self.assertEqual("ClearanceWarning", result["collisions"][0]["collision_kind"])
|
|
|
self.assertEqual(3.0, result["collisions"][0]["obstacle_bbox"]["ymin"])
|
|
|
self.assertEqual(-2.0, result["collisions"][0]["collision_bbox"]["ymin"])
|
|
|
|
|
|
def test_eplan_connection_route_ignores_terminal_exit_segment_collision(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
|
|
|
project_uuid="project-1",
|
|
|
)
|
|
|
terminal_body = doc.addObject("Part::Feature", "UngroupedTerminalBody")
|
|
|
terminal_body.Shape = FakeShape(FakeBoundBox(-5, 5, -5, 5, -5, 15))
|
|
|
|
|
|
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
|
|
|
|
|
|
self.assertEqual("Routed", result["route_status"])
|
|
|
self.assertEqual(0, result["collision_count"])
|
|
|
|
|
|
def test_eplan_connection_route_ignores_endpoint_device_body_as_obstacle(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
|
|
|
device = doc.addObject("App::DocumentObjectGroup", "QETDeviceStart")
|
|
|
device.QetInstanceId = start.QetInstanceId
|
|
|
device.addObject(start)
|
|
|
body = doc.addObject("Part::Feature", "StartDeviceBody")
|
|
|
body.Shape = FakeShape(FakeBoundBox(-5, 5, -5, 5, -5, 15))
|
|
|
device.addObject(body)
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
|
|
|
project_uuid="project-1",
|
|
|
)
|
|
|
|
|
|
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
|
|
|
|
|
|
self.assertEqual("Routed", result["route_status"])
|
|
|
self.assertEqual(0, result["collision_count"])
|
|
|
|
|
|
def test_route_eplan_connections_from_payload_skips_missing_terminal(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
_terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
payload = {
|
|
|
"wires": [
|
|
|
{
|
|
|
"wire_id": "wire-1",
|
|
|
"start_terminal_uuid": "terminal-start",
|
|
|
"end_terminal_uuid": "terminal-missing",
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
|
|
|
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
|
|
|
|
|
|
self.assertEqual(0, report["routed"])
|
|
|
self.assertEqual(1, report["skipped_missing_terminal"])
|
|
|
self.assertEqual(1, report["available_terminals"])
|
|
|
self.assertEqual(0, report["local_terminals"])
|
|
|
self.assertEqual(["terminal-missing"], report["missing_endpoint_uuids"])
|
|
|
self.assertEqual("terminal-start", report["missing_endpoint_samples"][0]["start_terminal_uuid"])
|
|
|
self.assertTrue(report["missing_endpoint_samples"][0]["start_found"])
|
|
|
self.assertFalse(report["missing_endpoint_samples"][0]["end_found"])
|
|
|
|
|
|
def test_route_eplan_connections_writes_diagnostic_object_for_missing_terminal(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
_terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
payload = {
|
|
|
"project_uuid": "project-1",
|
|
|
"wires": [
|
|
|
{
|
|
|
"wire_id": "wire-1",
|
|
|
"start_terminal_uuid": "terminal-start",
|
|
|
"end_terminal_uuid": "terminal-missing",
|
|
|
}
|
|
|
],
|
|
|
}
|
|
|
|
|
|
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
|
|
|
diagnostic_group = doc.getObject("QETWiring_05_Diagnostics")
|
|
|
|
|
|
self.assertEqual(1, report["skipped_missing_terminal"])
|
|
|
self.assertIsNotNone(diagnostic_group)
|
|
|
self.assertEqual(1, len(diagnostic_group.Group))
|
|
|
diagnostic = diagnostic_group.Group[0]
|
|
|
self.assertEqual("RoutingConnectionBatch", diagnostic.QetDiagnosticKind)
|
|
|
self.assertIn("terminal-missing", diagnostic.QetDiagnosticJson)
|
|
|
|
|
|
def test_route_eplan_connections_reports_total_connection_route_length(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
_terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
_terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
|
|
|
project_uuid="project-1",
|
|
|
kind="WireDuct",
|
|
|
)
|
|
|
payload = {
|
|
|
"project_uuid": "project-1",
|
|
|
"wires": [
|
|
|
{
|
|
|
"wire_id": "wire-1",
|
|
|
"start_terminal_uuid": "terminal-start",
|
|
|
"end_terminal_uuid": "terminal-end",
|
|
|
}
|
|
|
],
|
|
|
}
|
|
|
|
|
|
report = auto_routing.route_eplan_connections_from_payload(doc, payload)
|
|
|
message = auto_routing.format_eplan_connection_route_report(report)
|
|
|
|
|
|
self.assertGreater(report["total_length_mm"], 0.0)
|
|
|
self.assertEqual(report["total_length_mm"], report["routes"][0]["length_mm"])
|
|
|
self.assertIn("总长度", message)
|
|
|
|
|
|
def test_route_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_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_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)
|
|
|
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_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_clearance_collision_kind(self):
|
|
|
_install_fake_freecad()
|
|
|
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
|
|
|
report = {
|
|
|
"routed": 1,
|
|
|
"collision_warnings": 1,
|
|
|
"skipped_missing_terminal": 0,
|
|
|
"collision_samples": [
|
|
|
{
|
|
|
"wire_label": "N4111",
|
|
|
"obstacle_label": "柜体侧板",
|
|
|
"collision_kind": "ClearanceWarning",
|
|
|
}
|
|
|
],
|
|
|
}
|
|
|
|
|
|
message = auto_routing.format_eplan_connection_route_report(report)
|
|
|
|
|
|
self.assertIn("安全间隙", message)
|
|
|
self.assertIn("柜体侧板", message)
|
|
|
|
|
|
def test_route_eplan_connections_report_ignores_non_numeric_status_counts(self):
|
|
|
_install_fake_freecad()
|
|
|
_terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()
|
|
|
report = {
|
|
|
"routed": 1,
|
|
|
"collision_warnings": 0,
|
|
|
"skipped_missing_terminal": 0,
|
|
|
"route_status_counts": {
|
|
|
"Routed": "1",
|
|
|
"ExternalStatus": "not-a-number",
|
|
|
},
|
|
|
}
|
|
|
|
|
|
message = auto_routing.format_eplan_connection_route_report(report)
|
|
|
|
|
|
self.assertIn("正常 1 条", message)
|
|
|
self.assertNotIn("ExternalStatus", message)
|
|
|
|
|
|
def test_bind_wire_task_terminals_from_payload_does_not_create_wires(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, wiring_objects, _routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
root = terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
device = doc.addObject("App::DocumentObjectGroup", "QETDevice_device_a")
|
|
|
root.addObject(device)
|
|
|
terminal_objects.ensure_string_property(device, "QetElementUuid", "QET Exchange", "", "device-a")
|
|
|
terminal_objects.ensure_string_property(device, "QetInstanceId", "QET Exchange", "", "instance-a")
|
|
|
terminal_objects.ensure_string_property(device, "QetProjectUuid", "QET Exchange", "", "project-1")
|
|
|
terminal_group = terminal_objects.ensure_terminal_group(
|
|
|
doc,
|
|
|
device,
|
|
|
project_uuid="project-1",
|
|
|
instance_id="instance-a",
|
|
|
)
|
|
|
|
|
|
for slot_name, point in (
|
|
|
("P1", app.Vector(0, 0, 0)),
|
|
|
("P2", app.Vector(100, 0, 0)),
|
|
|
):
|
|
|
terminal = terminal_objects.create_lcs_object(
|
|
|
doc,
|
|
|
"QETTerminal_instance_a_{0}".format(slot_name),
|
|
|
placement=app.Placement(point, app.Rotation()),
|
|
|
label=slot_name,
|
|
|
)
|
|
|
terminal_group.addObject(terminal)
|
|
|
terminal_objects.set_terminal_semantics(
|
|
|
terminal,
|
|
|
"project-1",
|
|
|
"device-a",
|
|
|
"local:instance-a:{0}".format(slot_name),
|
|
|
"instance-a",
|
|
|
label=slot_name,
|
|
|
slot_name=slot_name,
|
|
|
)
|
|
|
|
|
|
payload = {
|
|
|
"project_uuid": "project-1",
|
|
|
"wires": [
|
|
|
{
|
|
|
"wire_id": "wire-1",
|
|
|
"start_element_uuid": "device-a",
|
|
|
"start_instance_id": "instance-a",
|
|
|
"start_terminal_uuid": "qet-terminal-p1",
|
|
|
"start_terminal_display": "P1",
|
|
|
"end_element_uuid": "device-a",
|
|
|
"end_instance_id": "instance-a",
|
|
|
"end_terminal_uuid": "qet-terminal-p2",
|
|
|
"end_terminal_display": "P2",
|
|
|
}
|
|
|
],
|
|
|
}
|
|
|
|
|
|
report = auto_routing.bind_wire_task_terminals_from_payload(doc, payload)
|
|
|
indexed = auto_routing.index_terminals(doc)
|
|
|
|
|
|
self.assertEqual(2, report["bound"])
|
|
|
self.assertEqual(0, report["created"])
|
|
|
self.assertEqual(0, report["local_terminals"])
|
|
|
self.assertEqual([], wiring_objects.iter_routed_wire_objects(doc))
|
|
|
self.assertEqual("qet", indexed["qet-terminal-p1"].QetTerminalBindingMode)
|
|
|
|
|
|
def test_route_eplan_connections_rebinds_local_template_terminals_from_wire_endpoints(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
root = terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
device = doc.addObject("App::DocumentObjectGroup", "QETDevice_device_a")
|
|
|
root.addObject(device)
|
|
|
terminal_objects.ensure_string_property(device, "QetElementUuid", "QET Exchange", "", "device-a")
|
|
|
terminal_objects.ensure_string_property(device, "QetInstanceId", "QET Exchange", "", "instance-a")
|
|
|
terminal_objects.ensure_string_property(device, "QetProjectUuid", "QET Exchange", "", "project-1")
|
|
|
terminal_group = terminal_objects.ensure_terminal_group(
|
|
|
doc,
|
|
|
device,
|
|
|
project_uuid="project-1",
|
|
|
instance_id="instance-a",
|
|
|
)
|
|
|
|
|
|
for slot_name, point in (
|
|
|
("P1", app.Vector(0, 0, 0)),
|
|
|
("P2", app.Vector(100, 0, 0)),
|
|
|
):
|
|
|
terminal = terminal_objects.create_lcs_object(
|
|
|
doc,
|
|
|
"QETTerminal_instance_a_{0}".format(slot_name),
|
|
|
placement=app.Placement(point, app.Rotation()),
|
|
|
label=slot_name,
|
|
|
)
|
|
|
terminal_group.addObject(terminal)
|
|
|
terminal_objects.set_terminal_semantics(
|
|
|
terminal,
|
|
|
"project-1",
|
|
|
"device-a",
|
|
|
"local:instance-a:{0}".format(slot_name),
|
|
|
"instance-a",
|
|
|
label=slot_name,
|
|
|
slot_name=slot_name,
|
|
|
)
|
|
|
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
|
|
|
project_uuid="project-1",
|
|
|
)
|
|
|
|
|
|
payload = {
|
|
|
"project_uuid": "project-1",
|
|
|
"wires": [
|
|
|
{
|
|
|
"wire_id": "wire-1",
|
|
|
"start_element_uuid": "device-a",
|
|
|
"start_instance_id": "instance-a",
|
|
|
"start_terminal_uuid": "qet-terminal-p1",
|
|
|
"start_terminal_display": "P1",
|
|
|
"end_element_uuid": "device-a",
|
|
|
"end_instance_id": "instance-a",
|
|
|
"end_terminal_uuid": "qet-terminal-p2",
|
|
|
"end_terminal_display": "P2",
|
|
|
}
|
|
|
],
|
|
|
}
|
|
|
|
|
|
report = auto_routing.route_eplan_connections_from_payload(
|
|
|
doc,
|
|
|
payload,
|
|
|
)
|
|
|
indexed = auto_routing.index_terminals(doc)
|
|
|
|
|
|
self.assertEqual(1, report["routed"])
|
|
|
self.assertEqual(2, report["auto_bound_terminals"])
|
|
|
self.assertEqual(0, report["local_terminals"])
|
|
|
self.assertIn("qet-terminal-p1", indexed)
|
|
|
self.assertIn("qet-terminal-p2", indexed)
|
|
|
self.assertEqual("qet", indexed["qet-terminal-p1"].QetTerminalBindingMode)
|
|
|
|
|
|
def test_clear_route_carriers_keeps_routed_wires(self):
|
|
|
_install_fake_freecad()
|
|
|
terminal_objects, wiring_objects, routing_network, auto_routing = _reload_modules()
|
|
|
app = sys.modules["FreeCAD"]
|
|
|
doc = FakeDocument()
|
|
|
terminal_objects.ensure_root_group(doc, "project-1")
|
|
|
start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0))
|
|
|
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0))
|
|
|
routing_network.create_route_carrier(
|
|
|
doc,
|
|
|
[app.Vector(0, 0, 20), app.Vector(100, 0, 20)],
|
|
|
project_uuid="project-1",
|
|
|
)
|
|
|
wire = auto_routing.route_eplan_connection_between_terminals(doc, start, end)["wire"]
|
|
|
|
|
|
removed = routing_network.clear_route_carriers(doc)
|
|
|
|
|
|
self.assertEqual(1, removed)
|
|
|
self.assertEqual([], routing_network.collect_route_carriers(doc))
|
|
|
self.assertIn(wire, wiring_objects.ensure_routed_group(doc, "project-1").Group)
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
unittest.main()
|