176 lines
9.7 KiB
Python
176 lines
9.7 KiB
Python
|
|
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.spacing",
|
||
|
|
"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": "slot.width", "objectType": "slot"},
|
||
|
|
{"capabilityKey": "slot.width", "objectType": "slot"},
|
||
|
|
{"capabilityKey": "round.radius", "objectType": "round"},
|
||
|
|
],
|
||
|
|
"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", "slot.position", "boss.position"},
|
||
|
|
)
|
||
|
|
summary = progress.get("summary")
|
||
|
|
_assert(isinstance(summary, dict), f"capability progress should include summary: {progress}")
|
||
|
|
_assert(summary.get("productized") == 6, f"productized capability count should include S5 plus Move-based S7 entries: {summary}")
|
||
|
|
_assert(summary.get("runnerReady") == 5, f"runner-ready capability count should honor UI gate: {summary}")
|
||
|
|
_assert(summary.get("executableCapabilities") == 2, f"blocked/ungated capabilities should not be executable: {summary}")
|
||
|
|
_assert(summary.get("plannedDetected") == 3, 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 but ungated capability should be visible: {productized_lines}")
|
||
|
|
_assert("槽宽:已识别待验证" in planned_lines, f"planned S7 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())
|