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): self.Axis = axis self.Angle = angle class Placement: def __init__(self, base=None, rotation=None): self.Base = base or Vector() self.Rotation = 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.Console = types.SimpleNamespace( PrintMessage=lambda *args, **kwargs: None, PrintWarning=lambda *args, **kwargs: None, PrintError=lambda *args, **kwargs: None, ) fake_freecad.ActiveDocument = None sys.modules["FreeCAD"] = fake_freecad fake_freecadgui = types.ModuleType("FreeCADGui") fake_freecadgui.SendMsgToActiveView = lambda *args, **kwargs: None fake_freecadgui.addCommand = lambda *args, **kwargs: None fake_freecadgui.Selection = types.SimpleNamespace(getSelection=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 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.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", "DeviceImport", "ManualWiring", ]: sys.modules.pop(name, None) import DeviceImport import ManualWiring import TerminalObjects return DeviceImport, ManualWiring, TerminalObjects class ManualWiringGroupTest(unittest.TestCase): def test_manual_wire_uses_terminal_global_points_after_device_move(self): _install_fake_freecad() _device_import, manual_wiring, terminal_objects = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() root = terminal_objects.ensure_root_group(doc, "project-1") start_device = doc.addObject("App::DocumentObjectGroup", "QETDevice_start") start_device.Placement = app.Placement(app.Vector(100, 0, 0), app.Rotation()) root.addObject(start_device) end_device = doc.addObject("App::DocumentObjectGroup", "QETDevice_end") end_device.Placement = app.Placement(app.Vector(300, 0, 0), app.Rotation()) root.addObject(end_device) for device, element_uuid, instance_id in [ (start_device, "device-start", "instance-start"), (end_device, "device-end", "instance-end"), ]: terminal_objects.ensure_string_property( device, "QetElementUuid", "QET Exchange", "Element UUID", element_uuid, ) terminal_objects.ensure_string_property( device, "QetInstanceId", "QET Exchange", "Instance ID", instance_id, ) start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart") start_terminal.Placement = app.Placement(app.Vector(10, 0, 0), app.Rotation()) 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(20, 0, 0), app.Rotation()) 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) self.assertEqual(110.0, wire.Shape[0].x) self.assertEqual(320.0, wire.Shape[-1].x) def test_manual_wire_is_added_to_device_wire_group(self): _install_fake_freecad() device_import, manual_wiring, terminal_objects = _reload_modules() doc = FakeDocument() root = terminal_objects.ensure_root_group(doc, "project-1") device_group = doc.addObject("App::DocumentObjectGroup", "QETDevice_device_a") root.addObject(device_group) terminal_objects.ensure_string_property( device_group, "QetElementUuid", "QET Exchange", "Element UUID", "device-a", ) terminal_objects.ensure_string_property( device_group, "QetInstanceId", "QET Exchange", "Instance ID", "instance-a", ) terminal_objects.ensure_string_property( device_group, "QetProjectUuid", "QET Exchange", "Project UUID", "project-1", ) start_terminal = FakeObject("TerminalStart", "Part::LocalCoordinateSystem") terminal_objects.set_terminal_semantics( start_terminal, "project-1", "device-a", "terminal-start", "instance-a", label="Start", ) end_terminal = FakeObject("TerminalEnd", "Part::LocalCoordinateSystem") terminal_objects.set_terminal_semantics( end_terminal, "project-1", "device-a", "terminal-end", "instance-a", label="End", ) wire = manual_wiring.create_manual_wire(doc, start_terminal, end_terminal) wire_group = terminal_objects.find_child_group_by_kind( device_group, terminal_objects.WIRE_GROUP_KIND, ) self.assertIsNotNone(wire_group) self.assertIn(wire, wire_group.Group) self.assertNotIn(wire, root.Group) if __name__ == "__main__": unittest.main()