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

1123 lines
45 KiB
Python

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