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(): fake_freecad = types.ModuleType("FreeCAD") 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.commands = {} fake_gui.addCommand = lambda name, command: fake_gui.commands.setdefault(name, command) sys.modules["FreeCADGui"] = fake_gui class FakeViewObject: def __init__(self): self.Visibility = True class FakeObject: def __init__(self, name, type_id): self.Name = name self.Label = name self.TypeId = type_id self.PropertiesList = [] self.Group = [] self.InList = [] self.ViewObject = FakeViewObject() def isDerivedFrom(self, type_name): if type_name == "App::DocumentObjectGroup": return self.TypeId == "App::DocumentObjectGroup" return self.TypeId == type_name 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.Name = "FakeDoc" self.Objects = [] self.recomputed = False 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 recompute(self): self.recomputed = True def _reload_modules(): for name in ["TerminalObjects", "StaleObjectActions"]: sys.modules.pop(name, None) terminal_objects = importlib.import_module("TerminalObjects") stale_object_actions = importlib.import_module("StaleObjectActions") return terminal_objects, stale_object_actions def _set_stale(terminal_objects, obj): terminal_objects.ensure_string_property( obj, "QetSyncStatus", "QET Sync", "Latest 2D/3D synchronization status", "Stale", ) class StaleObjectActionsTest(unittest.TestCase): def test_hides_and_shows_stale_object_trees(self): _install_fake_freecad() terminal_objects, stale_object_actions = _reload_modules() doc = FakeDocument() sys.modules["FreeCAD"].ActiveDocument = doc cabinet = doc.addObject("App::DocumentObjectGroup", "QETCabinet_1") terminal_objects.ensure_string_property(cabinet, "QetCabinetInstanceId", "QET Exchange", "", "1") _set_stale(terminal_objects, cabinet) cabinet_model = doc.addObject("Part::Feature", "CabinetModel") cabinet.addObject(cabinet_model) device = doc.addObject("App::DocumentObjectGroup", "QETDevice_device_stale") terminal_objects.ensure_string_property(device, "QetElementUuid", "QET Exchange", "", "device-stale") _set_stale(terminal_objects, device) model_child = doc.addObject("Part::Feature", "ImportedModel") device.addObject(model_child) terminal = doc.addObject("Part::LocalCoordinateSystem", "QETTerminal_terminal_stale") terminal_objects.set_terminal_semantics( terminal, "project-1", "device-stale", "terminal-stale", "instance-stale", ) _set_stale(terminal_objects, terminal) task = doc.addObject("Part::Feature", "QETWireTask_wire_stale") terminal_objects.ensure_string_property(task, "QetWireUuid", "QET Wiring", "", "wire-stale") terminal_objects.ensure_string_property(task, "RouteType", "QET Wiring", "", "Task") _set_stale(terminal_objects, task) routed_group = doc.addObject("App::DocumentObjectGroup", "QETWiring_04_Routed") routed_wire = doc.addObject("Part::Feature", "QETWire_wire_stale") terminal_objects.ensure_string_property(routed_wire, "QetWireUuid", "QET Wiring", "", "wire-stale") routed_group.addObject(routed_wire) _set_stale(terminal_objects, routed_wire) report = stale_object_actions.hide_stale_objects(doc) self.assertEqual(5, report["total"]) self.assertEqual(1, report["cabinets"]) self.assertEqual(1, report["devices"]) self.assertEqual(1, report["terminals"]) self.assertEqual(1, report["wire_tasks"]) self.assertEqual(1, report["routed_wires"]) self.assertFalse(cabinet.ViewObject.Visibility) self.assertFalse(cabinet_model.ViewObject.Visibility) self.assertFalse(device.ViewObject.Visibility) self.assertFalse(model_child.ViewObject.Visibility) self.assertFalse(terminal.ViewObject.Visibility) self.assertFalse(task.ViewObject.Visibility) self.assertFalse(routed_wire.ViewObject.Visibility) self.assertTrue(doc.recomputed) show_report = stale_object_actions.show_stale_objects(doc) self.assertEqual(7, show_report["affected_objects"]) self.assertTrue(cabinet.ViewObject.Visibility) self.assertTrue(cabinet_model.ViewObject.Visibility) self.assertTrue(device.ViewObject.Visibility) self.assertTrue(model_child.ViewObject.Visibility) self.assertTrue(terminal.ViewObject.Visibility) self.assertTrue(task.ViewObject.Visibility) self.assertTrue(routed_wire.ViewObject.Visibility) def test_registers_toolbar_commands(self): _install_fake_freecad() _terminal_objects, stale_object_actions = _reload_modules() stale_object_actions.register_commands() commands = sys.modules["FreeCADGui"].commands self.assertIn("QET_Exchange_HideStaleObjects", commands) self.assertIn("QET_Exchange_ShowStaleObjects", commands) self.assertIn("QET_Exchange_SummarizeStaleObjects", commands) if __name__ == "__main__": unittest.main()