2026-08-19 10:28:09 +08:00
from __future__ import annotations
import ast
import re
import subprocess
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 , load_scdm_backend_cache # noqa: E402
from step_editor.scdm_feature_mapper import attach_local_face_ids_to_scdm_cache , geometry_signature , map_scdm_raw_features , map_scdm_raw_features_file # noqa: E402
from step_editor.scdm_probe import generate_scdm_probe_script , prepare_scdm_probe_job , run_scdm_probe # noqa: E402
from step_editor.scdm_property_specs import property_specs_from_scdm_cache # noqa: E402
from step_editor.scdm_schema import read_json , write_json # noqa: E402
def _assert ( condition : bool , message : str ) -> None :
if not condition :
raise AssertionError ( message )
def _capability_keys ( cache : dict [ str , object ], object_id_part : str ) -> set [ str ]:
objects = cache . get ( "objects" )
_assert ( isinstance ( objects , list ), f "cache objects should be a list: { cache } " )
for item in objects :
if not isinstance ( item , dict ):
continue
if object_id_part not in str ( item . get ( "objectId" ) or "" ):
continue
capabilities = item . get ( "capabilities" )
_assert ( isinstance ( capabilities , list ), f "capabilities should be a list: { item } " )
return { str ( capability . get ( "key" )) for capability in capabilities if isinstance ( capability , dict )}
return set ()
def _script_path_from_command ( command : list [ str ] | tuple [ str , ... ]) -> Path :
for item in command :
if item . startswith ( "/RunScript=" ):
return Path ( item . split ( "=" , 1 )[ 1 ])
raise AssertionError ( f "missing /RunScript argument: { command } " )
def _job_path_from_script ( script_path : Path ) -> Path :
text = script_path . read_text ( encoding = "utf-8" )
match = re . search ( r "^JOB_PATH = (.+)$" , text , flags = re . MULTILINE )
_assert ( match is not None , f "generated probe script should embed JOB_PATH: { script_path } " )
return Path ( ast . literal_eval ( match . group ( 1 )))
def _successful_probe_runner ( command , ** _kwargs ): # type: ignore[no-untyped-def]
script_path = _script_path_from_command ( command )
job_path = _job_path_from_script ( script_path )
job = read_json ( job_path )
outputs = job . get ( "outputs" )
_assert ( isinstance ( outputs , dict ), f "job outputs should be an object: { job } " )
write_json (
outputs [ "rawFeatures" ],
{
"schemaVersion" : 1 ,
"backend" : job . get ( "backend" , {}),
"model" : job . get ( "model" , {}),
"diagnostics" : { "availableCommands" : [{ "name" : "OffsetFaces" , "available" : True }]},
"objects" : [],
},
)
return subprocess . CompletedProcess ( command , 0 , stdout = "ok" , stderr = "" )
def main () -> int :
with tempfile . TemporaryDirectory ( prefix = "step_editor_scdm_probe_" ) as temp :
root = Path ( temp )
step_path = root / "sample.step"
step_path . write_text ( "ISO-10303-21; \n END-ISO-10303-21; \n " , encoding = "utf-8" )
backend = ScdmBackendInfo ( path = root / "SpaceClaim.exe" , source = "test" , version = "v222" )
backend . path . write_text ( "fake" , encoding = "utf-8" )
prepared = prepare_scdm_probe_job ( step_path , output_dir = root / "probe" , project_root = root , backend = backend )
_assert ( prepared . get ( "ok" ) is True , f "probe job should be prepared: { prepared } " )
job_path = Path ( str ( prepared [ "job_path" ]))
script_path = Path ( str ( prepared [ "script_path" ]))
raw_path = Path ( str ( prepared [ "raw_features_path" ]))
_assert ( job_path . is_file (), "scdm_probe_job.json should be written" )
_assert ( script_path . is_file (), "scdm_probe.py should be written" )
job = read_json ( job_path )
_assert ( job . get ( "adapter" ) == "spaceclaim-v1" , f "bad adapter: { job } " )
_assert ( job . get ( "outputs" , {}) . get ( "rawFeatures" ) == str ( raw_path ), f "bad raw output path: { job } " )
script = script_path . read_text ( encoding = "utf-8" )
_assert ( "GetRootPart" in script , "probe script should inspect the active root part" )
_assert ( "GetHoleFaces" in script , "probe script should ask SCDM for standard hole faces" )
_assert ( "StandardHoles" in script and "FindStandardHoleOptions" in script and "getattr(standard_holes, 'Find'" in script , "probe script should try SCDM StandardHoles.Find before geometric fallback" )
_assert ( "availableCommands" in script and "ConstantRound" in script and "Chamfer" in script , "probe script should report SCDM command availability" )
_assert ( "RoundInfo" in script and "_round_info_from_face" in script and "change_round_radius" in script , "probe script should collect SCDM round diagnostics" )
2026-08-19 18:02:47 +08:00
_assert ( "ChamferInfo" in script and "_chamfer_info_from_face" in script and "change_chamfer_distance" in script , "probe script should collect SCDM chamfer diagnostics" )
_assert ( "SlotInfo" in script and "_slot_info_from_face" in script and "change_slot_depth" in script , "probe script should collect SCDM slot diagnostics" )
2026-08-19 10:28:09 +08:00
_assert ( "backendCommandCandidates" in script , "probe script should write raw command candidates" )
for token in ( "_geometry_from_edge" , "Length" , "StartPoint" , "EndPoint" , "adjacentFaceOrdinals" ):
_assert ( token in script , f "probe script should enrich Edge raw geometry with { token } " )
for token in ( "_record_face_adjacency" , "_face_adjacency_rows" , "edgeGeometrySummary" , "_final_edge_geometry_summary" , "_feature_inventory" ):
_assert ( token in script , f "probe script should summarize SCDM topology evidence with { token } " )
2026-08-19 18:02:47 +08:00
for token in ( "_component_entries" , "_component_body_locator_map" , "componentInstances" , "componentLocators" ):
_assert ( token in script , f "probe script should preserve SCDM component occurrence evidence with { token } " )
2026-08-19 10:28:09 +08:00
generated = generate_scdm_probe_script ( job_path )
_assert ( "JOB_PATH =" in generated and job_path . name in generated , "generated probe script should embed the job path" )
probe_result = run_scdm_probe ( step_path , backend = backend , output_dir = root / "run-probe" , project_root = root , runner = _successful_probe_runner )
_assert ( probe_result . get ( "ok" ) is True , f "fake run_scdm_probe should pass: { probe_result } " )
probe_backend = probe_result . get ( "backend" )
_assert ( isinstance ( probe_backend , dict ), f "probe result should carry backend status: { probe_result } " )
_assert ( probe_backend . get ( "runScriptOk" ) is True , f "successful probe should mark /RunScript usable: { probe_backend } " )
_assert ( probe_backend . get ( "licenseOk" ) is True , f "successful probe should mark license usable: { probe_backend } " )
cached_backend = load_scdm_backend_cache ( project_root_override = root )
_assert ( cached_backend is not None and cached_backend . run_script_ok is True , f "successful probe should update backend cache: { cached_backend } " )
raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : {
"availableCommands" : [
{ "name" : "StandardHoles" , "available" : True },
{ "name" : "OffsetFaces" , "available" : True },
{ "name" : "Move" , "available" : True },
{ "name" : "Fill" , "available" : True },
{ "name" : "Delete" , "available" : True },
2026-08-19 18:02:47 +08:00
{ "name" : "Chamfer" , "available" : True },
2026-08-19 10:28:09 +08:00
{ "name" : "ConstantRound" , "available" : True },
2026-08-19 18:02:47 +08:00
{ "name" : "ChamferInfo" , "available" : True },
{ "name" : "SlotInfo" , "available" : True },
2026-08-19 10:28:09 +08:00
{ "name" : "SomeFutureCommand" , "available" : False },
],
"faceAdjacency" : [
{
"bodyIndex" : 0 ,
"faceOrdinals" : [ 1 , 2 ],
"edgeCount" : 1 ,
"edgeKinds" : { "circular" : 1 },
"edges" : [{ "edgeOrdinal" : 7 , "globalEdgeOrdinal" : 9 , "kind" : "circular" , "radius" : 0.25 }],
}
],
"edgeGeometrySummary" : {
"totalEdgeCount" : 9 ,
"edgeKindCounts" : { "linear" : 5 , "circular" : 4 },
"circularEdgeCount" : 4 ,
"circularRadiusBuckets" : [{ "radius" : "0.25" , "count" : 4 }],
"minEdgeLength" : 0.5 ,
"maxEdgeLength" : 8.0 ,
},
"featureInventory" : {
2026-08-19 18:02:47 +08:00
"objectTypeCounts" : { "face" : 2 , "hole" : 1 , "edge" : 1 , "slot" : 1 , "round" : 1 , "chamfer" : 1 },
2026-08-19 10:28:09 +08:00
"surfaceTypeCounts" : { "plane" : 1 , "cylinder" : 3 },
"curveTypeCounts" : { "Line" : 5 , "Circle" : 4 },
"operationCounts" : {
"pull_face_offset" : 1 ,
"change_hole_diameter" : 1 ,
"change_slot_width" : 1 ,
2026-08-19 18:02:47 +08:00
"change_slot_depth" : 1 ,
2026-08-19 10:28:09 +08:00
"move_slot" : 1 ,
"change_boss_height" : 1 ,
"move_boss" : 1 ,
2026-08-19 18:02:47 +08:00
"change_chamfer_distance" : 1 ,
2026-08-19 10:28:09 +08:00
},
},
2026-08-19 18:02:47 +08:00
"componentInstances" : [
{
"backendId" : "component:0" ,
"componentIndex" : 0 ,
"componentPath" : [ 0 ],
"componentName" : "零件 3" ,
"contentBodyCount" : 1 ,
"placementTranslation" : [ 0.0 , 20.0 , 0.0 ],
}
],
2026-08-19 10:28:09 +08:00
},
"summary" : { "bodyCount" : 1 , "objectCount" : 10 , "faceCount" : 6 , "edgeCount" : 1 , "holeFaceCount" : 0 },
"objects" : [
{
"backendId" : "hole:1" ,
"objectType" : "hole" ,
"geometry" : {
"diameter" : 0.5 ,
"center" : [ 0.5 , 1.0 , 9.5 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
},
"topologyHint" : { "faceIds" : [ 85 , 94 ], "bodyIndex" : 0 , "faceOrdinal" : 12 },
"backendCommandCandidates" : [
{ "operation" : "change_hole_diameter" , "enabled" : True , "parameterFields" : { "diameter" : 0.5 }},
{ "operation" : "move_hole_axis" , "enabled" : True , "parameterFields" : { "center" : [ 0.5 , 1.0 , 9.5 ]}},
],
"rawLimitations" : [],
},
{
"backendId" : "hole:pattern-a" ,
"objectType" : "hole" ,
"geometry" : {
"diameter" : 0.5 ,
"center" : [ 0.0 , 0.0 , 0.0 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
},
"topologyHint" : { "faceIds" : [ 200 ], "bodyIndex" : 2 , "faceOrdinal" : 1 },
"backendCommandCandidates" : [
{ "operation" : "change_hole_diameter" , "enabled" : True , "parameterFields" : { "diameter" : 0.5 }},
{ "operation" : "move_hole_axis" , "enabled" : True , "parameterFields" : { "center" : [ 0.0 , 0.0 , 0.0 ]}},
],
"rawLimitations" : [],
},
{
"backendId" : "hole:pattern-b" ,
"objectType" : "hole" ,
"geometry" : {
"diameter" : 0.5 ,
"center" : [ 5.0 , 0.0 , 0.0 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
},
"topologyHint" : { "faceIds" : [ 201 ], "bodyIndex" : 2 , "faceOrdinal" : 2 },
"backendCommandCandidates" : [
{ "operation" : "change_hole_diameter" , "enabled" : True , "parameterFields" : { "diameter" : 0.5 }},
{ "operation" : "move_hole_axis" , "enabled" : True , "parameterFields" : { "center" : [ 5.0 , 0.0 , 0.0 ]}},
],
"rawLimitations" : [],
},
{
"backendId" : "hole:pattern-c" ,
"objectType" : "hole" ,
"geometry" : {
"diameter" : 0.5 ,
"center" : [ 10.0 , 0.0 , 0.0 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
},
"topologyHint" : { "faceIds" : [ 202 ], "bodyIndex" : 2 , "faceOrdinal" : 3 },
"backendCommandCandidates" : [
{ "operation" : "change_hole_diameter" , "enabled" : True , "parameterFields" : { "diameter" : 0.5 }},
{ "operation" : "move_hole_axis" , "enabled" : True , "parameterFields" : { "center" : [ 10.0 , 0.0 , 0.0 ]}},
],
"rawLimitations" : [],
},
{
"backendId" : "face:9" ,
"objectType" : "face" ,
"geometry" : {
"surfaceType" : "plane" ,
"center" : [ 0.0 , 0.0 , 10.0 ],
"normal" : [ 0.0 , 0.0 , 1.0 ],
},
"topologyHint" : { "faceIds" : [ 9 ]},
"backendCommandCandidates" : [
{ "operation" : "pull_face_offset" , "enabled" : True , "parameterFields" : { "distance" : 0.0 }},
],
"rawLimitations" : [],
},
2026-08-19 18:02:47 +08:00
{
"backendId" : "body:2/face:100" ,
"objectType" : "face" ,
"geometry" : {
"surfaceType" : "plane" ,
"center" : [ 0.0 , 0.0 , 0.0 ],
"normal" : [ 0.0 , 0.0 , 1.0 ],
},
"topologyHint" : { "faceIds" : [ 100 ], "bodyIndex" : 2 , "faceOrdinal" : 100 , "globalFaceOrdinal" : 100 },
"backendCommandCandidates" : [
{ "operation" : "pull_face_offset" , "enabled" : True , "parameterFields" : { "distance" : 0.0 }},
],
"rawLimitations" : [],
},
{
"backendId" : "body:2/face:101" ,
"objectType" : "face" ,
"geometry" : {
"surfaceType" : "plane" ,
"center" : [ 0.0 , 0.0 , 1.2 ],
"normal" : [ 0.0 , 0.0 , - 1.0 ],
},
"topologyHint" : { "faceIds" : [ 101 ], "bodyIndex" : 2 , "faceOrdinal" : 101 , "globalFaceOrdinal" : 101 },
"backendCommandCandidates" : [
{ "operation" : "pull_face_offset" , "enabled" : True , "parameterFields" : { "distance" : 0.0 }},
],
"rawLimitations" : [],
},
2026-08-19 10:28:09 +08:00
{
"backendId" : "body:8/face:5" ,
"objectType" : "face" ,
"geometry" : {
"surfaceType" : "cylinder" ,
"center" : [ 0.5 , 1.0 , 9.5 ],
"axis" : [ 0.0 , 1.0 , 0.0 ],
"radius" : 0.25 ,
},
"topologyHint" : { "bodyIndex" : 8 , "faceOrdinal" : 5 , "globalFaceOrdinal" : 85 },
"backendCommandCandidates" : [],
"rawLimitations" : [],
},
{
"backendId" : "body:8/face:16" ,
"objectType" : "face" ,
"geometry" : {
"surfaceType" : "cylinder" ,
"center" : [ 0.5 , 1.0 , 9.5 ],
"axis" : [ 0.0 , 1.0 , 0.0 ],
"radius" : 0.25 ,
},
"topologyHint" : { "bodyIndex" : 8 , "faceOrdinal" : 16 , "globalFaceOrdinal" : 96 },
"backendCommandCandidates" : [],
"rawLimitations" : [],
},
{
"backendId" : "round:1" ,
"objectType" : "round" ,
2026-08-19 18:02:47 +08:00
"geometry" : {
"radius" : 1.0 ,
"roundInfo" : {
"radius" : 1.0 ,
"diameter" : 2.0 ,
"isConstant" : True ,
"isRound" : True ,
"type" : "ConstantRound" ,
},
},
"topologyHint" : { "faceIds" : [ 60 ], "bodyIndex" : 0 , "faceOrdinal" : 60 , "globalFaceOrdinal" : 60 },
2026-08-19 10:28:09 +08:00
"backendCommandCandidates" : [
{ "operation" : "change_round_radius" , "enabled" : True },
{ "operation" : "delete_round_or_chamfer" , "enabled" : True },
],
"rawLimitations" : [],
},
2026-08-20 17:12:01 +08:00
{
"backendId" : "body:0/face:1722" ,
"objectType" : "round" ,
"geometry" : {
"surfaceType" : "cylinder" ,
"radius" : 0.0013 ,
"diameter" : 0.0026 ,
"center" : [ - 0.083 , - 0.031 , - 0.11635 ],
"axis" : [ 0.0 , 1.0 , 0.0 ],
"roundInfo" : { "radius" : 0.0 , "available" : True , "type" : "RoundInfoResult" },
},
"topologyHint" : { "faceIds" : [ 1722 ], "bodyIndex" : 0 , "faceOrdinal" : 1722 , "globalFaceOrdinal" : 1722 },
"backendCommandCandidates" : [
{ "operation" : "change_round_radius" , "enabled" : True },
{ "operation" : "delete_round_or_chamfer" , "enabled" : True },
],
"rawLimitations" : [],
},
{
"backendId" : "body:0/face:1723" ,
"objectType" : "round" ,
"geometry" : {
"surfaceType" : "cylinder" ,
"radius" : 0.0013 ,
"diameter" : 0.0026 ,
"center" : [ - 0.083 , - 0.031 , - 0.11635 ],
"axis" : [ 0.0 , 1.0 , 0.0 ],
"roundInfo" : { "radius" : 0.0 , "available" : True , "type" : "RoundInfoResult" },
},
"topologyHint" : { "faceIds" : [ 1723 ], "bodyIndex" : 0 , "faceOrdinal" : 1723 , "globalFaceOrdinal" : 1723 },
"backendCommandCandidates" : [
{ "operation" : "change_round_radius" , "enabled" : True },
{ "operation" : "delete_round_or_chamfer" , "enabled" : True },
],
"rawLimitations" : [],
},
2026-08-19 10:28:09 +08:00
{
"backendId" : "slot:1" ,
"objectType" : "slot" ,
2026-08-19 18:02:47 +08:00
"geometry" : { "width" : 2.0 , "depth" : 1.5 , "depthAxis" : [ 0.0 , 0.0 , - 1.0 ], "center" : [ 1.0 , 2.0 , 3.0 ]},
"topologyHint" : {
"faceIds" : [ 30 , 31 , 32 ],
"bodyIndex" : 0 ,
"faceOrdinal" : 30 ,
"depthFaceLocators" : [{ "bodyIndex" : 0 , "faceOrdinal" : 32 , "globalFaceOrdinal" : 42 }],
},
2026-08-19 10:28:09 +08:00
"backendCommandCandidates" : [
{ "operation" : "change_slot_width" , "enabled" : True },
2026-08-19 18:02:47 +08:00
{ "operation" : "change_slot_depth" , "enabled" : True },
2026-08-19 10:28:09 +08:00
{ "operation" : "move_slot" , "enabled" : True , "parameterFields" : { "center" : [ 1.0 , 2.0 , 3.0 ]}},
],
"rawLimitations" : [],
},
{
"backendId" : "boss:1" ,
"objectType" : "cylindrical_boss" ,
"geometry" : { "diameter" : 3.0 , "height" : 4.0 , "center" : [ 0.0 , 0.0 , 2.0 ]},
2026-08-19 18:02:47 +08:00
"topologyHint" : {
"faceIds" : [ 50 , 51 , 52 ],
"bodyIndex" : 0 ,
"faceOrdinal" : 50 ,
"heightFaceLocators" : [{ "bodyIndex" : 0 , "faceOrdinal" : 52 , "globalFaceOrdinal" : 72 }],
"diameterFaceLocators" : [{ "bodyIndex" : 0 , "faceOrdinal" : 50 , "globalFaceOrdinal" : 70 }],
},
2026-08-19 10:28:09 +08:00
"backendCommandCandidates" : [
{ "operation" : "change_boss_height" , "enabled" : True },
2026-08-19 18:02:47 +08:00
{ "operation" : "change_boss_diameter" , "enabled" : True },
2026-08-19 10:28:09 +08:00
{ "operation" : "move_boss" , "enabled" : True , "parameterFields" : { "center" : [ 0.0 , 0.0 , 2.0 ]}},
],
"rawLimitations" : [],
},
{
"backendId" : "chamfer:1" ,
"objectType" : "chamfer" ,
2026-08-19 18:02:47 +08:00
"geometry" : {
"distance" : 0.8 ,
"chamferInfo" : {
"distance" : 0.8 ,
"distance1" : 0.8 ,
"distance2" : 0.8 ,
"isEqualDistance" : True ,
"isChamfer" : True ,
"type" : "EqualDistanceChamfer" ,
},
},
"topologyHint" : { "faceIds" : [ 63 ], "bodyIndex" : 0 , "faceOrdinal" : 63 , "globalFaceOrdinal" : 63 },
2026-08-19 10:28:09 +08:00
"backendCommandCandidates" : [{ "operation" : "change_chamfer_distance" , "enabled" : True }],
"rawLimitations" : [],
},
{
"backendId" : "pattern:1" ,
"objectType" : "linear_pattern" ,
"geometry" : { "spacing" : 5.0 , "instanceCenter" : [ 0.0 , 5.0 , 0.0 ]},
"backendCommandCandidates" : [{ "operation" : "change_pattern_spacing" , "enabled" : True }],
"rawLimitations" : [],
},
2026-08-19 18:02:47 +08:00
{
"backendId" : "body:20" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 20.0 , 0.0 ], "bboxSize" : [ 1.0 , 2.0 , 3.0 ], "faceCount" : 6 , "edgeCount" : 12 },
"topologyHint" : {
"bodyIndex" : 20 ,
"faceIds" : [ 300 ],
"faceOrdinals" : [ 0 ],
"globalFaceOrdinals" : [ 300 ],
"componentLocators" : [{ "componentIndex" : 0 , "componentPath" : [ 0 ], "componentBodyIndex" : 0 , "bodyIndex" : 20 }],
},
"backendCommandCandidates" : [{ "operation" : "move_body" , "enabled" : True }],
"rawLimitations" : [],
},
{
"backendId" : "body:21" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 25.0 , 0.0 ], "bboxSize" : [ 1.0 , 2.0 , 3.0 ], "faceCount" : 6 , "edgeCount" : 12 },
"topologyHint" : {
"bodyIndex" : 21 ,
"faceIds" : [ 301 ],
"faceOrdinals" : [ 0 ],
"globalFaceOrdinals" : [ 301 ],
"componentLocators" : [{ "componentIndex" : 1 , "componentPath" : [ 1 ], "componentBodyIndex" : 0 , "bodyIndex" : 21 }],
},
"backendCommandCandidates" : [{ "operation" : "move_body" , "enabled" : True }],
"rawLimitations" : [],
},
{
"backendId" : "body:22" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 30.0 , 0.0 ], "bboxSize" : [ 1.0 , 2.0 , 3.0 ], "faceCount" : 6 , "edgeCount" : 12 },
"topologyHint" : {
"bodyIndex" : 22 ,
"faceIds" : [ 302 ],
"faceOrdinals" : [ 0 ],
"globalFaceOrdinals" : [ 302 ],
"componentLocators" : [{ "componentIndex" : 2 , "componentPath" : [ 2 ], "componentBodyIndex" : 0 , "bodyIndex" : 22 }],
},
"backendCommandCandidates" : [{ "operation" : "move_body" , "enabled" : True }],
"rawLimitations" : [],
},
2026-08-19 10:28:09 +08:00
{
"backendId" : "shell:1" ,
"objectType" : "shell" ,
"geometry" : { "thickness" : 1.2 },
"backendCommandCandidates" : [{ "operation" : "change_shell_thickness" , "enabled" : True }],
"rawLimitations" : [],
},
],
}
cache = map_scdm_raw_features ( raw )
edge_signature = geometry_signature (
{
"backendId" : "edge:1" ,
"objectType" : "edge" ,
"geometry" : {
"curveType" : "Circle" ,
"length" : 3.14 ,
"startPoint" : [ 0.0 , 0.0 , 0.0 ],
"endPoint" : [ 1.0 , 0.0 , 0.0 ],
"midPoint" : [ 0.5 , 0.0 , 0.0 ],
"radius" : 0.5 ,
"center" : [ 0.5 , 0.5 , 0.0 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
},
"topologyHint" : {
"bodyIndex" : 0 ,
"edgeOrdinal" : 7 ,
"globalEdgeOrdinal" : 9 ,
"adjacentFaceCount" : 2 ,
"adjacentFaceOrdinals" : [ 3 , 4 ],
},
}
)
_assert ( edge_signature . get ( "curveType" ) == "Circle" , f "Edge curve type should be preserved: { edge_signature } " )
_assert ( edge_signature . get ( "length" ) == 3.14 , f "Edge length should be preserved: { edge_signature } " )
_assert ( edge_signature . get ( "startPoint" ) == [ 0.0 , 0.0 , 0.0 ], f "Edge start point should be preserved: { edge_signature } " )
_assert ( edge_signature . get ( "adjacentFaceOrdinals" ) == [ 3 , 4 ], f "Edge adjacent faces should be preserved: { edge_signature } " )
2026-08-19 18:02:47 +08:00
component_signature = geometry_signature (
{
"backendId" : "body:20" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 20.0 , 0.0 ]},
"topologyHint" : {
"bodyIndex" : 20 ,
"bodyLocators" : [{ "bodyIndex" : 20 , "componentIndex" : 0 , "componentPath" : [ 0 ], "componentBodyIndex" : 0 }],
"componentLocators" : [{ "componentIndex" : 0 , "componentPath" : [ 0 ], "componentBodyIndex" : 0 , "bodyIndex" : 20 }],
},
}
)
_assert ( component_signature . get ( "componentLocators" ), f "component locators should be preserved in geometry signatures: { component_signature } " )
2026-08-19 10:28:09 +08:00
_assert ( cache . get ( "modelFingerprint" ) == "abc123" , f "model fingerprint should be copied: { cache } " )
2026-08-19 18:02:47 +08:00
_assert ( int ( cache . get ( "mapperRevision" ) or 0 ) >= 2 , f "SCDM cache should carry the mapper revision for disk reuse: { cache } " )
2026-08-19 10:28:09 +08:00
_assert ( cache . get ( "backendVersion" ) == "v222" , f "backend version should be copied: { cache } " )
_assert ({ "hole.diameter" , "hole.position" } <= _capability_keys ( cache , "hole:1" ), f "hole caps missing: { cache } " )
objects = cache . get ( "objects" )
_assert ( isinstance ( objects , list ), f "cache objects should be a list: { cache } " )
hole_object = next (( item for item in objects if isinstance ( item , dict ) and item . get ( "objectId" ) == "hole:hole:1" ), None )
_assert ( isinstance ( hole_object , dict ), f "hole object should be normalized: { cache } " )
signature = hole_object . get ( "geometrySignature" )
_assert ( isinstance ( signature , dict ), f "hole signature should be present: { hole_object } " )
_assert ( signature . get ( "bodyIndex" ) == 0 and signature . get ( "faceOrdinal" ) == 12 , f "SCDM script locator hints should be preserved: { signature } " )
_assert ({ "face.offset" } <= _capability_keys ( cache , "face:9" ), f "face caps missing: { cache } " )
cylinder_group = next (
( item for item in objects if isinstance ( item , dict ) and "cylindrical_group:body:8/face:5" in str ( item . get ( "objectId" ) or "" )),
None ,
)
_assert ( isinstance ( cylinder_group , dict ), f "split cylinder faces should produce a grouped SCDM object: { cache } " )
cylinder_signature = cylinder_group . get ( "geometrySignature" )
_assert ( isinstance ( cylinder_signature , dict ), f "cylinder group should carry a geometry signature: { cylinder_group } " )
_assert ( cylinder_signature . get ( "faceOrdinals" ) == [ 5 , 16 ], f "cylinder group should preserve all face ordinals: { cylinder_signature } " )
_assert ( len ( cylinder_signature . get ( "scdmFaceLocators" ) or []) == 2 , f "cylinder group should preserve SCDM face locators: { cylinder_signature } " )
_assert ({ "hole.diameter" , "hole.position" , "feature.fill" } <= _capability_keys ( cache , "cylindrical_group:body:8/face:5" ), f "cylinder group caps missing: { cache } " )
hole_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 94 ,), execution_ready = False )
hole_keys = { str ( spec . get ( "scdm_capability_key" )) for spec in hole_specs }
_assert ({ "hole.diameter" , "hole.position" } <= hole_keys , f "SCDM cache should map selected hole Face to UI specs: { hole_specs } " )
_assert ( all ( spec . get ( "enabled" ) is False for spec in hole_specs ), f "SCDM specs should stay disabled before S5: { hole_specs } " )
executable_hole_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 85 ,), execution_ready = True )
_assert ( any ( spec . get ( "enabled" ) is True for spec in executable_hole_specs ), f "SCDM specs should enable once runner is ready: { executable_hole_specs } " )
gated_hole_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 85 ,), execution_ready = { "face.offset" })
_assert ( all ( spec . get ( "enabled" ) is False for spec in gated_hole_specs ), f "capability gate should keep unverified hole edits disabled: { gated_hole_specs } " )
face_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 9 ,), execution_ready = True )
_assert ({ str ( spec . get ( "scdm_capability_key" )) for spec in face_specs } == { "face.offset" }, f "Face cache should map to offset only: { face_specs } " )
gated_face_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 9 ,), execution_ready = { "face.offset" })
_assert ( gated_face_specs and all ( spec . get ( "enabled" ) is True for spec in gated_face_specs ), f "capability gate should enable verified face offset: { gated_face_specs } " )
2026-08-19 18:02:47 +08:00
_assert ({ "slot.width" , "slot.depth" , "slot.position" } <= _capability_keys ( cache , "slot:1" ), f "slot width/depth/position should now be productized: { cache } " )
slot_object = next (( item for item in objects if isinstance ( item , dict ) and item . get ( "objectId" ) == "slot:slot:1" ), None )
_assert ( isinstance ( slot_object , dict ), f "slot object should be normalized: { cache } " )
slot_signature = slot_object . get ( "geometrySignature" )
_assert ( isinstance ( slot_signature , dict ) and slot_signature . get ( "depthFaceLocators" ), f "slot depth locator should be preserved: { slot_signature } " )
_assert ( isinstance ( slot_signature , dict ) and slot_signature . get ( "depthAxis" ) == [ 0.0 , 0.0 , - 1.0 ], f "slot depth axis should be preserved: { slot_signature } " )
slot_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 31 ,), execution_ready = { "slot.width" , "slot.depth" , "slot.position" })
2026-08-19 10:28:09 +08:00
_assert (
2026-08-19 18:02:47 +08:00
{ str ( spec . get ( "scdm_capability_key" )) for spec in slot_specs } == { "slot.width" , "slot.depth" , "slot.position" },
f "slot cache should expose productized slot.width, slot.depth and slot.position: { slot_specs } " ,
2026-08-19 10:28:09 +08:00
)
2026-08-19 18:02:47 +08:00
_assert ( slot_specs and all ( spec . get ( "enabled" ) is True for spec in slot_specs ), f "slot width/depth/position should enable when runner gate is ready: { slot_specs } " )
slot_width_spec = next (( spec for spec in slot_specs if spec . get ( "scdm_capability_key" ) == "slot.width" ), None )
_assert ( slot_width_spec and slot_width_spec . get ( "value_type" ) == "positive" , f "slot.width should use positive numeric input: { slot_specs } " )
slot_depth_spec = next (( spec for spec in slot_specs if spec . get ( "scdm_capability_key" ) == "slot.depth" ), None )
_assert ( slot_depth_spec and slot_depth_spec . get ( "value_type" ) == "positive" , f "slot.depth should use positive numeric input: { slot_specs } " )
2026-08-19 10:28:09 +08:00
blocked_slot_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 31 ,), execution_ready = { "face.offset" })
2026-08-19 18:02:47 +08:00
_assert ( blocked_slot_specs and all ( spec . get ( "enabled" ) is False for spec in blocked_slot_specs ), f "slot capabilities should honor runner gate: { blocked_slot_specs } " )
_assert ({ "boss.diameter" , "boss.height" , "boss.position" } <= _capability_keys ( cache , "boss:1" ), f "boss diameter/height/position should now be productized: { cache } " )
boss_object = next (( item for item in objects if isinstance ( item , dict ) and item . get ( "objectType" ) == "cylindrical_boss" ), None )
_assert ( isinstance ( boss_object , dict ), f "boss object should be normalized: { cache } " )
boss_signature = boss_object . get ( "geometrySignature" )
_assert ( isinstance ( boss_signature , dict ) and boss_signature . get ( "heightFaceLocators" ), f "boss height locator should be preserved: { boss_signature } " )
_assert ( isinstance ( boss_signature , dict ) and boss_signature . get ( "diameterFaceLocators" ), f "boss diameter locator should be preserved: { boss_signature } " )
boss_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 51 ,), execution_ready = { "boss.diameter" , "boss.height" , "boss.position" })
2026-08-19 10:28:09 +08:00
_assert (
2026-08-19 18:02:47 +08:00
{ str ( spec . get ( "scdm_capability_key" )) for spec in boss_specs } == { "boss.diameter" , "boss.height" , "boss.position" },
f "boss cache should expose productized boss.diameter, boss.height and boss.position: { boss_specs } " ,
2026-08-19 10:28:09 +08:00
)
2026-08-19 18:02:47 +08:00
_assert ( boss_specs and all ( spec . get ( "enabled" ) is True for spec in boss_specs ), f "boss diameter/height/position should enable when runner gate is ready: { boss_specs } " )
2026-08-19 10:28:09 +08:00
blocked_boss_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 51 ,), execution_ready = { "slot.position" })
2026-08-19 18:02:47 +08:00
_assert ( blocked_boss_specs and all ( spec . get ( "enabled" ) is False for spec in blocked_boss_specs ), f "boss capabilities should honor runner gate: { blocked_boss_specs } " )
_assert ({ "round.radius" , "feature.delete_round_or_chamfer" } <= _capability_keys ( cache , "round:1" ), f "round radius/delete should now be productized: { cache } " )
round_object = next (( item for item in objects if isinstance ( item , dict ) and item . get ( "objectId" ) == "round:round:1" ), None )
_assert ( isinstance ( round_object , dict ), f "round object should be normalized: { cache } " )
round_signature = round_object . get ( "geometrySignature" )
_assert ( isinstance ( round_signature , dict ) and round_signature . get ( "isConstantRound" ) is True , f "round constant evidence should be preserved: { round_signature } " )
2026-08-20 17:12:01 +08:00
round_split_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 1722 ,), execution_ready = True )
round_split_keys = { str ( spec . get ( "scdm_capability_key" )) for spec in round_split_specs }
round_split_group = next (
(
item
for item in objects
if isinstance ( item , dict )
and item . get ( "objectType" ) == "cylindrical_face_group"
and { 1722 , 1723 } <= set ( item . get ( "geometrySignature" , {}) . get ( "faceIds" , []))
),
None ,
)
_assert (
round_split_group is None ,
f "SCDM round cylinder fragments must not be derived into a cylindrical hole group: { round_split_group } " ,
)
round_split_radius_spec = next (( spec for spec in round_split_specs if spec . get ( "scdm_capability_key" ) == "round.radius" ), None )
round_split_radius_value = float ( str ( round_split_radius_spec . get ( "current_raw" ))) if round_split_radius_spec else None
_assert (
round_split_radius_value is not None and abs ( round_split_radius_value - 0.0013 ) < 1.0e-9 ,
f "SCDM roundInfo radius=0 should fall back to geometric radius: { round_split_specs } " ,
)
_assert (
"hole.diameter" not in round_split_keys and "hole.position" not in round_split_keys ,
f "SCDM round cylinder faces must not be derived into editable hole groups: { round_split_specs } " ,
)
_assert (
"feature.delete_round_or_chamfer" in round_split_keys ,
f "SCDM round cylinder faces should keep their own round/delete capabilities: { round_split_specs } " ,
)
2026-08-19 18:02:47 +08:00
round_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 60 ,), execution_ready = { "round.radius" , "feature.delete_round_or_chamfer" })
_assert (
{ str ( spec . get ( "scdm_capability_key" )) for spec in round_specs } == { "round.radius" , "feature.delete_round_or_chamfer" },
f "round cache should expose productized radius and delete command: { round_specs } " ,
)
round_radius_spec = next (( spec for spec in round_specs if spec . get ( "scdm_capability_key" ) == "round.radius" ), None )
round_delete_spec = next (( spec for spec in round_specs if spec . get ( "scdm_capability_key" ) == "feature.delete_round_or_chamfer" ), None )
_assert ( round_radius_spec and round_radius_spec . get ( "value_type" ) == "positive" and round_radius_spec . get ( "enabled" ) is True , f "round.radius should be executable when runner gate is ready: { round_specs } " )
_assert ( round_delete_spec and round_delete_spec . get ( "value_type" ) == "command" and round_delete_spec . get ( "enabled" ) is True , f "round delete command should be executable when runner gate is ready: { round_specs } " )
_assert ({ "chamfer.distance" } <= _capability_keys ( cache , "chamfer:1" ), f "chamfer distance should now be productized: { cache } " )
chamfer_object = next (( item for item in objects if isinstance ( item , dict ) and item . get ( "objectId" ) == "chamfer:chamfer:1" ), None )
_assert ( isinstance ( chamfer_object , dict ), f "chamfer object should be normalized: { cache } " )
chamfer_signature = chamfer_object . get ( "geometrySignature" )
_assert ( isinstance ( chamfer_signature , dict ) and chamfer_signature . get ( "isEqualDistanceChamfer" ) is True , f "chamfer equal-distance evidence should be preserved: { chamfer_signature } " )
chamfer_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 63 ,), execution_ready = { "chamfer.distance" })
_assert (
{ str ( spec . get ( "scdm_capability_key" )) for spec in chamfer_specs } == { "chamfer.distance" },
f "chamfer cache should expose productized chamfer.distance: { chamfer_specs } " ,
)
_assert ( chamfer_specs and chamfer_specs [ 0 ] . get ( "value_type" ) == "positive" and chamfer_specs [ 0 ] . get ( "enabled" ) is True , f "chamfer.distance should be executable when runner gate is ready: { chamfer_specs } " )
2026-08-19 10:28:09 +08:00
raw_without_local_ids = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"objects" : [
{
"backendId" : "face:no-local-id" ,
"objectType" : "face" ,
"geometry" : {
"surfaceType" : "plane" ,
"center" : [ 0.0 , 0.0 , 0.01 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
"planeOffset" : 0.01 ,
},
"topologyHint" : { "bodyIndex" : 0 , "faceOrdinal" : 3 },
"backendCommandCandidates" : [
{ "operation" : "pull_face_offset" , "enabled" : True , "parameterFields" : { "distance" : 0.0 }},
],
"rawLimitations" : [],
},
],
}
enriched = attach_local_face_ids_to_scdm_cache (
map_scdm_raw_features ( raw_without_local_ids ),
[
{
"faceId" : 9 ,
"surfaceType" : "plane" ,
"center" : [ 0.0 , 0.0 , 10.0 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
"planeOffset" : 10.0 ,
}
],
)
enriched_specs = property_specs_from_scdm_cache ( enriched , selected_face_ids = ( 9 ,), execution_ready = True )
_assert ( enriched_specs and enriched_specs [ 0 ] . get ( "scdm_capability_key" ) == "face.offset" , f "local Face IDs should be attached from geometry signatures: { enriched } " )
enriched_group = attach_local_face_ids_to_scdm_cache (
map_scdm_raw_features ( raw ),
[
{
"faceId" : 85 ,
"surfaceType" : "cylinder" ,
"center" : [ 0.5 , 1.0 , 9.5 ],
"axis" : [ 0.0 , 1.0 , 0.0 ],
"radius" : 0.25 ,
},
{
"faceId" : 96 ,
"surfaceType" : "cylinder" ,
"center" : [ 0.5 , 1.0 , 9.5 ],
"axis" : [ 0.0 , 1.0 , 0.0 ],
"radius" : 0.25 ,
},
],
)
grouped_specs = property_specs_from_scdm_cache ( enriched_group , selected_face_ids = ( 96 ,), execution_ready = True )
grouped_keys = { str ( spec . get ( "scdm_capability_key" )) for spec in grouped_specs }
_assert ({ "hole.diameter" , "hole.position" , "feature.fill" } <= grouped_keys , f "local Face IDs should attach to SCDM cylinder groups: { enriched_group } " )
2026-08-19 18:02:47 +08:00
body_pattern_without_face_ids = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "Move" , "available" : True }]},
"objects" : [
{
"backendId" : "body:20" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 20.0 , 0.0 ], "bboxSize" : [ 1.0 , 2.0 , 3.0 ], "faceCount" : 6 , "edgeCount" : 12 },
"topologyHint" : { "bodyIndex" : 20 , "faceOrdinals" : [ 0 ], "globalFaceOrdinals" : [ 300 ]},
"backendCommandCandidates" : [{ "operation" : "move_body" , "enabled" : True }],
"rawLimitations" : [],
},
{
"backendId" : "body:21" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 25.0 , 0.0 ], "bboxSize" : [ 1.0 , 2.0 , 3.0 ], "faceCount" : 6 , "edgeCount" : 12 },
"topologyHint" : { "bodyIndex" : 21 , "faceOrdinals" : [ 0 ], "globalFaceOrdinals" : [ 301 ]},
"backendCommandCandidates" : [{ "operation" : "move_body" , "enabled" : True }],
"rawLimitations" : [],
},
{
"backendId" : "body:22" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 30.0 , 0.0 ], "bboxSize" : [ 1.0 , 2.0 , 3.0 ], "faceCount" : 6 , "edgeCount" : 12 },
"topologyHint" : { "bodyIndex" : 22 , "faceOrdinals" : [ 0 ], "globalFaceOrdinals" : [ 302 ]},
"backendCommandCandidates" : [{ "operation" : "move_body" , "enabled" : True }],
"rawLimitations" : [],
},
],
}
enriched_body_pattern = attach_local_face_ids_to_scdm_cache (
map_scdm_raw_features ( body_pattern_without_face_ids ),
[
{ "faceId" : 300 , "bodyIndex" : 20 , "faceOrdinal" : 0 , "globalFaceOrdinal" : 300 , "surfaceType" : "plane" },
{ "faceId" : 301 , "bodyIndex" : 21 , "faceOrdinal" : 0 , "globalFaceOrdinal" : 301 , "surfaceType" : "plane" },
{ "faceId" : 302 , "bodyIndex" : 22 , "faceOrdinal" : 0 , "globalFaceOrdinal" : 302 , "surfaceType" : "plane" },
],
)
body_pattern_specs_from_ordinals = property_specs_from_scdm_cache ( enriched_body_pattern , selected_face_ids = ( 301 ,), execution_ready = { "pattern.spacing" })
_assert (
any ( spec . get ( "scdm_capability_key" ) == "pattern.spacing" for spec in body_pattern_specs_from_ordinals ),
f "body pattern spacing should attach local Face IDs from SCDM ordinals: { enriched_body_pattern } " ,
)
unit_scaled_face_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "m" },
"diagnostics" : { "availableCommands" : [{ "name" : "OffsetFaces" , "available" : True }]},
"objects" : [
{
"backendId" : "body:0/face:9" ,
"objectType" : "face" ,
"geometry" : { "surfaceType" : "plane" , "axis" : [ - 1.0 , 0.0 , 0.0 ], "planeOffset" : - 0.00125 },
"topologyHint" : { "bodyIndex" : 0 , "faceOrdinal" : 9 , "globalFaceOrdinal" : 9 },
"backendCommandCandidates" : [{ "operation" : "pull_face_offset" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
unit_scaled_face_cache = attach_local_face_ids_to_scdm_cache (
map_scdm_raw_features ( unit_scaled_face_raw ),
[
{
"faceId" : 9 ,
"bodyIndex" : 0 ,
"faceOrdinal" : 9 ,
"globalFaceOrdinal" : 9 ,
"surfaceType" : "plane" ,
"axis" : [ - 1.0 , 0.0 , 0.0 ],
"planeOffset" : - 1.25 ,
}
],
)
unit_scaled_face_signature = unit_scaled_face_cache [ "objects" ][ 0 ][ "geometrySignature" ] # type: ignore[index]
_assert ( abs ( float ( unit_scaled_face_signature . get ( "localUnitScale" ) or 0.0 ) - 0.001 ) <= 1.0e-12 , f "ordinal-matched Face should infer SCDM/local unit scale: { unit_scaled_face_signature } " )
unit_scaled_face_specs = property_specs_from_scdm_cache ( unit_scaled_face_cache , selected_face_ids = ( 9 ,), execution_ready = True )
_assert (
unit_scaled_face_specs and unit_scaled_face_specs [ 0 ] . get ( "current_text" ) == "-1.25" ,
f "SCDM length values should display in local model units: { unit_scaled_face_specs } " ,
)
unit_scaled_body_pattern_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "m" },
"diagnostics" : { "availableCommands" : [{ "name" : "Move" , "available" : True }]},
"objects" : [
{
"backendId" : "body:20" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 0.020 , 0.0 ], "bboxSize" : [ 0.001 , 0.002 , 0.003 ], "faceCount" : 6 , "edgeCount" : 12 },
"topologyHint" : { "bodyIndex" : 20 , "faceOrdinals" : [ 0 ], "globalFaceOrdinals" : [ 300 ]},
"backendCommandCandidates" : [{ "operation" : "move_body" , "enabled" : True }],
"rawLimitations" : [],
},
{
"backendId" : "body:21" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 0.025 , 0.0 ], "bboxSize" : [ 0.001 , 0.002 , 0.003 ], "faceCount" : 6 , "edgeCount" : 12 },
"topologyHint" : { "bodyIndex" : 21 , "faceOrdinals" : [ 0 ], "globalFaceOrdinals" : [ 301 ]},
"backendCommandCandidates" : [{ "operation" : "move_body" , "enabled" : True }],
"rawLimitations" : [],
},
{
"backendId" : "body:22" ,
"objectType" : "body" ,
"geometry" : { "center" : [ 0.0 , 0.030 , 0.0 ], "bboxSize" : [ 0.001 , 0.002 , 0.003 ], "faceCount" : 6 , "edgeCount" : 12 },
"topologyHint" : { "bodyIndex" : 22 , "faceOrdinals" : [ 0 ], "globalFaceOrdinals" : [ 302 ]},
"backendCommandCandidates" : [{ "operation" : "move_body" , "enabled" : True }],
"rawLimitations" : [],
},
],
}
unit_scaled_body_pattern_cache = attach_local_face_ids_to_scdm_cache (
map_scdm_raw_features ( unit_scaled_body_pattern_raw ),
[
{ "faceId" : 300 , "bodyIndex" : 20 , "faceOrdinal" : 0 , "globalFaceOrdinal" : 300 , "surfaceType" : "plane" , "bboxMin" : [ - 0.5 , 19.0 , 0.0 ], "bboxMax" : [ 0.5 , 21.0 , 1.0 ]},
{ "faceId" : 301 , "bodyIndex" : 21 , "faceOrdinal" : 0 , "globalFaceOrdinal" : 301 , "surfaceType" : "plane" , "bboxMin" : [ - 0.5 , 24.0 , 0.0 ], "bboxMax" : [ 0.5 , 26.0 , 1.0 ]},
{ "faceId" : 302 , "bodyIndex" : 22 , "faceOrdinal" : 0 , "globalFaceOrdinal" : 302 , "surfaceType" : "plane" , "bboxMin" : [ - 0.5 , 29.0 , 0.0 ], "bboxMax" : [ 0.5 , 31.0 , 1.0 ]},
{ "faceId" : 399 , "bodyIndex" : 99 , "faceOrdinal" : 0 , "globalFaceOrdinal" : 399 , "surfaceType" : "plane" , "axis" : [ 0.0 , 0.0 , 1.0 ], "planeOffset" : 0.0 , "bboxMin" : [ - 1.0 , 18.0 , 0.0 ], "bboxMax" : [ 1.0 , 32.0 , 0.0 ], "area" : 28.0 },
],
)
unit_scaled_body_pattern = next (
item for item in unit_scaled_body_pattern_cache [ "objects" ] if item . get ( "objectType" ) == "linear_pattern" # type: ignore[index]
)
unit_scaled_body_signature = unit_scaled_body_pattern [ "geometrySignature" ] # type: ignore[index]
_assert ( abs ( float ( unit_scaled_body_signature . get ( "localUnitScale" ) or 0.0 ) - 0.001 ) <= 1.0e-12 , f "body pattern should infer unit scale from local body centers: { unit_scaled_body_signature } " )
_assert ( unit_scaled_body_signature . get ( "supportFaceIds" ) == [ 399 ], f "body pattern should expose its support Face: { unit_scaled_body_signature } " )
fit = unit_scaled_body_signature . get ( "supportPatternFit" )
_assert ( isinstance ( fit , dict ) and fit . get ( "supportFaceIds" ) == [ 399 ], f "body pattern should record support fit limits: { unit_scaled_body_signature } " )
_assert ( abs ( float ( fit . get ( "maxSpacingLocal" ) or 0.0 ) - 6.0 ) <= 1.0e-9 , f "support fit should limit centered spacing: { fit } " )
unit_scaled_body_specs = property_specs_from_scdm_cache (
{ "objects" : [ unit_scaled_body_pattern ]},
selected_face_ids = ( 399 ,),
execution_ready = { "pattern.spacing" , "pattern.segment_spacing" },
)
_assert (
unit_scaled_body_specs and unit_scaled_body_specs [ 0 ] . get ( "current_text" ) == "5" ,
f "support Face should expose pattern spacing in local units: { unit_scaled_body_specs } " ,
)
_assert (
unit_scaled_body_specs [ 0 ] . get ( "max_value" ) == 6.0 ,
f "support Face pattern spacing should expose a local-unit hard limit: { unit_scaled_body_specs } " ,
)
local_segment_spec = next (
( spec for spec in unit_scaled_body_specs if spec . get ( "scdm_capability_key" ) == "pattern.segment_spacing" ),
None ,
)
_assert (
isinstance ( local_segment_spec , dict )
and str ( local_segment_spec . get ( "label" ) or "" ) . startswith ( "Solid20-Solid21" )
and str ( local_segment_spec . get ( "range_hint" ) or "" ) . startswith ( "固定前项,移动后侧:" )
and "固定 Solid20" in str ( local_segment_spec . get ( "range_hint" ) or "" )
and "平移 Solid21" in str ( local_segment_spec . get ( "range_hint" ) or "" ),
f "local mapped pattern segment labels should use visible Solid IDs and explain motion semantics: { unit_scaled_body_specs } " ,
)
2026-08-19 10:28:09 +08:00
missing_command_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "OffsetFaces" , "available" : False }]},
"objects" : [
{
"backendId" : "face:no-offset-command" ,
"objectType" : "face" ,
"geometry" : {
"surfaceType" : "plane" ,
"center" : [ 0.0 , 0.0 , 1.0 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
"planeOffset" : 1.0 ,
},
"topologyHint" : { "faceIds" : [ 12 ]},
"backendCommandCandidates" : [
{ "operation" : "pull_face_offset" , "enabled" : True , "parameterFields" : { "distance" : 0.0 }},
],
"rawLimitations" : [],
}
],
}
missing_command_specs = property_specs_from_scdm_cache ( map_scdm_raw_features ( missing_command_raw ), selected_face_ids = ( 12 ,), execution_ready = True )
_assert ( missing_command_specs and missing_command_specs [ 0 ] . get ( "enabled" ) is False , f "missing SCDM command should disable capability: { missing_command_specs } " )
_assert ( "OffsetFaces" in str ( missing_command_specs [ 0 ] . get ( "disabled_tip" ) or "" ), f "disabled reason should name the missing SCDM command: { missing_command_specs } " )
2026-08-20 17:12:01 +08:00
delete_only_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "Delete" , "available" : True }, { "name" : "Fill" , "available" : False }]},
"objects" : [
{
"backendId" : "round:delete-only" ,
"objectType" : "round" ,
"geometry" : { "roundInfo" : { "radius" : 1.0 , "isConstant" : True , "isRound" : True }},
"topologyHint" : { "faceIds" : [ 48 ], "bodyIndex" : 0 , "faceOrdinal" : 48 , "globalFaceOrdinal" : 48 },
"backendCommandCandidates" : [{ "operation" : "delete_round_or_chamfer" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
delete_only_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( delete_only_raw ),
selected_face_ids = ( 48 ,),
execution_ready = { "feature.delete_round_or_chamfer" },
)
delete_only_spec = next (( spec for spec in delete_only_specs if spec . get ( "scdm_capability_key" ) == "feature.delete_round_or_chamfer" ), None )
_assert ( delete_only_spec and delete_only_spec . get ( "enabled" ) is True , f "Delete-only SCDM should enable round/chamfer removal: { delete_only_specs } " )
shell_without_locator_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "Move" , "available" : True }]},
"objects" : [
{
"backendId" : "shell:local-face-ids-only" ,
"objectType" : "shell" ,
"geometry" : { "thickness" : 1.2 , "thicknessAxis" : [ 0.0 , 0.0 , 1.0 ]},
"topologyHint" : { "faceIds" : [ 149 , 150 ]},
"backendCommandCandidates" : [{ "operation" : "change_shell_thickness" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
shell_without_locator_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( shell_without_locator_raw ),
selected_face_ids = ( 149 ,),
execution_ready = { "shell.thickness" },
)
shell_without_locator_spec = next (( spec for spec in shell_without_locator_specs if spec . get ( "scdm_capability_key" ) == "shell.thickness" ), None )
_assert (
shell_without_locator_spec and shell_without_locator_spec . get ( "enabled" ) is False ,
f "shell.thickness should not execute when only local Face IDs are known: { shell_without_locator_specs } " ,
)
_assert ( "两侧墙面" in str ( shell_without_locator_spec . get ( "disabled_tip" ) or "" ), f "shell.thickness disabled reason should explain missing wall locators: { shell_without_locator_specs } " )
2026-08-19 18:02:47 +08:00
missing_current_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "Move" , "available" : True }]},
"objects" : [
{
"backendId" : "slot:missing-center" ,
"objectType" : "slot" ,
"geometry" : { "width" : 2.0 },
"topologyHint" : { "faceIds" : [ 44 ]},
"backendCommandCandidates" : [{ "operation" : "move_slot" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
missing_current_specs = property_specs_from_scdm_cache ( map_scdm_raw_features ( missing_current_raw ), selected_face_ids = ( 44 ,), execution_ready = { "slot.width" , "slot.position" })
slot_position_missing_current = next (( spec for spec in missing_current_specs if spec . get ( "scdm_capability_key" ) == "slot.position" ), None )
_assert ( slot_position_missing_current and slot_position_missing_current . get ( "enabled" ) is False , f "missing current value should disable slot.position: { missing_current_specs } " )
_assert ( "没有返回可用于编辑的当前值" in str ( slot_position_missing_current . get ( "disabled_tip" ) or "" ), f "missing current value should be explained: { missing_current_specs } " )
slot_missing_depth_locator_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "Move" , "available" : True }]},
"objects" : [
{
"backendId" : "slot:missing-depth-locator" ,
"objectType" : "slot" ,
"geometry" : { "width" : 2.0 , "depth" : 1.5 , "depthAxis" : [ 0.0 , 0.0 , - 1.0 ], "center" : [ 1.0 , 2.0 , 3.0 ]},
"topologyHint" : { "faceIds" : [ 45 ], "bodyIndex" : 0 , "faceOrdinal" : 45 },
"backendCommandCandidates" : [{ "operation" : "change_slot_depth" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
slot_missing_depth_locator_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( slot_missing_depth_locator_raw ),
selected_face_ids = ( 45 ,),
execution_ready = { "slot.depth" },
)
slot_depth_without_locator = next (( spec for spec in slot_missing_depth_locator_specs if spec . get ( "scdm_capability_key" ) == "slot.depth" ), None )
_assert ( slot_depth_without_locator and slot_depth_without_locator . get ( "enabled" ) is False , f "slot.depth should require a bottom face locator: { slot_missing_depth_locator_specs } " )
_assert ( "槽底面定位信息" in str ( slot_depth_without_locator . get ( "disabled_tip" ) or "" ), f "slot.depth missing locator should be explained: { slot_missing_depth_locator_specs } " )
slot_missing_depth_axis_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "Move" , "available" : True }]},
"objects" : [
{
"backendId" : "slot:missing-depth-axis" ,
"objectType" : "slot" ,
"geometry" : { "width" : 2.0 , "depth" : 1.5 , "center" : [ 1.0 , 2.0 , 3.0 ]},
"topologyHint" : { "faceIds" : [ 46 ], "bodyIndex" : 0 , "faceOrdinal" : 46 , "depthFaceLocators" : [{ "bodyIndex" : 0 , "faceOrdinal" : 46 }]},
"backendCommandCandidates" : [{ "operation" : "change_slot_depth" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
slot_missing_depth_axis_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( slot_missing_depth_axis_raw ),
selected_face_ids = ( 46 ,),
execution_ready = { "slot.depth" },
)
slot_depth_without_axis = next (( spec for spec in slot_missing_depth_axis_specs if spec . get ( "scdm_capability_key" ) == "slot.depth" ), None )
_assert ( slot_depth_without_axis and slot_depth_without_axis . get ( "enabled" ) is False , f "slot.depth should require a depth axis: { slot_missing_depth_axis_specs } " )
_assert ( "槽深方向" in str ( slot_depth_without_axis . get ( "disabled_tip" ) or "" ), f "slot.depth missing axis should be explained: { slot_missing_depth_axis_specs } " )
boss_missing_height_locator_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "Move" , "available" : True }]},
"objects" : [
{
"backendId" : "boss:missing-height-locator" ,
"objectType" : "cylindrical_boss" ,
"geometry" : { "diameter" : 3.0 , "height" : 4.0 , "center" : [ 0.0 , 0.0 , 2.0 ]},
"topologyHint" : { "faceIds" : [ 55 ], "bodyIndex" : 0 , "faceOrdinal" : 55 },
"backendCommandCandidates" : [{ "operation" : "change_boss_height" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
boss_missing_height_locator_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( boss_missing_height_locator_raw ),
selected_face_ids = ( 55 ,),
execution_ready = { "boss.height" },
)
boss_height_without_locator = next (( spec for spec in boss_missing_height_locator_specs if spec . get ( "scdm_capability_key" ) == "boss.height" ), None )
_assert ( boss_height_without_locator and boss_height_without_locator . get ( "enabled" ) is False , f "boss.height should require a top face locator: { boss_missing_height_locator_specs } " )
_assert ( "顶面定位信息" in str ( boss_height_without_locator . get ( "disabled_tip" ) or "" ), f "boss.height missing locator should be explained: { boss_missing_height_locator_specs } " )
boss_missing_diameter_locator_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "OffsetFaces" , "available" : True }]},
"objects" : [
{
"backendId" : "boss:missing-diameter-locator" ,
"objectType" : "cylindrical_boss" ,
"geometry" : { "diameter" : 3.0 , "height" : 4.0 , "center" : [ 0.0 , 0.0 , 2.0 ]},
"topologyHint" : { "faceIds" : [ 56 ], "bodyIndex" : 0 , "faceOrdinal" : 56 },
"backendCommandCandidates" : [{ "operation" : "change_boss_diameter" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
boss_missing_diameter_locator_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( boss_missing_diameter_locator_raw ),
selected_face_ids = ( 56 ,),
execution_ready = { "boss.diameter" },
)
boss_diameter_without_locator = next (( spec for spec in boss_missing_diameter_locator_specs if spec . get ( "scdm_capability_key" ) == "boss.diameter" ), None )
_assert ( boss_diameter_without_locator and boss_diameter_without_locator . get ( "enabled" ) is False , f "boss.diameter should require a side face locator: { boss_missing_diameter_locator_specs } " )
_assert ( "侧壁定位信息" in str ( boss_diameter_without_locator . get ( "disabled_tip" ) or "" ), f "boss.diameter missing locator should be explained: { boss_missing_diameter_locator_specs } " )
round_missing_constant_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "ConstantRound" , "available" : True }]},
"objects" : [
{
"backendId" : "round:missing-constant" ,
"objectType" : "round" ,
"geometry" : { "roundInfo" : { "radius" : 1.0 , "isRound" : True }},
"topologyHint" : { "faceIds" : [ 61 ], "bodyIndex" : 0 , "faceOrdinal" : 61 },
"backendCommandCandidates" : [{ "operation" : "change_round_radius" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
round_missing_constant_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( round_missing_constant_raw ),
selected_face_ids = ( 61 ,),
execution_ready = { "round.radius" },
)
round_without_constant = next (( spec for spec in round_missing_constant_specs if spec . get ( "scdm_capability_key" ) == "round.radius" ), None )
_assert ( round_without_constant and round_without_constant . get ( "enabled" ) is False , f "round.radius should require constant-round evidence: { round_missing_constant_specs } " )
_assert ( "等半径圆角证据" in str ( round_without_constant . get ( "disabled_tip" ) or "" ), f "round.radius missing constant evidence should be explained: { round_missing_constant_specs } " )
round_missing_locator_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "ConstantRound" , "available" : True }]},
"objects" : [
{
"backendId" : "round:missing-locator" ,
"objectType" : "round" ,
"geometry" : { "roundInfo" : { "radius" : 1.0 , "isConstant" : True , "isRound" : True }},
"topologyHint" : { "faceIds" : [ 62 ]},
"backendCommandCandidates" : [{ "operation" : "change_round_radius" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
round_missing_locator_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( round_missing_locator_raw ),
selected_face_ids = ( 62 ,),
execution_ready = { "round.radius" },
)
round_without_locator = next (( spec for spec in round_missing_locator_specs if spec . get ( "scdm_capability_key" ) == "round.radius" ), None )
_assert ( round_without_locator and round_without_locator . get ( "enabled" ) is False , f "round.radius should require SCDM face locator evidence: { round_missing_locator_specs } " )
_assert ( "可定位的圆角面" in str ( round_without_locator . get ( "disabled_tip" ) or "" ), f "round.radius missing locator should be explained: { round_missing_locator_specs } " )
chamfer_missing_equal_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "Chamfer" , "available" : True }]},
"objects" : [
{
"backendId" : "chamfer:missing-equal" ,
"objectType" : "chamfer" ,
"geometry" : { "chamferInfo" : { "distance" : 0.8 , "isChamfer" : True }},
"topologyHint" : { "faceIds" : [ 64 ], "bodyIndex" : 0 , "faceOrdinal" : 64 },
"backendCommandCandidates" : [{ "operation" : "change_chamfer_distance" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
chamfer_missing_equal_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( chamfer_missing_equal_raw ),
selected_face_ids = ( 64 ,),
execution_ready = { "chamfer.distance" },
)
chamfer_without_equal = next (( spec for spec in chamfer_missing_equal_specs if spec . get ( "scdm_capability_key" ) == "chamfer.distance" ), None )
_assert ( chamfer_without_equal and chamfer_without_equal . get ( "enabled" ) is False , f "chamfer.distance should require equal-distance evidence: { chamfer_missing_equal_specs } " )
_assert ( "等距倒角证据" in str ( chamfer_without_equal . get ( "disabled_tip" ) or "" ), f "chamfer.distance missing equal evidence should be explained: { chamfer_missing_equal_specs } " )
chamfer_missing_locator_raw = {
"schemaVersion" : 1 ,
"backend" : { "name" : "SCDM" , "version" : "v222" },
"model" : { "path" : str ( step_path ), "fingerprint" : "abc123" , "unit" : "mm" },
"diagnostics" : { "availableCommands" : [{ "name" : "Chamfer" , "available" : True }]},
"objects" : [
{
"backendId" : "chamfer:missing-locator" ,
"objectType" : "chamfer" ,
"geometry" : { "chamferInfo" : { "distance" : 0.8 , "isEqualDistance" : True , "isChamfer" : True }},
"topologyHint" : { "faceIds" : [ 65 ]},
"backendCommandCandidates" : [{ "operation" : "change_chamfer_distance" , "enabled" : True }],
"rawLimitations" : [],
}
],
}
chamfer_missing_locator_specs = property_specs_from_scdm_cache (
map_scdm_raw_features ( chamfer_missing_locator_raw ),
selected_face_ids = ( 65 ,),
execution_ready = { "chamfer.distance" },
)
chamfer_without_locator = next (( spec for spec in chamfer_missing_locator_specs if spec . get ( "scdm_capability_key" ) == "chamfer.distance" ), None )
_assert ( chamfer_without_locator and chamfer_without_locator . get ( "enabled" ) is False , f "chamfer.distance should require SCDM face locator evidence: { chamfer_missing_locator_specs } " )
_assert ( "可定位的倒角面" in str ( chamfer_without_locator . get ( "disabled_tip" ) or "" ), f "chamfer.distance missing locator should be explained: { chamfer_missing_locator_specs } " )
2026-08-19 10:28:09 +08:00
diagnostics = cache . get ( "diagnostics" )
_assert ( isinstance ( diagnostics , dict ), f "cache diagnostics should be present: { cache } " )
raw_summary = diagnostics . get ( "raw_summary" )
_assert ( isinstance ( raw_summary , dict ), f "SCDM raw summary should be preserved in cache diagnostics: { cache } " )
_assert ( raw_summary . get ( "faceCount" ) is None or isinstance ( raw_summary . get ( "faceCount" ), int ), f "raw summary should stay JSON-like: { raw_summary } " )
backend_commands = diagnostics . get ( "backend_commands" )
_assert ( isinstance ( backend_commands , list ), f "SCDM backend command inventory should be preserved: { cache } " )
command_names = { str ( item . get ( "name" )) for item in backend_commands if isinstance ( item , dict )}
_assert ({ "StandardHoles" , "ConstantRound" } <= command_names , f "backend command names should be available in diagnostics: { backend_commands } " )
face_adjacency = diagnostics . get ( "face_adjacency" )
_assert ( isinstance ( face_adjacency , list ) and face_adjacency , f "SCDM Face adjacency should be preserved in cache diagnostics: { cache } " )
edge_summary = diagnostics . get ( "edge_geometry_summary" )
_assert ( isinstance ( edge_summary , dict ) and edge_summary . get ( "circularEdgeCount" ) == 4 , f "SCDM Edge geometry summary should be preserved: { cache } " )
feature_inventory = diagnostics . get ( "feature_inventory" )
_assert (
isinstance ( feature_inventory , dict )
and feature_inventory . get ( "objectTypeCounts" , {}) . get ( "hole" ) == 1
and feature_inventory . get ( "operationCounts" , {}) . get ( "change_slot_width" ) == 1 ,
f "SCDM feature inventory should be preserved: { cache } " ,
)
2026-08-19 18:02:47 +08:00
component_instances = diagnostics . get ( "component_instances" )
_assert (
isinstance ( component_instances , list )
and component_instances
and component_instances [ 0 ] . get ( "componentPath" ) == [ 0 ],
f "SCDM component occurrence diagnostics should be preserved: { cache } " ,
)
2026-08-19 10:28:09 +08:00
geometry_hints = diagnostics . get ( "geometry_candidate_hints" )
2026-08-20 17:12:01 +08:00
_assert ( isinstance ( geometry_hints , list ), f "SCDM structural candidate hints should be preserved as a list: { cache } " )
2026-08-19 10:28:09 +08:00
hint_keys = { str ( item . get ( "capabilityKey" )) for item in geometry_hints if isinstance ( item , dict )}
_assert (
2026-08-20 17:12:01 +08:00
"pattern.instance_position" not in hint_keys and "shell.thickness" not in hint_keys and "slot.depth" not in hint_keys and "pattern.spacing" not in hint_keys ,
2026-08-19 10:28:09 +08:00
f "S7 geometry hints should cover planned feature families: { hint_keys } " ,
)
derived_candidates = diagnostics . get ( "derived_feature_candidates" )
_assert ( isinstance ( derived_candidates , list ) and derived_candidates , f "repeated holes should produce derived S7 candidates: { cache } " )
pattern_candidate = next (( item for item in derived_candidates if isinstance ( item , dict ) and item . get ( "objectType" ) == "linear_pattern" ), None )
_assert ( isinstance ( pattern_candidate , dict ), f "linear pattern candidate should be derived: { derived_candidates } " )
pattern_signature = pattern_candidate . get ( "geometrySignature" )
_assert ( isinstance ( pattern_signature , dict ), f "linear pattern should keep a geometry signature: { pattern_candidate } " )
_assert ( pattern_signature . get ( "spacing" ) == 5.0 , f "linear pattern spacing should be preserved: { pattern_signature } " )
_assert ( pattern_signature . get ( "instanceCount" ) == 3 , f "linear pattern count should be preserved: { pattern_signature } " )
_assert ( pattern_signature . get ( "axis" ) == [ 1.0 , 0.0 , 0.0 ], f "linear pattern axis should be preserved: { pattern_signature } " )
_assert ( len ( pattern_signature . get ( "instanceCenters" ) or []) == 3 , f "linear pattern centers should be preserved: { pattern_signature } " )
2026-08-19 18:02:47 +08:00
_assert ( len ( pattern_signature . get ( "patternInstances" ) or []) == 3 , f "linear pattern instance locators should be preserved: { pattern_signature } " )
_assert ({ "pattern.spacing" } <= _capability_keys ( cache , "derived:linear_pattern" ), f "derived linear pattern should expose productized spacing: { cache } " )
thin_wall_candidate = next (( item for item in derived_candidates if isinstance ( item , dict ) and item . get ( "objectType" ) == "thin_wall" ), None )
_assert ( isinstance ( thin_wall_candidate , dict ), f "paired planar faces should produce a thin-wall candidate: { derived_candidates } " )
thin_wall_signature = thin_wall_candidate . get ( "geometrySignature" )
_assert ( isinstance ( thin_wall_signature , dict ), f "thin-wall candidate should keep a geometry signature: { thin_wall_candidate } " )
_assert ( thin_wall_signature . get ( "thickness" ) == 1.2 , f "thin-wall thickness should be preserved: { thin_wall_signature } " )
_assert ( thin_wall_signature . get ( "thicknessAxis" ) == [ 0.0 , 0.0 , 1.0 ], f "thin-wall axis should be preserved: { thin_wall_signature } " )
_assert ( len ( thin_wall_signature . get ( "wallFaceLocators" ) or []) == 2 , f "thin-wall face locators should be preserved: { thin_wall_signature } " )
2026-08-20 17:12:01 +08:00
_assert ({ "shell.thickness" } <= _capability_keys ( cache , "derived:thin_wall" ), f "derived thin wall should expose productized shell thickness: { cache } " )
thin_wall_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 101 ,), execution_ready = { "shell.thickness" })
thin_wall_thickness_spec = next (( spec for spec in thin_wall_specs if spec . get ( "scdm_capability_key" ) == "shell.thickness" ), None )
_assert ( thin_wall_thickness_spec and thin_wall_thickness_spec . get ( "enabled" ) is True , f "thin-wall thickness should enable when both wall faces are locatable: { thin_wall_specs } " )
_assert ( thin_wall_thickness_spec . get ( "scope_text" ) == "固定一侧,移动另一侧" , f "thin-wall thickness intent should be explicit: { thin_wall_specs } " )
2026-08-19 18:02:47 +08:00
body_pattern_candidate = next (
(
item
for item in derived_candidates
if isinstance ( item , dict )
and item . get ( "objectType" ) == "linear_pattern"
and isinstance ( item . get ( "geometrySignature" ), dict )
and item . get ( "geometrySignature" , {}) . get ( "patternKind" ) == "body"
),
None ,
)
_assert ( isinstance ( body_pattern_candidate , dict ), f "repeated bodies should produce a body linear-pattern candidate: { derived_candidates } " )
body_pattern_signature = body_pattern_candidate . get ( "geometrySignature" )
_assert ( isinstance ( body_pattern_signature , dict ), f "body pattern should keep a geometry signature: { body_pattern_candidate } " )
_assert ( body_pattern_signature . get ( "spacing" ) == 5.0 , f "body pattern spacing should be preserved: { body_pattern_signature } " )
_assert ( body_pattern_signature . get ( "axis" ) == [ 0.0 , 1.0 , 0.0 ], f "body pattern axis should be preserved: { body_pattern_signature } " )
_assert ( body_pattern_signature . get ( "bodyIndices" ) == [ 20 , 21 , 22 ], f "body pattern body indices should be preserved: { body_pattern_signature } " )
body_instances = body_pattern_signature . get ( "patternInstances" ) or []
_assert ( len ( body_instances ) == 3 and all ( item . get ( "instanceKind" ) == "body" for item in body_instances ), f "body pattern instances should stay body-level: { body_pattern_signature } " )
_assert ( all ( item . get ( "componentLocators" ) for item in body_instances if isinstance ( item , dict )), f "body pattern instances should preserve component occurrence locators: { body_pattern_signature } " )
2026-08-20 17:12:01 +08:00
body_pattern_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 301 ,), execution_ready = { "pattern.spacing" , "pattern.segment_spacing" , "pattern.instance_position" })
2026-08-19 18:02:47 +08:00
body_pattern_spacing = [
spec for spec in body_pattern_specs if spec . get ( "scdm_capability_key" ) == "pattern.spacing"
]
_assert ( body_pattern_spacing , f "selecting a body member face should keep body pattern spacing as a recognized capability: { body_pattern_specs } " )
_assert (
body_pattern_spacing [ 0 ] . get ( "enabled" ) is True ,
f "body pattern spacing should be executable when every member has a component occurrence locator: { body_pattern_spacing } " ,
)
body_segment_specs = [
spec for spec in body_pattern_specs if spec . get ( "scdm_capability_key" ) == "pattern.segment_spacing"
]
_assert ( len ( body_segment_specs ) == 2 , f "three body pattern members should expose two local spacing segments: { body_pattern_specs } " )
_assert (
2026-08-20 17:12:01 +08:00
"Solid" in str ( body_segment_specs [ 0 ] . get ( "label" ) or "" )
2026-08-19 18:02:47 +08:00
and body_segment_specs [ 0 ] . get ( "enabled" ) is True
and body_segment_specs [ 0 ] . get ( "scdm_backend_operation" ) == "change_pattern_segment_spacing" ,
f "local body pattern spacing should be executable and name the adjacent members: { body_segment_specs } " ,
)
body_segment_modes = body_segment_specs [ 0 ] . get ( "scope_modes" )
_assert (
isinstance ( body_segment_modes , dict )
2026-08-20 17:12:01 +08:00
and { "fix_left_move_right" , "fix_right_move_left" , "split_keep_center" , "move_single_left" , "move_single_right" } <= set ( body_segment_modes ),
2026-08-19 18:02:47 +08:00
f "local pattern spacing should expose several explicit modeling intents: { body_segment_specs } " ,
)
_assert (
body_segment_modes [ "fix_left_move_right" ] . get ( "enabled" ) is True
and body_segment_modes [ "fix_right_move_left" ] . get ( "enabled" ) is True
and body_segment_modes [ "split_keep_center" ] . get ( "enabled" ) is True
2026-08-20 17:12:01 +08:00
and body_segment_modes [ "move_single_left" ] . get ( "enabled" ) is True
and body_segment_modes [ "move_single_right" ] . get ( "enabled" ) is True ,
f "implemented local spacing intents should be explicit: { body_segment_modes } " ,
2026-08-19 18:02:47 +08:00
)
for mode in body_segment_modes . values ():
if not isinstance ( mode , dict ):
continue
label = str ( mode . get ( "label" ) or "" ) . strip ()
tip = str ( mode . get ( "enabled_tip" ) or mode . get ( "disabled_tip" ) or mode . get ( "range_hint" ) or "" ) . strip ()
_assert ( label and tip . startswith ( label ), f "modeling-intent tooltip should start with its intent label: { body_segment_modes } " )
2026-08-20 17:12:01 +08:00
_assert (
"不用于保持整列等距" in str ( body_segment_modes [ "move_single_right" ] . get ( "range_hint" ) or "" )
and "不用于保持整列等距" in str ( body_segment_modes [ "move_single_left" ] . get ( "range_hint" ) or "" ),
f "single-instance local spacing intents should warn that they break equal spacing: { body_segment_modes } " ,
)
2026-08-19 18:02:47 +08:00
_assert (
"固定前项" in str ( body_segment_specs [ 0 ] . get ( "scope_text" ) or "" )
and "移动后侧" in str ( body_segment_specs [ 0 ] . get ( "scope_text" ) or "" ),
f "local pattern spacing should expose motion semantics in the intent column: { body_segment_specs } " ,
)
body_segment_signature = body_segment_specs [ 0 ] . get ( "scdm_geometry_signature" )
_assert (
isinstance ( body_segment_signature , dict )
and body_segment_signature . get ( "segmentIndex" ) == 0
and body_segment_signature . get ( "movingSide" ) == "after" ,
f "local segment specs should carry segment execution metadata: { body_segment_specs } " ,
)
2026-08-20 17:12:01 +08:00
body_instance_position = next (( spec for spec in body_pattern_specs if spec . get ( "scdm_capability_key" ) == "pattern.instance_position" ), None )
_assert (
body_instance_position
and body_instance_position . get ( "enabled" ) is True
and body_instance_position . get ( "value_type" ) == "vector3"
and body_instance_position . get ( "scdm_backend_operation" ) == "move_pattern_instance" ,
f "body pattern member should expose a single-instance position edit: { body_pattern_specs } " ,
)
_assert (
"只移动该实例" in str ( body_instance_position . get ( "scope_text" ) or "" )
and "不自动保持整体阵列等距" in str ( body_instance_position . get ( "range_hint" ) or "" ),
f "pattern instance position should explain that it is not a spacing edit: { body_instance_position } " ,
)
solid_body_pattern_specs = property_specs_from_scdm_cache (
cache ,
selected_solid_ids = ( 21 ,),
execution_ready = { "pattern.spacing" , "pattern.segment_spacing" , "pattern.instance_position" , "face.offset" },
)
solid_pattern_keys = [ str ( spec . get ( "scdm_capability_key" ) or "" ) for spec in solid_body_pattern_specs ]
_assert (
"pattern.spacing" in solid_pattern_keys
and "pattern.segment_spacing" in solid_pattern_keys
and "pattern.instance_position" in solid_pattern_keys ,
f "selecting a Solid member should expose body-level pattern capabilities: { solid_body_pattern_specs } " ,
)
_assert (
"face.offset" not in solid_pattern_keys ,
f "selecting a Solid should not expand child Face offsets from the SCDM cache: { solid_body_pattern_specs } " ,
)
solid_instance_specs = [
spec for spec in solid_body_pattern_specs if spec . get ( "scdm_capability_key" ) == "pattern.instance_position"
]
_assert (
len ( solid_instance_specs ) == 1
and str ( solid_instance_specs [ 0 ] . get ( "label" ) or "" ) . startswith ( "Solid21" ),
f "Solid selection should expose only the selected member position row: { solid_body_pattern_specs } " ,
)
pattern_specs = property_specs_from_scdm_cache ( cache , selected_face_ids = ( 201 ,), execution_ready = { "pattern.spacing" , "pattern.segment_spacing" , "pattern.instance_position" })
2026-08-19 18:02:47 +08:00
pattern_spacing_spec = next (( spec for spec in pattern_specs if spec . get ( "scdm_capability_key" ) == "pattern.spacing" ), None )
_assert ( pattern_spacing_spec and pattern_spacing_spec . get ( "enabled" ) is True , f "pattern.spacing should enable for selected pattern member: { pattern_specs } " )
_assert ( pattern_spacing_spec . get ( "value_type" ) == "positive" , f "pattern.spacing should use positive numeric input: { pattern_specs } " )
face_segment_specs = [
spec for spec in pattern_specs if spec . get ( "scdm_capability_key" ) == "pattern.segment_spacing"
]
_assert ( len ( face_segment_specs ) == 2 , f "three face pattern members should expose two local spacing segments: { pattern_specs } " )
_assert (
str ( face_segment_specs [ 0 ] . get ( "label" ) or "" ) . startswith ( "Face" ),
f "face pattern local spacing should name the adjacent Face members instead of raw ordinal numbers: { face_segment_specs } " ,
)
2026-08-20 17:12:01 +08:00
face_instance_position = next (( spec for spec in pattern_specs if spec . get ( "scdm_capability_key" ) == "pattern.instance_position" ), None )
_assert (
face_instance_position
and face_instance_position . get ( "enabled" ) is True
and str ( face_instance_position . get ( "label" ) or "" ) . startswith ( "Face" ),
f "face pattern member should expose its own position edit with visible Face ID: { pattern_specs } " ,
)
2026-08-19 10:28:09 +08:00
not_productized = diagnostics . get ( "discovered_not_productized" )
2026-08-19 18:02:47 +08:00
_assert ( isinstance ( not_productized , list ) and not_productized , f "non-productized candidates should stay diagnostic only: { cache } " )
2026-08-19 10:28:09 +08:00
planned = diagnostics . get ( "planned_not_productized" )
_assert ( isinstance ( planned , list ), f "planned diagnostics should be present: { cache } " )
planned_keys = { str ( item . get ( "capabilityKey" )) for item in planned if isinstance ( item , dict )}
2026-08-20 17:12:01 +08:00
_assert ( "pattern.instance_position" not in planned_keys , f "pattern.instance_position should now be productized with member-level specs: { planned_keys } " )
_assert ( "shell.thickness" not in planned_keys , f "shell.thickness should now be productized with strict geometry gates: { planned_keys } " )
2026-08-19 10:28:09 +08:00
raw_file = root / "raw.json"
cache_file = root / "cache.json"
write_json ( raw_file , raw )
from_file = map_scdm_raw_features_file ( raw_file , cache_file )
_assert ( cache_file . is_file (), "cache file should be written" )
_assert ( from_file . get ( "objects" ) == read_json ( cache_file ) . get ( "objects" ), "file mapper should match in-memory mapper" )
print ( "scdm probe pipeline ok" )
return 0
if __name__ == "__main__" :
raise SystemExit ( main ())