|
|
|
|
@ -53,11 +53,15 @@ STATE_WRITEBACK_REPORT = "_qet_exchange_writeback_report"
|
|
|
|
|
STATE_IMPORT_SCHEDULED = "_qet_exchange_import_scheduled"
|
|
|
|
|
STATE_TREE_FILTER = "_qet_exchange_tree_filter"
|
|
|
|
|
STATE_TREE_SIGNAL_CONNECTIONS = "_qet_exchange_tree_signal_connections"
|
|
|
|
|
STATE_SIGNAL_TIMER = "_qet_exchange_signal_timer"
|
|
|
|
|
STATE_SIGNAL_REVISION = "_qet_exchange_signal_revision"
|
|
|
|
|
STATE_JSON_PATH = "_qet_exchange_json_path"
|
|
|
|
|
TREE_FILTER_MARKER = "_qet_exchange_tree_filter_installed"
|
|
|
|
|
TREE_SIGNAL_MARKER = "_qet_exchange_tree_signal_installed"
|
|
|
|
|
IMPORT_READY_DELAY_MS = 1500
|
|
|
|
|
IMPORT_READY_RETRY_DELAY_MS = 1000
|
|
|
|
|
IMPORT_READY_MAX_RETRIES = 10
|
|
|
|
|
SIGNAL_POLL_INTERVAL_MS = 1500
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ExchangeValidationError(RuntimeError):
|
|
|
|
|
@ -678,6 +682,18 @@ def _normalize_conductor_uuids(item, entry_label):
|
|
|
|
|
return normalized
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_wire_style(item, entry_label):
|
|
|
|
|
value = item.get("wire_style", {})
|
|
|
|
|
if value is None:
|
|
|
|
|
return {}
|
|
|
|
|
if not isinstance(value, dict):
|
|
|
|
|
_append_debug_log(
|
|
|
|
|
"Field 'wire_style' in {0} is not an object; ignored.".format(entry_label)
|
|
|
|
|
)
|
|
|
|
|
return {}
|
|
|
|
|
return dict(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _normalize_wires(payload):
|
|
|
|
|
wires = payload.get("wires", [])
|
|
|
|
|
if wires is None:
|
|
|
|
|
@ -709,6 +725,7 @@ def _normalize_wires(payload):
|
|
|
|
|
"wire_mark": _optional_string(item, "wire_mark", entry_label),
|
|
|
|
|
"wire_mark_is_manual": wire_mark_is_manual,
|
|
|
|
|
"wire_style_id": _optional_text(item, "wire_style_id"),
|
|
|
|
|
"wire_style": _normalize_wire_style(item, entry_label),
|
|
|
|
|
"start_element_uuid": _optional_string(item, "start_element_uuid", entry_label),
|
|
|
|
|
"start_instance_id": _optional_string(item, "start_instance_id", entry_label),
|
|
|
|
|
"start_terminal_uuid": _optional_string(item, "start_terminal_uuid", entry_label),
|
|
|
|
|
@ -996,6 +1013,160 @@ def _import_wiring_tasks(payload):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _signal_path_for_json(json_path):
|
|
|
|
|
try:
|
|
|
|
|
return str(Path(json_path).with_name("qet_exchange_signal.json"))
|
|
|
|
|
except Exception:
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _load_exchange_signal(signal_path):
|
|
|
|
|
path = Path(signal_path)
|
|
|
|
|
if not path.is_file():
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
raw_text = path.read_text(encoding="utf-8")
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
_append_debug_log("exchange signal read failed: {0}".format(exc))
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
signal_payload = json.loads(raw_text)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
_append_debug_log("exchange signal JSON parse failed: {0}".format(exc))
|
|
|
|
|
return None
|
|
|
|
|
if not isinstance(signal_payload, dict):
|
|
|
|
|
_append_debug_log("exchange signal ignored: root is not an object")
|
|
|
|
|
return None
|
|
|
|
|
return signal_payload
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _signal_revision(signal_payload):
|
|
|
|
|
try:
|
|
|
|
|
return int(signal_payload.get("revision", 0) or 0)
|
|
|
|
|
except Exception:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reload_exchange_payload_from_signal(json_path, signal_payload):
|
|
|
|
|
if WiringImport is None:
|
|
|
|
|
_append_debug_log("signal reload skipped: WiringImport module unavailable")
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
current_payload = getattr(App, STATE_PAYLOAD, None)
|
|
|
|
|
expected_project_uuid = ""
|
|
|
|
|
if isinstance(current_payload, dict):
|
|
|
|
|
expected_project_uuid = str(current_payload.get("project_uuid", "") or "").strip()
|
|
|
|
|
signal_project_uuid = str(signal_payload.get("project_uuid", "") or "").strip()
|
|
|
|
|
if expected_project_uuid and signal_project_uuid and signal_project_uuid != expected_project_uuid:
|
|
|
|
|
message = "exchange signal project_uuid mismatch: signal={0}, current={1}".format(
|
|
|
|
|
signal_project_uuid,
|
|
|
|
|
expected_project_uuid,
|
|
|
|
|
)
|
|
|
|
|
_append_debug_log(message)
|
|
|
|
|
App.Console.PrintError("[FreeCADExchange] {0}\n".format(message))
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
payload = load_exchange_payload(json_path)
|
|
|
|
|
summary = _build_summary(payload, json_path)
|
|
|
|
|
setattr(App, STATE_PAYLOAD, payload)
|
|
|
|
|
setattr(App, STATE_SUMMARY, summary)
|
|
|
|
|
|
|
|
|
|
wiring_report = WiringImport.import_wire_tasks_from_payload(payload, App.ActiveDocument)
|
|
|
|
|
style_report = WiringImport.apply_wire_styles_from_payload(payload, App.ActiveDocument)
|
|
|
|
|
setattr(App, STATE_WIRING_IMPORT_REPORT, wiring_report)
|
|
|
|
|
try:
|
|
|
|
|
App.ActiveDocument.recompute()
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
_append_debug_log("signal reload recompute failed: {0}".format(exc))
|
|
|
|
|
|
|
|
|
|
revision = _signal_revision(signal_payload)
|
|
|
|
|
_append_debug_log(
|
|
|
|
|
"exchange signal reload complete: revision={0}, tasks_updated={1}, routed_matched={2}, view_updates={3}".format(
|
|
|
|
|
revision,
|
|
|
|
|
wiring_report.get("updated_tasks", 0) if isinstance(wiring_report, dict) else 0,
|
|
|
|
|
style_report.get("routed_wires_matched", 0) if isinstance(style_report, dict) else 0,
|
|
|
|
|
style_report.get("updated_view", 0) if isinstance(style_report, dict) else 0,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
App.Console.PrintMessage(
|
|
|
|
|
"[FreeCADExchange] Reloaded QET wire styles: revision {0}, tasks updated {1}, routed wires matched {2}, view updates {3}\n".format(
|
|
|
|
|
revision,
|
|
|
|
|
wiring_report.get("updated_tasks", 0) if isinstance(wiring_report, dict) else 0,
|
|
|
|
|
style_report.get("routed_wires_matched", 0) if isinstance(style_report, dict) else 0,
|
|
|
|
|
style_report.get("updated_view", 0) if isinstance(style_report, dict) else 0,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
return {"wiring_report": wiring_report, "style_report": style_report}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _check_exchange_signal(json_path):
|
|
|
|
|
signal_path = _signal_path_for_json(json_path)
|
|
|
|
|
if not signal_path:
|
|
|
|
|
return
|
|
|
|
|
signal_payload = _load_exchange_signal(signal_path)
|
|
|
|
|
if not isinstance(signal_payload, dict):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
event_name = str(signal_payload.get("event", "") or "").strip()
|
|
|
|
|
if event_name and event_name not in {"wire_style_changed"}:
|
|
|
|
|
_append_debug_log("exchange signal event treated as reload: {0}".format(event_name))
|
|
|
|
|
|
|
|
|
|
revision = _signal_revision(signal_payload)
|
|
|
|
|
if revision <= 0:
|
|
|
|
|
return
|
|
|
|
|
last_revision = int(getattr(App, STATE_SIGNAL_REVISION, 0) or 0)
|
|
|
|
|
if revision <= last_revision:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
reload_exchange_payload_from_signal(json_path, signal_payload)
|
|
|
|
|
except ExchangeValidationError as exc:
|
|
|
|
|
_append_debug_log("exchange signal reload validation failed: {0}".format(exc))
|
|
|
|
|
App.Console.PrintError("[FreeCADExchange] Signal reload failed: {0}\n".format(exc))
|
|
|
|
|
return
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
_append_debug_log("exchange signal reload failed: {0}".format(exc))
|
|
|
|
|
_append_debug_log(traceback.format_exc())
|
|
|
|
|
App.Console.PrintError("[FreeCADExchange] Signal reload failed: {0}\n".format(exc))
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
setattr(App, STATE_SIGNAL_REVISION, revision)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _install_exchange_signal_timer(json_path):
|
|
|
|
|
json_path = str(json_path or "").strip()
|
|
|
|
|
if not json_path:
|
|
|
|
|
return
|
|
|
|
|
existing_timer = getattr(App, STATE_SIGNAL_TIMER, None)
|
|
|
|
|
if existing_timer is not None:
|
|
|
|
|
try:
|
|
|
|
|
existing_timer.stop()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
signal_path = _signal_path_for_json(json_path)
|
|
|
|
|
initial_signal = _load_exchange_signal(signal_path) if signal_path else None
|
|
|
|
|
if isinstance(initial_signal, dict):
|
|
|
|
|
setattr(App, STATE_SIGNAL_REVISION, _signal_revision(initial_signal))
|
|
|
|
|
else:
|
|
|
|
|
setattr(App, STATE_SIGNAL_REVISION, 0)
|
|
|
|
|
|
|
|
|
|
# 中文注释:保持 QTimer 引用,避免 Python 对象被回收;FreeCAD 后续收到 QET 信号后
|
|
|
|
|
# 只重载 wiring task / routed wire 样式,不重新创建全部设备。
|
|
|
|
|
timer = QtCore.QTimer()
|
|
|
|
|
timer.setInterval(SIGNAL_POLL_INTERVAL_MS)
|
|
|
|
|
timer.timeout.connect(lambda: _check_exchange_signal(json_path))
|
|
|
|
|
timer.start()
|
|
|
|
|
setattr(App, STATE_SIGNAL_TIMER, timer)
|
|
|
|
|
setattr(App, STATE_JSON_PATH, json_path)
|
|
|
|
|
_append_debug_log(
|
|
|
|
|
"exchange signal timer installed: path={0}, interval_ms={1}".format(
|
|
|
|
|
signal_path,
|
|
|
|
|
SIGNAL_POLL_INTERVAL_MS,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
DEFAULT_SCENE_FILE_NAME = "QETScene.FCStd"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -1648,6 +1819,21 @@ def _run_scheduled_device_import(attempt=0):
|
|
|
|
|
wiring_report = _import_wiring_tasks(payload)
|
|
|
|
|
if wiring_report is not None:
|
|
|
|
|
setattr(App, STATE_WIRING_IMPORT_REPORT, wiring_report)
|
|
|
|
|
if WiringImport is not None:
|
|
|
|
|
try:
|
|
|
|
|
style_report = WiringImport.apply_wire_styles_from_payload(
|
|
|
|
|
payload,
|
|
|
|
|
App.ActiveDocument,
|
|
|
|
|
)
|
|
|
|
|
_append_debug_log(
|
|
|
|
|
"initial routed wire style apply: matched={0}, view_updates={1}".format(
|
|
|
|
|
style_report.get("routed_wires_matched", 0),
|
|
|
|
|
style_report.get("updated_view", 0),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
_append_debug_log("initial routed wire style apply failed: {0}".format(exc))
|
|
|
|
|
_append_debug_log(traceback.format_exc())
|
|
|
|
|
_log_document_state(
|
|
|
|
|
"scheduled device import after wiring import",
|
|
|
|
|
App.ActiveDocument,
|
|
|
|
|
@ -1768,6 +1954,7 @@ def bootstrap_if_requested():
|
|
|
|
|
)
|
|
|
|
|
setattr(App, STATE_PAYLOAD, payload)
|
|
|
|
|
setattr(App, STATE_SUMMARY, summary)
|
|
|
|
|
_install_exchange_signal_timer(json_path)
|
|
|
|
|
if not getattr(App, STATE_IMPORT_SCHEDULED, False):
|
|
|
|
|
setattr(App, STATE_IMPORT_SCHEDULED, True)
|
|
|
|
|
_append_debug_log(
|
|
|
|
|
|