feat: 接入 SCDM 优先编辑闭环并支持阵列局部间距

接入 SCDM probe/edit/cache/校验链路,增强孔组、阵列、关系式和参数表交互。

支持阵列相邻段间距、移动意图切换、结果回滚校验,并补充对应回归脚本。
This commit is contained in:
2026-08-19 18:02:47 +08:00
parent a3eb7e2476
commit 4e7877e05c
21 changed files with 5646 additions and 491 deletions
+86 -16
View File
@@ -21,9 +21,9 @@ from PySide6.QtWidgets import (
from .model import StepModel
from .asitus_bridge import run_asitus_hole_recognition
from .records import OperationRecord
from .scdm_feature_mapper import attach_local_face_ids_to_scdm_cache, map_scdm_raw_features
from .scdm_feature_mapper import SCDM_FEATURE_CACHE_REVISION, attach_local_face_ids_to_scdm_cache, map_scdm_raw_features
from .scdm_probe import run_scdm_probe
from .scdm_schema import write_json
from .scdm_schema import default_scdm_work_dir, file_fingerprint, read_json, write_json
from .ui_helpers import * # noqa: F403
from .workers import EditWorker, LoadWorker, ScanWorker
@@ -1308,11 +1308,13 @@ class WindowCoreMixin:
f"Loaded {self.step_path.name}; 大模型已跳过全量边线补绘,旋转会更流畅,切到 Edge 选择时再按需生成。"
)
pending_scdm_reload = isinstance(getattr(self, "pending_scdm_edit_reload", None), dict)
if large_interaction_model and not pending_scdm_reload:
scdm_cache_restored = False if pending_scdm_reload else self._restore_scdm_feature_cache_from_disk()
if large_interaction_model and not pending_scdm_reload and not scdm_cache_restored:
self._defer_large_model_recognition_preloads()
else:
QTimer.singleShot(160, self._start_asitus_hole_recognition_preload)
QTimer.singleShot(240, lambda: self._start_scdm_probe_preload(force=pending_scdm_reload))
if pending_scdm_reload or not scdm_cache_restored:
QTimer.singleShot(240, lambda: self._start_scdm_probe_preload(force=pending_scdm_reload))
def _large_model_interaction_mode(self, stats: object | None = None) -> bool:
if stats is not None:
@@ -1354,9 +1356,84 @@ class WindowCoreMixin:
if hasattr(self, "_update_current_capability_panel"):
self._update_current_capability_panel()
def _install_scdm_feature_cache(self, cache: dict[str, object], *, cache_path: str = "", message: str = "") -> None:
self.scdm_feature_cache = dict(cache)
self.scdm_feature_cache_state = "ready"
self.scdm_feature_cache_message = message or "SCDM 可修改参数识别完成。"
self.scdm_feature_cache_path = str(cache_path or "")
if self.model is not None:
try:
self.model.scdm_feature_cache = dict(cache)
except Exception:
pass
if hasattr(self, "_update_current_capability_panel"):
self._update_current_capability_panel()
if hasattr(self, "_refresh_property_editor"):
self._refresh_property_editor()
def _restore_scdm_feature_cache_from_disk(self) -> bool:
if self.model is None or self.step_path is None:
return False
step_path = Path(self.step_path)
try:
fingerprint = file_fingerprint(step_path)
except OSError:
return False
project_root = Path(__file__).resolve().parent.parent
work_dir = default_scdm_work_dir(step_path, project_root=project_root, fingerprint=fingerprint)
cache_path = work_dir / "scdm_feature_cache.json"
raw_path = work_dir / "scdm_raw_features.json"
cache: dict[str, object] | None = None
if cache_path.is_file():
try:
candidate = read_json(cache_path)
revision = _safe_int_or_none(candidate.get("mapperRevision")) or 0
if str(candidate.get("modelFingerprint") or "") == fingerprint and revision >= SCDM_FEATURE_CACHE_REVISION:
cache = candidate
except Exception:
cache = None
if cache is None and raw_path.is_file():
try:
raw = read_json(raw_path)
raw_model = raw.get("model")
raw_fingerprint = str(raw_model.get("fingerprint") or "") if isinstance(raw_model, dict) else ""
if raw_fingerprint == fingerprint:
cache = attach_local_face_ids_to_scdm_cache(
map_scdm_raw_features(raw),
self._scdm_local_face_signatures(),
)
write_json(cache_path, cache)
except Exception:
cache = None
if cache is None:
return False
self._install_scdm_feature_cache(
cache,
cache_path=str(cache_path),
message="已从本地 SCDM 识别缓存恢复;模型变更后会重新识别。",
)
self.statusBar().showMessage("已恢复本地 SCDM 识别缓存,参数表可直接使用。")
return True
def _current_scdm_feature_cache_matches_loaded_step(self) -> bool:
if self.step_path is None or not isinstance(getattr(self, "scdm_feature_cache", None), dict):
return False
cache = getattr(self, "scdm_feature_cache", None)
try:
fingerprint = file_fingerprint(Path(self.step_path))
except OSError:
return False
revision = _safe_int_or_none(cache.get("mapperRevision")) or 0
return str(cache.get("modelFingerprint") or "") == fingerprint and revision >= SCDM_FEATURE_CACHE_REVISION
def _start_scdm_probe_preload(self, *, force: bool = False) -> None:
if self.model is None or self.step_path is None:
return
if not force and self._current_scdm_feature_cache_matches_loaded_step():
return
if not force and self._large_model_interaction_mode():
self._defer_large_model_recognition_preloads()
return
@@ -1476,21 +1553,14 @@ class WindowCoreMixin:
return
if isinstance(result.get("backend"), dict):
self.scdm_backend_status = dict(result["backend"])
self.scdm_feature_cache = dict(cache)
self.scdm_feature_cache_state = "ready"
self.scdm_feature_cache_message = "SCDM 可修改参数识别完成。"
self.scdm_feature_cache_path = str(result.get("cache_path") or "")
try:
self.model.scdm_feature_cache = dict(cache)
except Exception:
pass
self._install_scdm_feature_cache(
dict(cache),
cache_path=str(result.get("cache_path") or ""),
message="SCDM 可修改参数识别完成。",
)
objects = cache.get("objects")
count = len(objects) if isinstance(objects, list) else 0
self.statusBar().showMessage(f"SCDM 可修改参数识别完成:{count} 个产品化对象。")
if hasattr(self, "_update_current_capability_panel"):
self._update_current_capability_panel()
if hasattr(self, "_refresh_property_editor"):
self._refresh_property_editor()
if hasattr(self, "_finish_pending_scdm_edit_reload"):
self._finish_pending_scdm_edit_reload(cache_ready=True)
finally: