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.
370 lines
13 KiB
Python
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()
|