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, 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 or Rotation() 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 class FakeViewObject: def __init__(self): self.Visibility = True self.ShapeColor = None 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() self.Placement = sys.modules["FreeCAD"].Placement() def isDerivedFrom(self, type_name): if self.TypeId == type_name: return True if type_name == "App::DocumentObjectGroup": return self.TypeId in {"App::DocumentObjectGroup", "App::Part"} 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.Name = "QETScene" self.Objects = [] 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", "TemplateSemantics", "DeviceImport", "TemplateInstantiation", ]: sys.modules.pop(name, None) return importlib.import_module("TemplateInstantiation"), importlib.import_module("TerminalObjects") class TemplateInstantiationTest(unittest.TestCase): def test_template_lcs_slots_become_engineering_terminals(self): _install_fake_freecad() template_instantiation, terminal_objects = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() root = terminal_objects.ensure_root_group(doc, "project-1") device = doc.addObject("App::Part", "QETDevice_ct_1") root.addObject(device) terminal_objects.ensure_string_property(device, "QetElementUuid", "QET Exchange", "", "ct-1") terminal_objects.ensure_string_property(device, "QetInstanceId", "QET Exchange", "", "inst-1") terminal_objects.ensure_string_property(device, "QetProjectUuid", "QET Exchange", "", "project-1") p1 = doc.addObject("Part::LocalCoordinateSystem", "P1") p1.Placement = app.Placement(app.Vector(10, 20, 30), app.Rotation()) p1.addProperty("App::PropertyString", "Role", "QET Template", "") p1.Role = "Terminal" p1.addProperty("App::PropertyBool", "CanWire", "QET Template", "") p1.CanWire = True p1.addProperty("App::PropertyString", "QetTemplateSlotName", "QET Template", "") p1.QetTemplateSlotName = "P1" device.addObject(p1) report = template_instantiation.ensure_engineering_terminals_for_device(doc, device) terminal_group = terminal_objects.find_child_group_by_kind( device, terminal_objects.TERMINAL_GROUP_KIND, ) terminals = terminal_objects.collect_terminal_objects(terminal_group) self.assertEqual(1, report["created_terminals"]) self.assertEqual(1, len(terminals)) self.assertEqual("local:inst-1:P1", terminals[0].QetTerminalUuid) self.assertEqual("inst-1", terminals[0].QetInstanceId) self.assertEqual("ct-1", terminals[0].QetElementUuid) self.assertEqual("P1", terminals[0].QetTemplateSlotName) self.assertEqual("local", terminals[0].QetTerminalBindingMode) self.assertTrue(terminals[0].CanWire) self.assertFalse(p1.ViewObject.Visibility) def test_device_without_template_slots_reports_no_created_terminals(self): _install_fake_freecad() template_instantiation, terminal_objects = _reload_modules() doc = FakeDocument() root = terminal_objects.ensure_root_group(doc, "project-1") device = doc.addObject("App::Part", "QETDevice_ct_1") root.addObject(device) terminal_objects.ensure_string_property(device, "QetElementUuid", "QET Exchange", "", "ct-1") terminal_objects.ensure_string_property(device, "QetInstanceId", "QET Exchange", "", "inst-1") terminal_objects.ensure_string_property(device, "QetProjectUuid", "QET Exchange", "", "project-1") report = template_instantiation.ensure_engineering_terminals_for_device(doc, device) terminal_group = terminal_objects.find_child_group_by_kind( device, terminal_objects.TERMINAL_GROUP_KIND, ) self.assertEqual(0, report["slots"]) self.assertEqual(0, report["created_terminals"]) self.assertEqual(1, report["skipped_devices_without_template_slots"]) self.assertIn("没有模板端子", report["warnings"][0]) self.assertEqual([], terminal_objects.collect_terminal_objects(terminal_group)) if __name__ == "__main__": unittest.main()