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

370 lines
13 KiB
Python

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 __init__(self, axis=None, angle=None, w_axis=None):
self.Axis = axis
self.Angle = angle
self.WAxis = w_axis
def multVec(self, vector):
if self.WAxis is not None and vector.z == 1:
return self.WAxis
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.GuiUp = True
fake_freecad.Console = types.SimpleNamespace(
PrintMessage=lambda *args, **kwargs: None,
PrintWarning=lambda *args, **kwargs: None,
PrintError=lambda *args, **kwargs: None,
PrintLog=lambda *args, **kwargs: None,
)
sys.modules["FreeCAD"] = fake_freecad
fake_freecadgui = types.ModuleType("FreeCADGui")
fake_freecadgui.addCommand = lambda *args, **kwargs: None
fake_freecadgui.SendMsgToActiveView = lambda *args, **kwargs: None
fake_freecadgui.Selection = types.SimpleNamespace(
getSelection=lambda: [],
getSelectionEx=lambda: [],
)
sys.modules["FreeCADGui"] = fake_freecadgui
fake_importgui = types.ModuleType("ImportGui")
fake_importgui.insert = lambda *args, **kwargs: None
sys.modules["ImportGui"] = fake_importgui
fake_part = types.ModuleType("Part")
fake_part.makePolygon = lambda points: tuple(points)
sys.modules["Part"] = fake_part
class FakeDraftWire:
def __init__(self, obj):
obj.addProperty("App::PropertyVectorList", "Points", "Draft", "Wire points")
fake_draft = types.ModuleType("Draft")
def make_wire(points, closed=False, placement=None, face=None, support=None, bs2wire=False):
doc = fake_freecad.ActiveDocument
obj = doc.addObject("Part::FeaturePython", "Wire")
obj.Points = list(points)
obj.Closed = bool(closed)
obj.AttachmentSupport = support
obj.Placement = placement or fake_freecad.Placement()
FakeDraftWire(obj)
return obj
fake_draft.make_wire = make_wire
sys.modules["Draft"] = fake_draft
class FakeViewObject:
def __init__(self):
self.Visibility = True
self.LineWidth = None
self.LineColor = None
class FakeObject:
def __init__(self, name, type_id):
self.Name = name
self.Label = name
self.TypeId = type_id
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)
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
def _reload_modules():
for name in ["TerminalObjects", "WiringObjects", "ManualWiring", "ExchangeWriteBack"]:
sys.modules.pop(name, None)
import TerminalObjects
import WiringObjects
import ManualWiring
import ExchangeWriteBack
return TerminalObjects, WiringObjects, ManualWiring, ExchangeWriteBack
class WiringTest(unittest.TestCase):
def test_ensure_wiring_root_group_creates_scene_buckets(self):
_install_fake_freecad()
terminal_objects, wiring_objects, _manual_wiring, _write_back = _reload_modules()
doc = FakeDocument()
root = terminal_objects.ensure_root_group(doc, "project-1")
wiring_root = wiring_objects.ensure_wiring_root_group(doc, "project-1")
self.assertIn(wiring_root, root.Group)
self.assertEqual("QETWiring", wiring_root.Name)
self.assertEqual("QET Wiring", wiring_root.Label)
self.assertIsNotNone(doc.getObject("QETWiring_01_Tasks"))
self.assertIsNotNone(doc.getObject("QETWiring_04_Routed"))
def test_initialize_wiring_scene_creates_root_and_hides_legacy_wire_groups(self):
_install_fake_freecad()
terminal_objects, wiring_objects, _manual_wiring, _write_back = _reload_modules()
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")
legacy_group = terminal_objects.ensure_wire_group(
doc,
device,
project_uuid="project-1",
instance_id="instance-a",
)
legacy_group.ViewObject.Visibility = True
wiring_root = wiring_objects.initialize_wiring_scene(doc, "project-1")
self.assertEqual("QETWiring", wiring_root.Name)
self.assertIsNotNone(doc.getObject("QETWiring_04_Routed"))
self.assertFalse(legacy_group.ViewObject.Visibility)
def test_create_manual_wire_preserves_manual_waypoints_as_orthogonal_segments(self):
_install_fake_freecad()
terminal_objects, wiring_objects, manual_wiring, _write_back = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
app.ActiveDocument = doc
root = terminal_objects.ensure_root_group(doc, "project-1")
wiring_objects.ensure_wiring_root_group(doc, "project-1")
start_device = doc.addObject("App::DocumentObjectGroup", "QETDevice_start")
root.addObject(start_device)
terminal_objects.ensure_string_property(
start_device,
"QetElementUuid",
"QET Exchange",
"",
"device-start",
)
terminal_objects.ensure_string_property(
start_device,
"QetInstanceId",
"QET Exchange",
"",
"instance-start",
)
terminal_objects.ensure_string_property(
start_device,
"QetProjectUuid",
"QET Exchange",
"",
"project-1",
)
end_device = doc.addObject("App::DocumentObjectGroup", "QETDevice_end")
root.addObject(end_device)
terminal_objects.ensure_string_property(
end_device,
"QetElementUuid",
"QET Exchange",
"",
"device-end",
)
terminal_objects.ensure_string_property(
end_device,
"QetInstanceId",
"QET Exchange",
"",
"instance-end",
)
terminal_objects.ensure_string_property(
end_device,
"QetProjectUuid",
"QET Exchange",
"",
"project-1",
)
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
start_terminal.Placement = app.Placement(
app.Vector(1, 2, 3),
app.Rotation(w_axis=app.Vector(0, 1, 0)),
)
start_device.addObject(start_terminal)
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(
app.Vector(9, 8, 7),
app.Rotation(w_axis=app.Vector(0, 0, 1)),
)
end_device.addObject(end_terminal)
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
wire = manual_wiring.create_manual_wire(
doc,
start_terminal,
end_terminal,
waypoints=[
{
"point": app.Vector(4, 5, 6),
"support_axis": "x",
"anchor_kind": "face",
"source_label": "柜体面",
}
],
terminal_exit_length=20.0,
)
routed_group = doc.getObject("QETWiring_04_Routed")
self.assertIsNotNone(routed_group)
self.assertIn(wire, routed_group.Group)
self.assertEqual("Manual", getattr(wire, "RouteType", ""))
self.assertEqual("terminal-start", getattr(wire, "QetStartTerminalUuid", ""))
self.assertEqual("terminal-end", getattr(wire, "QetEndTerminalUuid", ""))
self.assertEqual(
[
(1.0, 2.0, 3.0),
(1.0, 22.0, 3.0),
(1.0, 5.0, 3.0),
(1.0, 5.0, 6.0),
(4.0, 5.0, 6.0),
(4.0, 5.0, 27.0),
(9.0, 5.0, 27.0),
(9.0, 8.0, 27.0),
(9.0, 8.0, 7.0),
],
[(point.x, point.y, point.z) for point in getattr(wire, "Points", [])],
)
self.assertTrue(any(point.x == 4.0 and point.y == 5.0 and point.z == 6.0 for point in wire.Points))
self.assertIn("QetManualWaypointsJson", getattr(wire, "PropertiesList", []))
self.assertIn('"support_axis": "x"', getattr(wire, "QetManualWaypointsJson", ""))
payload = wiring_objects.wire_payload_from_object(wire)
self.assertEqual(20.0, payload["terminal_exit_length"])
self.assertEqual("Manual", payload["route_mode"])
self.assertEqual(
["start_terminal", "start_exit", "waypoint", "end_exit", "end_terminal"],
[node["role"] for node in payload["route_nodes"]],
)
self.assertEqual("face", payload["route_nodes"][2]["anchor_kind"])
def test_wire_writeback_omits_scene_routed_wire_payload(self):
_install_fake_freecad()
terminal_objects, wiring_objects, manual_wiring, write_back = _reload_modules()
doc = FakeDocument()
root = terminal_objects.ensure_root_group(doc, "project-1")
wiring_objects.ensure_wiring_root_group(doc, "project-1")
routed_group = doc.getObject("QETWiring_04_Routed")
wire = doc.addObject("Part::Feature", "QETWire_terminal_start_terminal_end")
wire.Shape = [
sys.modules["FreeCAD"].Vector(1, 2, 3),
sys.modules["FreeCAD"].Vector(4, 5, 6),
]
terminal_objects.ensure_string_property(wire, "QetProjectUuid", "QET Exchange", "", "project-1")
terminal_objects.ensure_string_property(wire, "QetStartTerminalUuid", "QET Exchange", "", "terminal-start")
terminal_objects.ensure_string_property(wire, "QetEndTerminalUuid", "QET Exchange", "", "terminal-end")
terminal_objects.ensure_string_property(wire, "QetStartInstanceId", "QET Exchange", "", "instance-start")
terminal_objects.ensure_string_property(wire, "QetEndInstanceId", "QET Exchange", "", "instance-end")
terminal_objects.ensure_string_property(wire, "RouteType", "QET Exchange", "", "Manual")
routed_group.addObject(wire)
report = write_back.write_back_document(doc, scene_path=r"D:\tmp\scene.FCStd", payload={"project_uuid": "project-1"})
self.assertNotIn("manual_wires", report)
if __name__ == "__main__":
unittest.main()