from __future__ import annotations import sys import tempfile from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent if str(PROJECT_ROOT) not in sys.path: sys.path.insert(0, str(PROJECT_ROOT)) from step_editor.scdm_backend import ScdmBackendInfo, save_scdm_backend_cache # noqa: E402 from step_editor.scdm_status import cached_scdm_backend_payload, summarize_scdm_capability_progress, summarize_scdm_runtime # noqa: E402 def _assert(condition: bool, message: str) -> None: if not condition: raise AssertionError(message) def main() -> int: empty = summarize_scdm_runtime(cache_state="empty") _assert(empty.get("backendReady") is False, f"empty backend should not be ready: {empty}") _assert("未配置" in str(empty.get("headline")), f"empty headline should be clear: {empty}") _assert("导入 STEP" in str(empty.get("detail")), f"empty detail should explain next step: {empty}") backend = ScdmBackendInfo( path=Path("D:/softwaresInstallDir/ANSYS Inc/v222/SCDM/SpaceClaim.exe"), source="common:D:/softwaresInstallDir/ANSYS Inc", version="v222", verified_at="2026-08-18T00:00:00Z", run_script_ok=True, license_ok=True, ) feature_cache = { "objects": [ {"objectId": "face:1", "capabilities": [{"key": "face.offset"}]}, {"objectId": "hole:1", "capabilities": [{"key": "hole.diameter"}, {"key": "hole.position"}]}, ] } ready = summarize_scdm_runtime(backend=backend, cache_state="ready", feature_cache=feature_cache) _assert(ready.get("backendReady") is True, f"backend should be ready: {ready}") _assert("已配置 v222" in str(ready.get("headline")), f"version should be visible: {ready}") _assert("常见安装目录" in str(ready.get("headline")), f"source should be product text: {ready}") _assert(ready.get("objectCount") == 2 and ready.get("capabilityCount") == 3, f"cache counts should be summarized: {ready}") _assert("识别缓存已就绪" in str(ready.get("detail")), f"ready detail should be explicit: {ready}") _assert("/RunScript:可用" in str(ready.get("tooltip")), f"tooltip should include /RunScript status: {ready}") running = summarize_scdm_runtime(backend=backend.to_cache(), cache_state="running") _assert("正在后台识别" in str(running.get("detail")), f"running state should explain background probe: {running}") failed = summarize_scdm_runtime(backend=backend.to_cache(), cache_state="failed", cache_message="SpaceClaim.exe was not found.") _assert("识别未启用" in str(failed.get("detail")), f"failed state should be clear: {failed}") _assert("已有能力" in str(failed.get("detail")), f"failed state should explain fallback: {failed}") disabled = summarize_scdm_runtime(backend={"disabled": True}, cache_state="ready", feature_cache=feature_cache) _assert(disabled.get("backendReady") is False, f"disabled backend should not be ready: {disabled}") _assert("已关闭" in str(disabled.get("headline")), f"disabled state should be clear: {disabled}") _assert("不会启动" in str(disabled.get("detail")), f"disabled detail should explain behavior: {disabled}") stale = summarize_scdm_runtime(backend=backend.to_cache(), cache_state="stale", cache_message="模型已重新加载,SCDM cache 已失效。") _assert("失效" in str(stale.get("detail")), f"stale state should be visible: {stale}") progress_cache = { "objects": [ { "objectId": "face:1", "capabilities": [{"key": "face.offset", "displayName": "偏移"}], }, { "objectId": "hole:1", "capabilities": [ {"key": "hole.diameter", "displayName": "直径", "blockReason": "SCDM 当前脚本环境缺少 OffsetFaces 命令。"}, {"key": "hole.position", "displayName": "位置"}, {"key": "feature.fill", "displayName": "填孔/删除小特征"}, ], }, ], "diagnostics": { "face_adjacency": [ {"bodyIndex": 0, "faceOrdinals": [1, 2], "edgeCount": 1}, {"bodyIndex": 0, "faceOrdinals": [2, 3], "edgeCount": 2}, ], "edge_geometry_summary": { "totalEdgeCount": 12, "edgeKindCounts": {"linear": 8, "circular": 4}, "circularEdgeCount": 4, "circularRadiusBuckets": [{"radius": "0.25", "count": 4}], }, "feature_inventory": { "objectTypeCounts": {"face": 4, "hole": 2, "edge": 12, "slot": 1}, "surfaceTypeCounts": {"plane": 4, "cylinder": 3}, "operationCounts": {"pull_face_offset": 4, "change_hole_diameter": 2, "change_slot_width": 1}, }, "geometry_candidate_hints": [ { "capabilityKey": "boss.height", "displayName": "凸台高度", "evidenceCount": 3, "confidence": "low", }, { "capabilityKey": "pattern.instance_position", "displayName": "阵列实例位置", "evidenceCount": 2, "confidence": "low", }, ], "derived_feature_candidates": [ { "objectId": "derived:linear_pattern:hole-a|hole-b|hole-c", "objectType": "linear_pattern", "geometrySignature": {"spacing": 5.0, "instanceCount": 3}, } ], "planned_not_productized": [ {"capabilityKey": "shell.thickness", "objectType": "shell"}, ], "discovered_not_productized": [ {"objectType": "mystery_feature"}, {"objectType": "mystery_feature"}, ], }, } progress = summarize_scdm_capability_progress( feature_cache=progress_cache, execution_ready={ "face.offset", "hole.diameter", "hole.position", "feature.fill", "slot.width", "slot.depth", "slot.position", "boss.diameter", "boss.height", "boss.position", "round.radius", "chamfer.distance", "feature.delete_round_or_chamfer", "pattern.spacing", "pattern.segment_spacing", }, ) summary = progress.get("summary") _assert(isinstance(summary, dict), f"capability progress should include summary: {progress}") _assert(summary.get("productized") == 15, f"productized capability count should include S5 plus slot dimensions, boss dimensions, round/chamfer dimensions, pattern spacing, local segment spacing, Move-based S7 entries and round/chamfer delete: {summary}") _assert(summary.get("runnerReady") == 15, f"runner-ready capability count should honor UI gate: {summary}") _assert(summary.get("executableCapabilities") == 3, f"blocked/ungated capabilities should not be executable: {summary}") _assert(summary.get("plannedDetected") == 1, f"planned S7 detections should be counted: {summary}") _assert(summary.get("discoveredNotProductized") == 2, f"unknown discoveries should be counted: {summary}") _assert(summary.get("faceAdjacency") == 2 and summary.get("circularEdges") == 4, f"probe topology evidence should be counted: {summary}") _assert( summary.get("inventoryObjectTypes") == 19 and summary.get("inventoryOperationCandidates") == 7, f"probe feature inventory should be counted: {summary}", ) _assert(summary.get("geometryHints") == 5, f"geometry candidate hints should be counted: {summary}") _assert(summary.get("derivedFeatureCandidates") == 1, f"derived S7 candidate count should be visible: {summary}") productized_lines = "\n".join(str(line) for line in progress.get("productizedLines", [])) planned_lines = "\n".join(str(line) for line in progress.get("plannedLines", [])) evidence_lines = "\n".join(str(line) for line in progress.get("probeEvidence", {}).get("lines", [])) _assert("偏移:已开放" in productized_lines, f"open SCDM capability should be visible: {productized_lines}") _assert("直径:已开放但被后端阻止" in productized_lines, f"blocked SCDM capability should be explicit: {productized_lines}") _assert("填孔/删除小特征:已开放" in productized_lines, f"recognized command capability should be visible as open: {productized_lines}") _assert("槽深:已开放待识别" in productized_lines, f"productized slot.depth should be visible: {productized_lines}") _assert("凸台直径:已开放待识别" in productized_lines, f"productized boss.diameter should be visible: {productized_lines}") _assert("凸台高度:已开放待识别" in productized_lines, f"productized boss.height should be visible: {productized_lines}") _assert("圆角半径:已开放待识别" in productized_lines, f"productized round.radius should be visible: {productized_lines}") _assert("倒角距离:已开放待识别" in productized_lines, f"productized chamfer.distance should be visible: {productized_lines}") _assert("阵列间距:已开放待识别" in productized_lines, f"productized pattern.spacing should be visible: {productized_lines}") _assert("局部间距:已开放待识别" in productized_lines, f"productized pattern.segment_spacing should be visible: {productized_lines}") _assert("壳体厚度:已识别待验证" in planned_lines, f"planned shell.thickness candidate should be visible: {planned_lines}") _assert("阵列实例位置:几何证据待分类" in planned_lines, f"geometry-only S7 hint should be visible: {planned_lines}") _assert("Face 邻接 2 组" in evidence_lines and "圆边 4 条" in evidence_lines, f"probe evidence lines should be readable: {evidence_lines}") _assert("对象分布" in evidence_lines and "命令候选分布" in evidence_lines, f"probe inventory lines should be readable: {evidence_lines}") _assert("几何候选 凸台高度:3" in evidence_lines, f"probe geometry hint lines should be readable: {evidence_lines}") _assert("linear_pattern:1" in evidence_lines, f"derived S7 candidate lines should be readable: {evidence_lines}") with tempfile.TemporaryDirectory(prefix="step_editor_scdm_status_") as temp: root = Path(temp) fake_exe = root / "ANSYS Inc" / "v222" / "SCDM" / "SpaceClaim.exe" fake_exe.parent.mkdir(parents=True, exist_ok=True) fake_exe.write_text("fake", encoding="utf-8") saved_backend = ScdmBackendInfo(path=fake_exe, source="manual", version="v222", run_script_ok=True, license_ok=True) save_scdm_backend_cache(saved_backend, project_root_override=root) cached = cached_scdm_backend_payload(root) _assert(isinstance(cached, dict), f"cached backend should load: {cached}") _assert(str(cached.get("source")) == "manual", f"cached source should be preserved: {cached}") print("scdm status summary ok") return 0 if __name__ == "__main__": raise SystemExit(main())