2026-08-19 10:28:09 +08:00
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" ,
},
{
2026-08-19 18:02:47 +08:00
"capabilityKey" : "pattern.instance_position" ,
"displayName" : "阵列实例位置" ,
2026-08-19 10:28:09 +08:00
"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 },
}
],
2026-08-20 17:12:01 +08:00
"planned_not_productized" : [],
2026-08-19 10:28:09 +08:00
"discovered_not_productized" : [
{ "objectType" : "mystery_feature" },
{ "objectType" : "mystery_feature" },
],
},
}
progress = summarize_scdm_capability_progress (
feature_cache = progress_cache ,
2026-08-19 18:02:47 +08:00
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" ,
2026-08-20 17:12:01 +08:00
"pattern.instance_position" ,
"shell.thickness" ,
2026-08-19 18:02:47 +08:00
},
2026-08-19 10:28:09 +08:00
)
summary = progress . get ( "summary" )
_assert ( isinstance ( summary , dict ), f "capability progress should include summary: { progress } " )
2026-08-20 17:12:01 +08:00
_assert ( summary . get ( "productized" ) == 17 , f "productized capability count should include S5 plus slot dimensions, boss dimensions, round/chamfer dimensions, pattern spacing, local segment spacing, instance position, shell thickness, Move-based S7 entries and round/chamfer delete: { summary } " )
_assert ( summary . get ( "runnerReady" ) == 17 , f "runner-ready capability count should honor UI gate: { summary } " )
2026-08-19 18:02:47 +08:00
_assert ( summary . get ( "executableCapabilities" ) == 3 , f "blocked/ungated capabilities should not be executable: { summary } " )
2026-08-20 17:12:01 +08:00
_assert ( summary . get ( "plannedDetected" ) == 0 , f "planned S7 detections should be counted: { summary } " )
2026-08-19 10:28:09 +08:00
_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 } " )
2026-08-19 18:02:47 +08:00
_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 } " )
2026-08-20 17:12:01 +08:00
_assert ( "阵列实例位置:已开放待识别" in productized_lines , f "productized pattern.instance_position should be visible: { productized_lines } " )
_assert ( "壳体厚度:已开放待识别" in productized_lines , f "productized shell.thickness should be visible: { productized_lines } " )
_assert ( "阵列实例位置" not in planned_lines , f "pattern.instance_position should no longer be a planned-only line: { planned_lines } " )
2026-08-19 10:28:09 +08:00
_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 ())