You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

360 lines
12 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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 _device_report_label(device_group):
display_tag = (getattr(device_group, "QetDisplayTag", "") or "").strip()
instance_id = (getattr(device_group, "QetInstanceId", "") or "").strip()
element_uuid = (getattr(device_group, "QetElementUuid", "") or "").strip()
return display_tag or instance_id or element_uuid or getattr(device_group, "Name", "")
def _payload_identity_sets(payload):
cabinet_instance_ids = 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 []:
instance_id = _string_value(item, "device_instance_id")
if instance_id:
device_instance_ids.add(instance_id)
for terminal in item.get("terminals", []) or []:
terminal_uuid = _string_value(terminal, "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_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 = bool(
instance_id and instance_id in identity_sets["device_instance_ids"]
)
try:
import FreeCAD as _App
_App.Console.PrintMessage(
"[FreeCADExchange] stale check device: name={0}, element_uuid={1}, instance_id={2}, active={3}\n".format(
getattr(device_group, "Name", ""), element_uuid, instance_id, active
)
)
except Exception:
pass
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, authoritative_stale_terminal_uuids=None):
"""authoritative_stale_terminal_uuids: QET-side stale list (binding_table - diagram_elements)."""
if authoritative_stale_terminal_uuids is None:
authoritative_stale_terminal_uuids = set()
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 authoritative_stale_terminal_uuids:
_set_status(
terminal,
SYNC_STATUS_STALE,
"This 3D terminal was removed from the 2D schematic (QET authoritative).",
)
report["stale"] += 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)
# QET 侧权威失效列表stale = binding_table - diagram_elements
authoritative_stale_instance_ids = set()
authoritative_stale_terminal_uuids = set()
for item in payload.get("stale_devices", []) or []:
instance_id = _string_value(item, "instance_id")
tu = _string_value(item, "terminal_uuid")
if instance_id:
authoritative_stale_instance_ids.add(instance_id)
if tu:
authoritative_stale_terminal_uuids.add(tu)
report = {
"active_cabinets": 0,
"stale_cabinets": 0,
"active_devices": 0,
"stale_devices": 0,
"stale_device_details": [],
"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):
element_uuid = (getattr(device_group, "QetElementUuid", "") or "").strip()
instance_id = (getattr(device_group, "QetInstanceId", "") or "").strip()
instance_is_active = bool(
instance_id and instance_id in identity_sets["device_instance_ids"]
)
if (
not instance_is_active
and (instance_id and instance_id in authoritative_stale_instance_ids)
):
_set_status(
device_group,
SYNC_STATUS_STALE,
"This 3D device was removed from the 2D schematic (QET authoritative).",
)
report["stale_devices"] += 1
report["stale_device_details"].append(
{
"display_tag": (getattr(device_group, "QetDisplayTag", "") or "").strip(),
"instance_id": instance_id,
"element_uuid": element_uuid,
"label": _device_report_label(device_group),
}
)
else:
device_status = _mark_device(device_group, identity_sets)
if device_status == "active":
report["active_devices"] += 1
else:
report["stale_devices"] += 1
report["stale_device_details"].append(
{
"display_tag": (getattr(device_group, "QetDisplayTag", "") or "").strip(),
"instance_id": instance_id,
"element_uuid": element_uuid,
"label": _device_report_label(device_group),
}
)
terminal_report = _mark_terminals(
device_group, identity_sets, authoritative_stale_terminal_uuids
)
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