Improve QET scene sync and cabinet reuse
parent
8a63e8de40
commit
33ae1d8e31
@ -0,0 +1,291 @@
|
|||||||
|
# FreeCADExchange stale object marking helpers.
|
||||||
|
|
||||||
|
import FreeCAD as App
|
||||||
|
|
||||||
|
import TerminalObjects
|
||||||
|
|
||||||
|
try:
|
||||||
|
import WiringObjects
|
||||||
|
except Exception:
|
||||||
|
WiringObjects = None
|
||||||
|
|
||||||
|
|
||||||
|
SYNC_STATUS_ACTIVE = "Active"
|
||||||
|
SYNC_STATUS_STALE = "Stale"
|
||||||
|
SYNC_GROUP = "QET Sync"
|
||||||
|
CABINET_GROUP_PREFIX = "QETCabinet_"
|
||||||
|
|
||||||
|
|
||||||
|
def _string_value(item, field_name):
|
||||||
|
if not isinstance(item, dict):
|
||||||
|
return ""
|
||||||
|
value = item.get(field_name, "")
|
||||||
|
if value is None:
|
||||||
|
return ""
|
||||||
|
return str(value).strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _set_status(obj, status, reason=""):
|
||||||
|
TerminalObjects.ensure_string_property(
|
||||||
|
obj,
|
||||||
|
"QetSyncStatus",
|
||||||
|
SYNC_GROUP,
|
||||||
|
"Latest 2D/3D synchronization status",
|
||||||
|
status,
|
||||||
|
)
|
||||||
|
TerminalObjects.ensure_string_property(
|
||||||
|
obj,
|
||||||
|
"QetSyncReason",
|
||||||
|
SYNC_GROUP,
|
||||||
|
"Why the object has this synchronization status",
|
||||||
|
reason,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _payload_identity_sets(payload):
|
||||||
|
cabinet_instance_ids = set()
|
||||||
|
device_element_uuids = set()
|
||||||
|
device_instance_ids = set()
|
||||||
|
terminal_uuids = set()
|
||||||
|
wire_uuids = set()
|
||||||
|
|
||||||
|
cabinet = payload.get("cabinet")
|
||||||
|
if isinstance(cabinet, dict):
|
||||||
|
for field_name in ("cabinet_instance_id", "cabinet_uuid", "location_id"):
|
||||||
|
value = _string_value(cabinet, field_name)
|
||||||
|
if value:
|
||||||
|
cabinet_instance_ids.add(value)
|
||||||
|
break
|
||||||
|
|
||||||
|
for item in payload.get("devices", []) or []:
|
||||||
|
element_uuid = _string_value(item, "element_uuid")
|
||||||
|
instance_id = _string_value(item, "instance_id")
|
||||||
|
if element_uuid:
|
||||||
|
device_element_uuids.add(element_uuid)
|
||||||
|
if instance_id:
|
||||||
|
device_instance_ids.add(instance_id)
|
||||||
|
|
||||||
|
for item in payload.get("terminals", []) or []:
|
||||||
|
terminal_uuid = _string_value(item, "terminal_uuid")
|
||||||
|
if terminal_uuid:
|
||||||
|
terminal_uuids.add(terminal_uuid)
|
||||||
|
|
||||||
|
for item in payload.get("wires", []) or []:
|
||||||
|
wire_uuid = (
|
||||||
|
_string_value(item, "wire_id")
|
||||||
|
or _string_value(item, "wire_uuid")
|
||||||
|
or _string_value(item, "id")
|
||||||
|
)
|
||||||
|
if wire_uuid:
|
||||||
|
wire_uuids.add(wire_uuid)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"cabinet_instance_ids": cabinet_instance_ids,
|
||||||
|
"device_element_uuids": device_element_uuids,
|
||||||
|
"device_instance_ids": device_instance_ids,
|
||||||
|
"terminal_uuids": terminal_uuids,
|
||||||
|
"wire_uuids": wire_uuids,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_cabinet_groups(doc):
|
||||||
|
root = doc.getObject(TerminalObjects.ROOT_GROUP_NAME)
|
||||||
|
candidates = []
|
||||||
|
if root is not None:
|
||||||
|
candidates.extend(list(getattr(root, "Group", []) or []))
|
||||||
|
if not candidates:
|
||||||
|
candidates.extend(list(getattr(doc, "Objects", []) or []))
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
for obj in candidates:
|
||||||
|
name = getattr(obj, "Name", "")
|
||||||
|
if not name.startswith(CABINET_GROUP_PREFIX) and name != "QETCabinetModel":
|
||||||
|
continue
|
||||||
|
if "QetCabinetInstanceId" not in getattr(obj, "PropertiesList", []):
|
||||||
|
continue
|
||||||
|
if id(obj) in seen:
|
||||||
|
continue
|
||||||
|
seen.add(id(obj))
|
||||||
|
yield obj
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_device_groups(doc):
|
||||||
|
root = doc.getObject(TerminalObjects.ROOT_GROUP_NAME)
|
||||||
|
candidates = []
|
||||||
|
if root is not None:
|
||||||
|
candidates.extend(list(getattr(root, "Group", []) or []))
|
||||||
|
if not candidates:
|
||||||
|
candidates.extend(list(getattr(doc, "Objects", []) or []))
|
||||||
|
|
||||||
|
seen = set()
|
||||||
|
for obj in candidates:
|
||||||
|
name = getattr(obj, "Name", "")
|
||||||
|
if not name.startswith(TerminalObjects.DEVICE_GROUP_PREFIX):
|
||||||
|
continue
|
||||||
|
if "QetElementUuid" not in getattr(obj, "PropertiesList", []):
|
||||||
|
continue
|
||||||
|
if id(obj) in seen:
|
||||||
|
continue
|
||||||
|
seen.add(id(obj))
|
||||||
|
yield obj
|
||||||
|
|
||||||
|
|
||||||
|
def _mark_device(device_group, identity_sets):
|
||||||
|
element_uuid = (getattr(device_group, "QetElementUuid", "") or "").strip()
|
||||||
|
instance_id = (getattr(device_group, "QetInstanceId", "") or "").strip()
|
||||||
|
active = False
|
||||||
|
if element_uuid and element_uuid in identity_sets["device_element_uuids"]:
|
||||||
|
active = True
|
||||||
|
if instance_id and instance_id in identity_sets["device_instance_ids"]:
|
||||||
|
active = True
|
||||||
|
|
||||||
|
if active:
|
||||||
|
_set_status(device_group, SYNC_STATUS_ACTIVE)
|
||||||
|
return "active"
|
||||||
|
|
||||||
|
_set_status(
|
||||||
|
device_group,
|
||||||
|
SYNC_STATUS_STALE,
|
||||||
|
"This 3D device is not present in the latest 2D exchange payload.",
|
||||||
|
)
|
||||||
|
return "stale"
|
||||||
|
|
||||||
|
|
||||||
|
def _mark_cabinet(cabinet_group, identity_sets):
|
||||||
|
cabinet_instance_id = (getattr(cabinet_group, "QetCabinetInstanceId", "") or "").strip()
|
||||||
|
active = bool(cabinet_instance_id and cabinet_instance_id in identity_sets["cabinet_instance_ids"])
|
||||||
|
if active:
|
||||||
|
_set_status(cabinet_group, SYNC_STATUS_ACTIVE)
|
||||||
|
return "active"
|
||||||
|
|
||||||
|
_set_status(
|
||||||
|
cabinet_group,
|
||||||
|
SYNC_STATUS_STALE,
|
||||||
|
"This 3D cabinet is not present in the latest 2D exchange payload.",
|
||||||
|
)
|
||||||
|
return "stale"
|
||||||
|
|
||||||
|
|
||||||
|
def _mark_terminals(device_group, identity_sets):
|
||||||
|
report = {"active": 0, "stale": 0}
|
||||||
|
terminal_group = TerminalObjects.find_child_group_by_kind(
|
||||||
|
device_group,
|
||||||
|
TerminalObjects.TERMINAL_GROUP_KIND,
|
||||||
|
)
|
||||||
|
for terminal in TerminalObjects.collect_terminal_objects(terminal_group):
|
||||||
|
terminal_uuid = (getattr(terminal, "QetTerminalUuid", "") or "").strip()
|
||||||
|
if TerminalObjects.is_local_terminal_uuid(terminal_uuid):
|
||||||
|
_set_status(terminal, SYNC_STATUS_ACTIVE)
|
||||||
|
report["active"] += 1
|
||||||
|
continue
|
||||||
|
if terminal_uuid and terminal_uuid in identity_sets["terminal_uuids"]:
|
||||||
|
_set_status(terminal, SYNC_STATUS_ACTIVE)
|
||||||
|
report["active"] += 1
|
||||||
|
continue
|
||||||
|
_set_status(
|
||||||
|
terminal,
|
||||||
|
SYNC_STATUS_STALE,
|
||||||
|
"This 3D terminal is not present in the latest 2D exchange payload.",
|
||||||
|
)
|
||||||
|
report["stale"] += 1
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_wire_task_objects(doc):
|
||||||
|
task_group = doc.getObject("QETWiring_01_Tasks")
|
||||||
|
if task_group is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for obj in list(getattr(task_group, "Group", []) or []):
|
||||||
|
if (getattr(obj, "RouteType", "") or "").strip() != "Task":
|
||||||
|
continue
|
||||||
|
if "QetWireUuid" not in getattr(obj, "PropertiesList", []):
|
||||||
|
continue
|
||||||
|
result.append(obj)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_routed_wire_objects(doc):
|
||||||
|
if WiringObjects is None:
|
||||||
|
return []
|
||||||
|
try:
|
||||||
|
return WiringObjects.iter_routed_wire_objects(doc)
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def _mark_wire_object(obj, identity_sets, stale_reason):
|
||||||
|
wire_uuid = (getattr(obj, "QetWireUuid", "") or "").strip()
|
||||||
|
if wire_uuid and wire_uuid in identity_sets["wire_uuids"]:
|
||||||
|
_set_status(obj, SYNC_STATUS_ACTIVE)
|
||||||
|
return "active"
|
||||||
|
|
||||||
|
_set_status(obj, SYNC_STATUS_STALE, stale_reason)
|
||||||
|
return "stale"
|
||||||
|
|
||||||
|
|
||||||
|
def mark_stale_objects_from_payload(payload, doc=None):
|
||||||
|
if doc is None:
|
||||||
|
doc = getattr(App, "ActiveDocument", None)
|
||||||
|
if doc is None:
|
||||||
|
raise RuntimeError("No active FreeCAD document is available.")
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
raise RuntimeError("Exchange payload must be an object.")
|
||||||
|
|
||||||
|
identity_sets = _payload_identity_sets(payload)
|
||||||
|
report = {
|
||||||
|
"active_cabinets": 0,
|
||||||
|
"stale_cabinets": 0,
|
||||||
|
"active_devices": 0,
|
||||||
|
"stale_devices": 0,
|
||||||
|
"active_terminals": 0,
|
||||||
|
"stale_terminals": 0,
|
||||||
|
"active_wire_tasks": 0,
|
||||||
|
"stale_wire_tasks": 0,
|
||||||
|
"active_routed_wires": 0,
|
||||||
|
"stale_routed_wires": 0,
|
||||||
|
"warnings": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for cabinet_group in _iter_cabinet_groups(doc):
|
||||||
|
cabinet_status = _mark_cabinet(cabinet_group, identity_sets)
|
||||||
|
if cabinet_status == "active":
|
||||||
|
report["active_cabinets"] += 1
|
||||||
|
else:
|
||||||
|
report["stale_cabinets"] += 1
|
||||||
|
|
||||||
|
for device_group in _iter_device_groups(doc):
|
||||||
|
device_status = _mark_device(device_group, identity_sets)
|
||||||
|
if device_status == "active":
|
||||||
|
report["active_devices"] += 1
|
||||||
|
else:
|
||||||
|
report["stale_devices"] += 1
|
||||||
|
|
||||||
|
terminal_report = _mark_terminals(device_group, identity_sets)
|
||||||
|
report["active_terminals"] += terminal_report["active"]
|
||||||
|
report["stale_terminals"] += terminal_report["stale"]
|
||||||
|
|
||||||
|
for task in _iter_wire_task_objects(doc):
|
||||||
|
status = _mark_wire_object(
|
||||||
|
task,
|
||||||
|
identity_sets,
|
||||||
|
"This 3D wire task is not present in the latest 2D exchange payload.",
|
||||||
|
)
|
||||||
|
if status == "active":
|
||||||
|
report["active_wire_tasks"] += 1
|
||||||
|
else:
|
||||||
|
report["stale_wire_tasks"] += 1
|
||||||
|
|
||||||
|
for wire in _iter_routed_wire_objects(doc):
|
||||||
|
status = _mark_wire_object(
|
||||||
|
wire,
|
||||||
|
identity_sets,
|
||||||
|
"This routed 3D wire is not present in the latest 2D exchange payload.",
|
||||||
|
)
|
||||||
|
if status == "active":
|
||||||
|
report["active_routed_wires"] += 1
|
||||||
|
else:
|
||||||
|
report["stale_routed_wires"] += 1
|
||||||
|
|
||||||
|
return report
|
||||||
@ -0,0 +1,184 @@
|
|||||||
|
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()
|
||||||
@ -0,0 +1,224 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
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)
|
||||||
|
|
||||||
|
def removeObject(self, child):
|
||||||
|
if child in self.Group:
|
||||||
|
self.Group.remove(child)
|
||||||
|
if self in child.InList:
|
||||||
|
child.InList.remove(self)
|
||||||
|
|
||||||
|
|
||||||
|
class FakeDocument:
|
||||||
|
def __init__(self):
|
||||||
|
self.Name = "FakeDoc"
|
||||||
|
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 _reload_modules():
|
||||||
|
for name in [
|
||||||
|
"DeviceImport",
|
||||||
|
"TerminalObjects",
|
||||||
|
"WiringObjects",
|
||||||
|
"StaleObjectSync",
|
||||||
|
]:
|
||||||
|
sys.modules.pop(name, None)
|
||||||
|
terminal_objects = importlib.import_module("TerminalObjects")
|
||||||
|
wiring_objects = importlib.import_module("WiringObjects")
|
||||||
|
stale_object_sync = importlib.import_module("StaleObjectSync")
|
||||||
|
return terminal_objects, wiring_objects, stale_object_sync
|
||||||
|
|
||||||
|
|
||||||
|
class StaleObjectSyncTest(unittest.TestCase):
|
||||||
|
def test_marks_devices_terminals_and_wires_missing_from_payload_as_stale(self):
|
||||||
|
_install_fake_freecad()
|
||||||
|
terminal_objects, wiring_objects, stale_object_sync = _reload_modules()
|
||||||
|
|
||||||
|
doc = FakeDocument()
|
||||||
|
root = terminal_objects.ensure_root_group(doc, "project-1")
|
||||||
|
|
||||||
|
active_cabinet = doc.addObject("App::DocumentObjectGroup", "QETCabinet_1")
|
||||||
|
active_cabinet.addProperty("App::PropertyString", "QetCabinetInstanceId", "QET Exchange", "")
|
||||||
|
active_cabinet.QetCabinetInstanceId = "1"
|
||||||
|
root.addObject(active_cabinet)
|
||||||
|
|
||||||
|
stale_cabinet = doc.addObject("App::DocumentObjectGroup", "QETCabinet_2")
|
||||||
|
stale_cabinet.addProperty("App::PropertyString", "QetCabinetInstanceId", "QET Exchange", "")
|
||||||
|
stale_cabinet.QetCabinetInstanceId = "2"
|
||||||
|
root.addObject(stale_cabinet)
|
||||||
|
|
||||||
|
active_device = doc.addObject("App::Part", "QETDevice_device_active")
|
||||||
|
active_device.addProperty("App::PropertyString", "QetElementUuid", "QET Exchange", "")
|
||||||
|
active_device.QetElementUuid = "device-active"
|
||||||
|
active_device.addProperty("App::PropertyString", "QetInstanceId", "QET Exchange", "")
|
||||||
|
active_device.QetInstanceId = "instance-active"
|
||||||
|
root.addObject(active_device)
|
||||||
|
|
||||||
|
stale_device = doc.addObject("App::Part", "QETDevice_device_stale")
|
||||||
|
stale_device.addProperty("App::PropertyString", "QetElementUuid", "QET Exchange", "")
|
||||||
|
stale_device.QetElementUuid = "device-stale"
|
||||||
|
stale_device.addProperty("App::PropertyString", "QetInstanceId", "QET Exchange", "")
|
||||||
|
stale_device.QetInstanceId = "instance-stale"
|
||||||
|
root.addObject(stale_device)
|
||||||
|
|
||||||
|
active_terminals = terminal_objects.ensure_terminal_group(
|
||||||
|
doc,
|
||||||
|
active_device,
|
||||||
|
project_uuid="project-1",
|
||||||
|
instance_id="instance-active",
|
||||||
|
)
|
||||||
|
active_terminal = doc.addObject("Part::LocalCoordinateSystem", "QETTerminal_terminal_active")
|
||||||
|
terminal_objects.set_terminal_semantics(
|
||||||
|
active_terminal,
|
||||||
|
"project-1",
|
||||||
|
"device-active",
|
||||||
|
"terminal-active",
|
||||||
|
"instance-active",
|
||||||
|
)
|
||||||
|
active_terminals.addObject(active_terminal)
|
||||||
|
|
||||||
|
stale_terminals = terminal_objects.ensure_terminal_group(
|
||||||
|
doc,
|
||||||
|
stale_device,
|
||||||
|
project_uuid="project-1",
|
||||||
|
instance_id="instance-stale",
|
||||||
|
)
|
||||||
|
stale_terminal = doc.addObject("Part::LocalCoordinateSystem", "QETTerminal_terminal_stale")
|
||||||
|
terminal_objects.set_terminal_semantics(
|
||||||
|
stale_terminal,
|
||||||
|
"project-1",
|
||||||
|
"device-stale",
|
||||||
|
"terminal-stale",
|
||||||
|
"instance-stale",
|
||||||
|
)
|
||||||
|
stale_terminals.addObject(stale_terminal)
|
||||||
|
|
||||||
|
active_task = wiring_objects.create_wire_task(
|
||||||
|
doc,
|
||||||
|
"project-1",
|
||||||
|
"wire-active",
|
||||||
|
"wire-active",
|
||||||
|
"terminal-active",
|
||||||
|
"terminal-b",
|
||||||
|
"instance-active",
|
||||||
|
"instance-b",
|
||||||
|
)
|
||||||
|
stale_task = wiring_objects.create_wire_task(
|
||||||
|
doc,
|
||||||
|
"project-1",
|
||||||
|
"wire-stale",
|
||||||
|
"wire-stale",
|
||||||
|
"terminal-stale",
|
||||||
|
"terminal-c",
|
||||||
|
"instance-stale",
|
||||||
|
"instance-c",
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"project_uuid": "project-1",
|
||||||
|
"cabinet": {
|
||||||
|
"location_id": 1,
|
||||||
|
},
|
||||||
|
"devices": [
|
||||||
|
{
|
||||||
|
"element_uuid": "device-active",
|
||||||
|
"instance_id": "instance-active",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"terminals": [
|
||||||
|
{
|
||||||
|
"terminal_uuid": "terminal-active",
|
||||||
|
"instance_id": "instance-active",
|
||||||
|
"element_uuid": "device-active",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"wires": [
|
||||||
|
{
|
||||||
|
"wire_id": "wire-active",
|
||||||
|
"start_terminal_uuid": "terminal-active",
|
||||||
|
"end_terminal_uuid": "terminal-b",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
report = stale_object_sync.mark_stale_objects_from_payload(payload, doc)
|
||||||
|
|
||||||
|
self.assertEqual(1, report["active_cabinets"])
|
||||||
|
self.assertEqual(1, report["stale_cabinets"])
|
||||||
|
self.assertEqual(1, report["active_devices"])
|
||||||
|
self.assertEqual(1, report["stale_devices"])
|
||||||
|
self.assertEqual(1, report["active_terminals"])
|
||||||
|
self.assertEqual(1, report["stale_terminals"])
|
||||||
|
self.assertEqual(1, report["active_wire_tasks"])
|
||||||
|
self.assertEqual(1, report["stale_wire_tasks"])
|
||||||
|
self.assertEqual("Active", active_cabinet.QetSyncStatus)
|
||||||
|
self.assertEqual("Stale", stale_cabinet.QetSyncStatus)
|
||||||
|
self.assertEqual("Active", active_device.QetSyncStatus)
|
||||||
|
self.assertEqual("Stale", stale_device.QetSyncStatus)
|
||||||
|
self.assertEqual("Active", active_terminal.QetSyncStatus)
|
||||||
|
self.assertEqual("Stale", stale_terminal.QetSyncStatus)
|
||||||
|
self.assertEqual("Active", active_task.QetSyncStatus)
|
||||||
|
self.assertEqual("Stale", stale_task.QetSyncStatus)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in New Issue