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 __init__(self, transform=None): self._transform = transform def multVec(self, vector): if self._transform is None: return Vector(vector.x, vector.y, vector.z) return self._transform(vector) class Placement: def __init__(self, base=None, rotation=None): self.Base = base or Vector() self.Rotation = rotation or Rotation() fake_freecad = types.ModuleType("FreeCAD") fake_freecad.Vector = Vector fake_freecad.Rotation = Rotation fake_freecad.Placement = Placement sys.modules["FreeCAD"] = fake_freecad class FakeObject: def __init__(self, name, type_id="App::DocumentObjectGroup"): self.Name = name self.TypeId = type_id self.Group = [] self.InList = [] self.Placement = sys.modules["FreeCAD"].Placement() self.PropertiesList = [] self.ViewObject = types.SimpleNamespace(Visibility=True) def addObject(self, child): if child not in self.Group: self.Group.append(child) if self not in child.InList: child.InList.append(self) def addProperty(self, prop_type, prop_name, group_name, description): if prop_name not in self.PropertiesList: self.PropertiesList.append(prop_name) 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 _reload_module(): sys.modules.pop("TerminalObjects", None) return importlib.import_module("TerminalObjects") class TerminalDirectionTest(unittest.TestCase): def test_terminal_direction_uses_terminal_and_parent_rotation(self): _install_fake_freecad() terminal_objects = _reload_module() app = sys.modules["FreeCAD"] terminal_rotates_z_to_x = app.Rotation( lambda vector: app.Vector(vector.z, vector.y, -vector.x) ) parent_rotates_x_to_y = app.Rotation( lambda vector: app.Vector(-vector.y, vector.x, vector.z) ) parent = FakeObject("QETDevice_ct_1") parent.Placement = app.Placement(app.Vector(100, 0, 0), parent_rotates_x_to_y) terminal = FakeObject("Terminal_P1", "Part::LocalCoordinateSystem") terminal.Placement = app.Placement(app.Vector(10, 0, 0), terminal_rotates_z_to_x) parent.addObject(terminal) direction = terminal_objects.terminal_direction(terminal) self.assertAlmostEqual(0.0, direction.x) self.assertAlmostEqual(1.0, direction.y) self.assertAlmostEqual(0.0, direction.z) class TemplateTerminalVisibilityTest(unittest.TestCase): def test_hide_template_terminal_hints_hides_template_lcs_but_keeps_engineering_terminal_visible(self): _install_fake_freecad() terminal_objects = _reload_module() container = FakeObject("QETDevice_ct_1") template_terminal = FakeObject("D1", "Part::LocalCoordinateSystem") template_terminal.Role = "Terminal" template_terminal.CanWire = True template_terminal.PropertiesList = ["Role", "CanWire", "QetTemplateSlotName"] container.addObject(template_terminal) engineering_terminal = FakeObject("QETTerminal_D1", "Part::LocalCoordinateSystem") terminal_objects.set_terminal_semantics( engineering_terminal, "project-1", "device-1", "terminal-1", "instance-1", label="D1", slot_name="D1", ) container.addObject(engineering_terminal) hidden = terminal_objects.hide_template_terminal_hints(container) self.assertEqual(1, hidden) self.assertFalse(template_terminal.ViewObject.Visibility) self.assertTrue(engineering_terminal.ViewObject.Visibility) if __name__ == "__main__": unittest.main()