feat:自动布线功能
parent
b64e751c29
commit
7c337c7522
@ -0,0 +1,96 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import FreeCAD as App
|
||||||
|
|
||||||
|
|
||||||
|
REPO_ROOT = r"D:\LightWork3D"
|
||||||
|
MODULE_DIR = os.path.join(REPO_ROOT, "src", "Mod", "FreeCADExchange")
|
||||||
|
OUT_DIR = os.path.join(REPO_ROOT, "tests", "out")
|
||||||
|
OUT_FCSTD = os.path.join(OUT_DIR, "auto_routing_smoke.FCStd")
|
||||||
|
OUT_JSON = os.path.join(OUT_DIR, "auto_routing_smoke_result.json")
|
||||||
|
|
||||||
|
if MODULE_DIR not in sys.path:
|
||||||
|
sys.path.insert(0, MODULE_DIR)
|
||||||
|
|
||||||
|
import AutoRouting
|
||||||
|
import RoutingNetwork
|
||||||
|
import TerminalObjects
|
||||||
|
import WiringObjects
|
||||||
|
|
||||||
|
|
||||||
|
def _make_terminal(doc, name, terminal_uuid, point):
|
||||||
|
terminal = TerminalObjects.create_lcs_object(
|
||||||
|
doc,
|
||||||
|
name,
|
||||||
|
placement=App.Placement(point, App.Rotation()),
|
||||||
|
label=terminal_uuid,
|
||||||
|
)
|
||||||
|
TerminalObjects.set_terminal_semantics(
|
||||||
|
terminal,
|
||||||
|
"project-smoke",
|
||||||
|
"element-" + terminal_uuid,
|
||||||
|
terminal_uuid,
|
||||||
|
"instance-" + terminal_uuid,
|
||||||
|
label=terminal_uuid,
|
||||||
|
)
|
||||||
|
return terminal
|
||||||
|
|
||||||
|
|
||||||
|
def _point_payload(point):
|
||||||
|
return {
|
||||||
|
"x": float(point.x),
|
||||||
|
"y": float(point.y),
|
||||||
|
"z": float(point.z),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
os.makedirs(OUT_DIR, exist_ok=True)
|
||||||
|
doc = App.newDocument("AutoRoutingSmoke")
|
||||||
|
App.setActiveDocument(doc.Name)
|
||||||
|
TerminalObjects.ensure_root_group(doc, "project-smoke")
|
||||||
|
WiringObjects.initialize_wiring_scene(doc, "project-smoke")
|
||||||
|
|
||||||
|
start = _make_terminal(doc, "SmokeTerminalStart", "terminal-start", App.Vector(0, 0, 0))
|
||||||
|
end = _make_terminal(doc, "SmokeTerminalEnd", "terminal-end", App.Vector(160, 0, 0))
|
||||||
|
|
||||||
|
RoutingNetwork.create_route_carrier(
|
||||||
|
doc,
|
||||||
|
[
|
||||||
|
App.Vector(0, 0, 20),
|
||||||
|
App.Vector(0, 60, 20),
|
||||||
|
App.Vector(160, 60, 20),
|
||||||
|
App.Vector(160, 0, 20),
|
||||||
|
],
|
||||||
|
label="Smoke Route Carrier",
|
||||||
|
project_uuid="project-smoke",
|
||||||
|
)
|
||||||
|
|
||||||
|
obstacle = doc.addObject("Part::Box", "SmokeObstacle")
|
||||||
|
obstacle.Label = "Smoke Obstacle"
|
||||||
|
obstacle.Length = 40
|
||||||
|
obstacle.Width = 40
|
||||||
|
obstacle.Height = 60
|
||||||
|
obstacle.Placement = App.Placement(App.Vector(60, -20, -10), App.Rotation())
|
||||||
|
doc.recompute()
|
||||||
|
|
||||||
|
result = AutoRouting.route_between_terminals(doc, start, end)
|
||||||
|
payload = {
|
||||||
|
"algorithm": result["algorithm"],
|
||||||
|
"route_status": result["route_status"],
|
||||||
|
"collision_count": result["collision_count"],
|
||||||
|
"points": [_point_payload(point) for point in result["points"]],
|
||||||
|
"network": RoutingNetwork.network_summary(doc),
|
||||||
|
"scene": OUT_FCSTD,
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.saveAs(OUT_FCSTD)
|
||||||
|
with open(OUT_JSON, "w", encoding="utf-8") as handle:
|
||||||
|
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
||||||
|
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@ -0,0 +1,460 @@
|
|||||||
|
import importlib
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
self.BoundBox = bbox
|
||||||
|
self._normal = normal
|
||||||
|
|
||||||
|
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",
|
||||||
|
"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_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_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_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_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_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"])
|
||||||
|
|
||||||
|
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()
|
||||||
Loading…
Reference in New Issue