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 # noqa: E402
from step_editor.scdm_edit_runner import generate_scdm_edit_script , prepare_scdm_edit_job , run_scdm_edit_job # 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 _fake_spaceclaim ( path : Path ) -> Path :
path . parent . mkdir ( parents = True , exist_ok = True )
path . write_text ( "fake" , encoding = "utf-8" )
return path
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 script should embed JOB_PATH: { script_path } " )
return Path ( ast . literal_eval ( match . group ( 1 )))
def _successful_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 } " )
output_step = Path ( str ( outputs [ "outputStep" ]))
output_step . write_text ( "ISO-10303-21; \n /* fake SCDM output */ \n END-ISO-10303-21; \n " , encoding = "utf-8" )
write_json (
outputs [ "result" ],
{
"ok" : True ,
"reason" : "ok" ,
"message" : "fake edit finished" ,
"outputStep" : str ( output_step ),
"backendOperation" : job . get ( "target" , {}) . get ( "backendOperation" ),
},
)
return subprocess . CompletedProcess ( command , 0 , stdout = "ok" , stderr = "" )
def _failure_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 [ "error" ],
{
"ok" : False ,
"reason" : "fake-failed" ,
"message" : "fake SCDM command failed" ,
"backendOperation" : job . get ( "target" , {}) . get ( "backendOperation" ),
},
)
return subprocess . CompletedProcess ( command , 7 , stdout = "" , stderr = "fake failure" )
def _missing_output_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 [ "result" ],
{
"ok" : True ,
"reason" : "ok" ,
"message" : "fake success without STEP" ,
"outputStep" : str ( outputs [ "outputStep" ]),
},
)
return subprocess . CompletedProcess ( command , 0 , stdout = "" , stderr = "" )
def _empty_output_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 } " )
Path ( str ( outputs [ "outputStep" ])) . write_text ( "" , encoding = "utf-8" )
write_json (
outputs [ "result" ],
{
"ok" : True ,
"reason" : "ok" ,
"message" : "fake success with empty STEP" ,
"outputStep" : str ( outputs [ "outputStep" ]),
},
)
return subprocess . CompletedProcess ( command , 0 , stdout = "" , stderr = "" )
def main () -> int :
with tempfile . TemporaryDirectory ( prefix = "step_editor_scdm_edit_" ) 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 = _fake_spaceclaim ( root / "SpaceClaim.exe" ), source = "test" , version = "v222" )
signature = {
"objectType" : "hole" ,
"faceIds" : [ 85 , 94 ],
"bodyIndex" : 0 ,
"faceOrdinal" : 12 ,
"faceOrdinals" : [ 12 , 19 ],
"scdmFaceLocators" : [
{ "bodyIndex" : 0 , "faceOrdinal" : 12 , "globalFaceOrdinal" : 85 },
{ "bodyIndex" : 0 , "faceOrdinal" : 19 , "globalFaceOrdinal" : 96 },
],
"center" : [ 0.5 , 1.0 , 9.5 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
}
prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "prepared" ,
backend = backend ,
capability_key = "hole.diameter" ,
target_value = "0.75" ,
object_id = "hole:85-94" ,
object_signature = signature ,
timeout_seconds = 45.0 ,
)
_assert ( prepared . get ( "ok" ) is True , f "edit job should be prepared: { prepared } " )
job_path = Path ( str ( prepared [ "job_path" ]))
script_path = Path ( str ( prepared [ "script_path" ]))
_assert ( job_path . is_file (), "scdm_edit_job.json should be written" )
_assert ( script_path . is_file (), "scdm_edit.py should be written" )
job = read_json ( job_path )
_assert ( job . get ( "adapter" ) == "spaceclaim-v1" , f "bad adapter: { job } " )
_assert ( job . get ( "schemaVersion" ) == 1 , f "bad schema version: { job } " )
_assert ( job . get ( "model" , {}) . get ( "sourceStep" ) == str ( step_path . resolve ( strict = False )), f "bad source path: { job } " )
_assert ( job . get ( "model" , {}) . get ( "rollbackStep" ) == str ( step_path . resolve ( strict = False )), f "bad rollback path: { job } " )
_assert ( job . get ( "target" , {}) . get ( "capabilityKey" ) == "hole.diameter" , f "bad capability: { job } " )
_assert ( job . get ( "target" , {}) . get ( "backendOperation" ) == "change_hole_diameter" , f "bad operation: { job } " )
_assert ( job . get ( "target" , {}) . get ( "value" ) == 0.75 , f "target should be numeric: { job } " )
_assert ( job . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "faceIds" ) == [ 85 , 94 ], f "bad signature: { job } " )
_assert ( job . get ( "execution" , {}) . get ( "isolatedProcess" ) is True , f "job should record isolated execution: { job } " )
script = script_path . read_text ( encoding = "utf-8" )
for token in (
"change_hole_diameter" ,
"move_hole_axis" ,
"move_slot" ,
2026-08-19 18:02:47 +08:00
"change_slot_width" ,
"change_slot_depth" ,
2026-08-19 10:28:09 +08:00
"move_boss" ,
2026-08-19 18:02:47 +08:00
"change_boss_height" ,
"change_boss_diameter" ,
"change_round_radius" ,
"change_chamfer_distance" ,
2026-08-20 17:12:01 +08:00
"change_shell_thickness" ,
2026-08-19 18:02:47 +08:00
"change_pattern_spacing" ,
"change_pattern_segment_spacing" ,
2026-08-20 17:12:01 +08:00
"move_pattern_instance" ,
2026-08-19 18:02:47 +08:00
"segment_before" ,
"segment_split" ,
2026-08-20 17:12:01 +08:00
"segment_single_left" ,
"segment_single_right" ,
"wallFaceLocators" ,
2026-08-19 10:28:09 +08:00
"pull_face_offset" ,
"fill_feature" ,
2026-08-19 18:02:47 +08:00
"delete_round_or_chamfer" ,
2026-08-19 10:28:09 +08:00
"StandardHoles.ModifyHoleRadius" ,
2026-08-19 18:02:47 +08:00
"ConstantRound.ModifyRadius" ,
"Chamfer.ModifyDistance" ,
2026-08-19 10:28:09 +08:00
"OffsetFaces.Execute" ,
"Move.Translate" ,
"MoveOptions" ,
2026-08-19 18:02:47 +08:00
"ConstantRoundOptions" ,
"ChamferOptions" ,
2026-08-19 10:28:09 +08:00
"OffsetFaceOptions" ,
"FillOptions" ,
"FillMode" ,
"Delete.Execute" ,
"DocumentSave" ,
"scdmFaceLocators" ,
2026-08-19 18:02:47 +08:00
"heightFaceLocators" ,
"depthFaceLocators" ,
"diameterFaceLocators" ,
"patternInstances" ,
"bodyLocators" ,
"instanceKind" ,
2026-08-19 10:28:09 +08:00
"result.json" ,
"error.json" ,
):
_assert ( token in script , f "generated edit script missing { token } " )
_assert ( "Pull.Execute" not in script , "generated edit script should use documented OffsetFaces instead of Pull.Execute" )
2026-08-20 17:12:01 +08:00
_assert ( "_delete_selection(selection, fill_errors)" in script , "generated edit script should fall back from Fill to Delete" )
_assert (
"if fill is None: \n raise Exception('capability_not_implemented: Fill command not available')" not in script ,
"Delete-capable SCDM environments must not fail merely because Fill is missing" ,
)
2026-08-19 10:28:09 +08:00
generated = generate_scdm_edit_script ( job_path )
_assert ( "JOB_PATH =" in generated and job_path . name in generated , "generated edit script should embed the job path" )
slot_signature = {
"objectType" : "slot" ,
"faceIds" : [ 30 , 31 , 32 ],
"bodyIndex" : 0 ,
"faceOrdinal" : 30 ,
"faceOrdinals" : [ 30 , 31 , 32 ],
"globalFaceOrdinal" : 40 ,
"globalFaceOrdinals" : [ 40 , 41 , 42 ],
2026-08-19 18:02:47 +08:00
"width" : 2.0 ,
"depth" : 1.5 ,
"depthAxis" : [ 0.0 , 0.0 , - 1.0 ],
"depthFaceLocators" : [{ "bodyIndex" : 0 , "faceOrdinal" : 32 , "globalFaceOrdinal" : 42 }],
2026-08-19 10:28:09 +08:00
"center" : [ 1.0 , 2.0 , 3.0 ],
"axis" : [ 1.0 , 0.0 , 0.0 ],
}
slot_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "slot-position-prepared" ,
backend = backend ,
capability_key = "slot.position" ,
target_value = [ 1.0 , 2.0 , 5.0 ],
object_id = "slot:30-31-32" ,
object_signature = slot_signature ,
)
_assert ( slot_prepared . get ( "ok" ) is True , f "slot.position should be productized and prepare an edit job: { slot_prepared } " )
slot_job = read_json ( slot_prepared [ "job_path" ])
_assert ( slot_job . get ( "target" , {}) . get ( "backendOperation" ) == "move_slot" , f "slot.position should route to move_slot: { slot_job } " )
_assert ( slot_job . get ( "target" , {}) . get ( "value" ) == [ 1.0 , 2.0 , 5.0 ], f "slot.position target should be vector3: { slot_job } " )
_assert ( slot_job . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "faceIds" ) == [ 30 , 31 , 32 ], f "slot faces should be preserved: { slot_job } " )
2026-08-19 18:02:47 +08:00
slot_width_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "slot-width-prepared" ,
backend = backend ,
capability_key = "slot.width" ,
target_value = "2.5" ,
object_id = "slot:30-31-32" ,
object_signature = slot_signature ,
)
_assert ( slot_width_prepared . get ( "ok" ) is True , f "slot.width should be productized and prepare an edit job: { slot_width_prepared } " )
slot_width_job = read_json ( slot_width_prepared [ "job_path" ])
_assert ( slot_width_job . get ( "target" , {}) . get ( "backendOperation" ) == "change_slot_width" , f "slot.width should route to change_slot_width: { slot_width_job } " )
_assert ( slot_width_job . get ( "target" , {}) . get ( "value" ) == 2.5 , f "slot.width target should be numeric: { slot_width_job } " )
_assert ( slot_width_job . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "width" ) == 2.0 , f "slot width signature should be preserved: { slot_width_job } " )
2026-08-20 17:12:01 +08:00
slot_width_noop = prepare_scdm_edit_job (
step_path ,
output_dir = root / "slot-width-noop" ,
backend = backend ,
capability_key = "slot.width" ,
target_value = "2.0" ,
object_id = "slot:30-31-32" ,
object_signature = slot_signature ,
)
_assert (
slot_width_noop . get ( "ok" ) is False and slot_width_noop . get ( "reason" ) == "target-already-current" ,
f "slot.width no-op should be blocked before launching SCDM: { slot_width_noop } " ,
)
2026-08-19 18:02:47 +08:00
slot_depth_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "slot-depth-prepared" ,
backend = backend ,
capability_key = "slot.depth" ,
target_value = "2.0" ,
object_id = "slot:30-31-32" ,
object_signature = slot_signature ,
)
_assert ( slot_depth_prepared . get ( "ok" ) is True , f "slot.depth should be productized and prepare an edit job: { slot_depth_prepared } " )
slot_depth_job = read_json ( slot_depth_prepared [ "job_path" ])
_assert ( slot_depth_job . get ( "target" , {}) . get ( "backendOperation" ) == "change_slot_depth" , f "slot.depth should route to change_slot_depth: { slot_depth_job } " )
_assert ( slot_depth_job . get ( "target" , {}) . get ( "value" ) == 2.0 , f "slot.depth target should be numeric: { slot_depth_job } " )
slot_depth_signature = slot_depth_job . get ( "object" , {}) . get ( "geometrySignature" , {})
_assert ( slot_depth_signature . get ( "depth" ) == 1.5 , f "slot depth signature should be preserved: { slot_depth_job } " )
_assert ( slot_depth_signature . get ( "depthFaceLocators" ), f "slot depth bottom face locator should be preserved: { slot_depth_job } " )
_assert ( slot_depth_signature . get ( "depthAxis" ) == [ 0.0 , 0.0 , - 1.0 ], f "slot depth axis should be preserved: { slot_depth_job } " )
2026-08-20 17:12:01 +08:00
slot_depth_missing_locator_signature = dict ( slot_signature )
slot_depth_missing_locator_signature . pop ( "depthFaceLocators" , None )
slot_depth_missing_locator = prepare_scdm_edit_job (
step_path ,
output_dir = root / "slot-depth-missing-locator" ,
backend = backend ,
capability_key = "slot.depth" ,
target_value = "2.0" ,
object_id = "slot:30-31-32" ,
object_signature = slot_depth_missing_locator_signature ,
)
_assert (
slot_depth_missing_locator . get ( "ok" ) is False
and slot_depth_missing_locator . get ( "reason" ) == "object-signature-missing-depth-face-locator" ,
f "slot.depth should be blocked before SCDM when bottom face locator is missing: { slot_depth_missing_locator } " ,
)
2026-08-19 10:28:09 +08:00
boss_signature = {
"objectType" : "cylindrical_boss" ,
"faceIds" : [ 50 , 51 , 52 ],
"bodyIndex" : 0 ,
"faceOrdinal" : 50 ,
"faceOrdinals" : [ 50 , 51 , 52 ],
"globalFaceOrdinal" : 70 ,
"globalFaceOrdinals" : [ 70 , 71 , 72 ],
2026-08-19 18:02:47 +08:00
"height" : 4.0 ,
"diameter" : 3.0 ,
"heightFaceLocators" : [{ "bodyIndex" : 0 , "faceOrdinal" : 52 , "globalFaceOrdinal" : 72 }],
"diameterFaceLocators" : [{ "bodyIndex" : 0 , "faceOrdinal" : 50 , "globalFaceOrdinal" : 70 }],
2026-08-19 10:28:09 +08:00
"center" : [ 0.0 , 0.0 , 2.0 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
}
boss_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "boss-position-prepared" ,
backend = backend ,
capability_key = "boss.position" ,
target_value = [ 2.0 , 0.0 , 2.0 ],
object_id = "boss:50-51-52" ,
object_signature = boss_signature ,
)
_assert ( boss_prepared . get ( "ok" ) is True , f "boss.position should be productized and prepare an edit job: { boss_prepared } " )
boss_job = read_json ( boss_prepared [ "job_path" ])
_assert ( boss_job . get ( "target" , {}) . get ( "backendOperation" ) == "move_boss" , f "boss.position should route to move_boss: { boss_job } " )
_assert ( boss_job . get ( "target" , {}) . get ( "value" ) == [ 2.0 , 0.0 , 2.0 ], f "boss.position target should be vector3: { boss_job } " )
_assert ( boss_job . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "faceIds" ) == [ 50 , 51 , 52 ], f "boss faces should be preserved: { boss_job } " )
2026-08-19 18:02:47 +08:00
boss_height_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "boss-height-prepared" ,
backend = backend ,
capability_key = "boss.height" ,
target_value = "5.5" ,
object_id = "boss:50-51-52" ,
object_signature = boss_signature ,
)
_assert ( boss_height_prepared . get ( "ok" ) is True , f "boss.height should be productized and prepare an edit job: { boss_height_prepared } " )
boss_height_job = read_json ( boss_height_prepared [ "job_path" ])
_assert ( boss_height_job . get ( "target" , {}) . get ( "backendOperation" ) == "change_boss_height" , f "boss.height should route to change_boss_height: { boss_height_job } " )
_assert ( boss_height_job . get ( "target" , {}) . get ( "value" ) == 5.5 , f "boss.height target should be numeric: { boss_height_job } " )
boss_height_signature = boss_height_job . get ( "object" , {}) . get ( "geometrySignature" , {})
_assert ( boss_height_signature . get ( "height" ) == 4.0 , f "boss height signature should be preserved: { boss_height_job } " )
_assert ( boss_height_signature . get ( "heightFaceLocators" ), f "boss height top face locator should be preserved: { boss_height_job } " )
2026-08-20 17:12:01 +08:00
boss_height_missing_locator_signature = dict ( boss_signature )
boss_height_missing_locator_signature . pop ( "heightFaceLocators" , None )
boss_height_missing_locator = prepare_scdm_edit_job (
step_path ,
output_dir = root / "boss-height-missing-locator" ,
backend = backend ,
capability_key = "boss.height" ,
target_value = "5.5" ,
object_id = "boss:50-51-52" ,
object_signature = boss_height_missing_locator_signature ,
)
_assert (
boss_height_missing_locator . get ( "ok" ) is False
and boss_height_missing_locator . get ( "reason" ) == "object-signature-missing-height-face-locator" ,
f "boss.height should be blocked before SCDM when top face locator is missing: { boss_height_missing_locator } " ,
)
2026-08-19 18:02:47 +08:00
boss_diameter_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "boss-diameter-prepared" ,
backend = backend ,
capability_key = "boss.diameter" ,
target_value = "4.5" ,
object_id = "boss:50-51-52" ,
object_signature = boss_signature ,
)
_assert ( boss_diameter_prepared . get ( "ok" ) is True , f "boss.diameter should be productized and prepare an edit job: { boss_diameter_prepared } " )
boss_diameter_job = read_json ( boss_diameter_prepared [ "job_path" ])
_assert ( boss_diameter_job . get ( "target" , {}) . get ( "backendOperation" ) == "change_boss_diameter" , f "boss.diameter should route to change_boss_diameter: { boss_diameter_job } " )
_assert ( boss_diameter_job . get ( "target" , {}) . get ( "value" ) == 4.5 , f "boss.diameter target should be numeric: { boss_diameter_job } " )
boss_diameter_signature = boss_diameter_job . get ( "object" , {}) . get ( "geometrySignature" , {})
_assert ( boss_diameter_signature . get ( "diameter" ) == 3.0 , f "boss diameter signature should be preserved: { boss_diameter_job } " )
_assert ( boss_diameter_signature . get ( "diameterFaceLocators" ), f "boss diameter side face locator should be preserved: { boss_diameter_job } " )
2026-08-20 17:12:01 +08:00
boss_diameter_missing_locator_signature = dict ( boss_signature )
boss_diameter_missing_locator_signature . pop ( "diameterFaceLocators" , None )
boss_diameter_missing_locator = prepare_scdm_edit_job (
step_path ,
output_dir = root / "boss-diameter-missing-locator" ,
backend = backend ,
capability_key = "boss.diameter" ,
target_value = "4.5" ,
object_id = "boss:50-51-52" ,
object_signature = boss_diameter_missing_locator_signature ,
)
_assert (
boss_diameter_missing_locator . get ( "ok" ) is False
and boss_diameter_missing_locator . get ( "reason" ) == "object-signature-missing-diameter-face-locator" ,
f "boss.diameter should be blocked before SCDM when side face locator is missing: { boss_diameter_missing_locator } " ,
)
hole_position_noop = prepare_scdm_edit_job (
step_path ,
output_dir = root / "hole-position-noop" ,
backend = backend ,
capability_key = "hole.position" ,
target_value = [ 0.5 , 1.0 , 9.5 ],
object_id = "hole:85-94" ,
object_signature = signature ,
)
_assert (
hole_position_noop . get ( "ok" ) is False and hole_position_noop . get ( "reason" ) == "target-already-current" ,
f "hole.position no-op should be blocked before launching SCDM: { hole_position_noop } " ,
)
boss_height_zero = prepare_scdm_edit_job (
step_path ,
output_dir = root / "boss-height-zero" ,
backend = backend ,
capability_key = "boss.height" ,
target_value = "0" ,
object_id = "boss:50-51-52" ,
object_signature = boss_signature ,
)
_assert (
boss_height_zero . get ( "ok" ) is False and boss_height_zero . get ( "reason" ) == "target-value-illegal" ,
f "positive dimensions should reject zero before launching SCDM: { boss_height_zero } " ,
)
2026-08-19 18:02:47 +08:00
round_signature = {
"objectType" : "round" ,
"faceIds" : [ 60 ],
"bodyIndex" : 0 ,
"faceOrdinal" : 60 ,
"globalFaceOrdinal" : 80 ,
"center" : [ 1.0 , 0.0 , 2.0 ],
"axis" : [ 0.0 , 0.0 , 1.0 ],
"radius" : 0.5 ,
"roundType" : "ConstantRound" ,
"isConstantRound" : True ,
}
round_radius_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "round-radius-prepared" ,
backend = backend ,
capability_key = "round.radius" ,
target_value = "0.75" ,
object_id = "round:60" ,
object_signature = round_signature ,
)
_assert ( round_radius_prepared . get ( "ok" ) is True , f "round.radius should be productized and prepare an edit job: { round_radius_prepared } " )
round_radius_job = read_json ( round_radius_prepared [ "job_path" ])
_assert ( round_radius_job . get ( "target" , {}) . get ( "backendOperation" ) == "change_round_radius" , f "round.radius should route to change_round_radius: { round_radius_job } " )
_assert ( round_radius_job . get ( "target" , {}) . get ( "value" ) == 0.75 , f "round.radius target should be numeric: { round_radius_job } " )
round_radius_signature = round_radius_job . get ( "object" , {}) . get ( "geometrySignature" , {})
_assert ( round_radius_signature . get ( "isConstantRound" ) is True , f "round.radius should preserve constant round evidence: { round_radius_job } " )
_assert ( round_radius_signature . get ( "radius" ) == 0.5 , f "round.radius should preserve current radius: { round_radius_job } " )
round_delete_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "round-delete-prepared" ,
backend = backend ,
capability_key = "feature.delete_round_or_chamfer" ,
target_value = "" ,
object_id = "round:60" ,
object_signature = round_signature ,
)
_assert ( round_delete_prepared . get ( "ok" ) is True , f "round/chamfer delete should be productized and prepare an edit job: { round_delete_prepared } " )
round_delete_job = read_json ( round_delete_prepared [ "job_path" ])
_assert ( round_delete_job . get ( "target" , {}) . get ( "backendOperation" ) == "delete_round_or_chamfer" , f "round delete should route to delete_round_or_chamfer: { round_delete_job } " )
_assert ( round_delete_job . get ( "target" , {}) . get ( "value" ) is True , f "command target should be true: { round_delete_job } " )
chamfer_signature = {
"objectType" : "chamfer" ,
"faceIds" : [ 63 ],
"bodyIndex" : 0 ,
"faceOrdinal" : 63 ,
"globalFaceOrdinal" : 83 ,
"distance" : 0.8 ,
"distance1" : 0.8 ,
"distance2" : 0.8 ,
"chamferType" : "EqualDistanceChamfer" ,
"isEqualDistanceChamfer" : True ,
}
chamfer_distance_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "chamfer-distance-prepared" ,
backend = backend ,
capability_key = "chamfer.distance" ,
target_value = "1.2" ,
object_id = "chamfer:63" ,
object_signature = chamfer_signature ,
)
_assert ( chamfer_distance_prepared . get ( "ok" ) is True , f "chamfer.distance should be productized and prepare an edit job: { chamfer_distance_prepared } " )
chamfer_distance_job = read_json ( chamfer_distance_prepared [ "job_path" ])
_assert ( chamfer_distance_job . get ( "target" , {}) . get ( "backendOperation" ) == "change_chamfer_distance" , f "chamfer.distance should route to change_chamfer_distance: { chamfer_distance_job } " )
_assert ( chamfer_distance_job . get ( "target" , {}) . get ( "value" ) == 1.2 , f "chamfer.distance target should be numeric: { chamfer_distance_job } " )
chamfer_job_signature = chamfer_distance_job . get ( "object" , {}) . get ( "geometrySignature" , {})
_assert ( chamfer_job_signature . get ( "isEqualDistanceChamfer" ) is True , f "chamfer.distance should preserve equal-distance evidence: { chamfer_distance_job } " )
_assert ( chamfer_job_signature . get ( "distance" ) == 0.8 , f "chamfer.distance should preserve current distance: { chamfer_distance_job } " )
2026-08-20 17:12:01 +08:00
shell_signature = {
"objectType" : "thin_wall" ,
"faceIds" : [ 101 , 102 ],
"bodyIndex" : 0 ,
"faceOrdinals" : [ 101 , 102 ],
"globalFaceOrdinals" : [ 201 , 202 ],
"thickness" : 1.2 ,
"thicknessAxis" : [ 0.0 , 0.0 , 1.0 ],
"wallFaceCenters" : [[ 0.0 , 0.0 , 0.0 ], [ 0.0 , 0.0 , 1.2 ]],
"wallFaceLocators" : [
{ "bodyIndex" : 0 , "faceOrdinal" : 101 , "globalFaceOrdinal" : 201 },
{ "bodyIndex" : 0 , "faceOrdinal" : 102 , "globalFaceOrdinal" : 202 },
],
}
shell_thickness_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "shell-thickness-prepared" ,
backend = backend ,
capability_key = "shell.thickness" ,
target_value = "1.6" ,
object_id = "thin_wall:101-102" ,
object_signature = shell_signature ,
)
_assert ( shell_thickness_prepared . get ( "ok" ) is True , f "shell.thickness should be productized and prepare an edit job: { shell_thickness_prepared } " )
shell_thickness_job = read_json ( shell_thickness_prepared [ "job_path" ])
_assert ( shell_thickness_job . get ( "target" , {}) . get ( "backendOperation" ) == "change_shell_thickness" , f "shell.thickness should route to change_shell_thickness: { shell_thickness_job } " )
_assert ( shell_thickness_job . get ( "target" , {}) . get ( "value" ) == 1.6 , f "shell.thickness target should be numeric: { shell_thickness_job } " )
shell_job_signature = shell_thickness_job . get ( "object" , {}) . get ( "geometrySignature" , {})
_assert ( shell_job_signature . get ( "thickness" ) == 1.2 , f "shell thickness signature should be preserved: { shell_thickness_job } " )
_assert ( shell_job_signature . get ( "wallFaceLocators" ), f "shell wall face locators should be preserved: { shell_thickness_job } " )
shell_missing_locator_signature = dict ( shell_signature )
shell_missing_locator_signature . pop ( "wallFaceLocators" , None )
shell_missing_locator_signature . pop ( "faceOrdinals" , None )
shell_missing_locator_signature . pop ( "globalFaceOrdinals" , None )
shell_missing_locator = prepare_scdm_edit_job (
step_path ,
output_dir = root / "shell-thickness-missing-locator" ,
backend = backend ,
capability_key = "shell.thickness" ,
target_value = "1.6" ,
object_id = "thin_wall:101-102" ,
object_signature = shell_missing_locator_signature ,
)
_assert (
shell_missing_locator . get ( "ok" ) is False
and shell_missing_locator . get ( "reason" ) == "object-signature-missing-wall-face-locator" ,
f "shell.thickness should be blocked before SCDM when both wall locators are missing: { shell_missing_locator } " ,
)
2026-08-19 18:02:47 +08:00
pattern_signature = {
"objectType" : "linear_pattern" ,
"faceIds" : [ 85 , 94 , 87 , 96 , 89 , 98 ],
"bodyIndex" : 0 ,
"axis" : [ 1.0 , 0.0 , 0.0 ],
"spacing" : 5.0 ,
"pitch" : 5.0 ,
"instanceCount" : 3 ,
"instanceCenters" : [[ 0.0 , 0.0 , 0.0 ], [ 5.0 , 0.0 , 0.0 ], [ 10.0 , 0.0 , 0.0 ]],
"patternInstances" : [
{
"sourceObjectId" : "hole:a" ,
"center" : [ 0.0 , 0.0 , 0.0 ],
"faceIds" : [ 85 , 94 ],
"scdmFaceLocators" : [
{ "bodyIndex" : 0 , "faceOrdinal" : 12 , "globalFaceOrdinal" : 85 },
{ "bodyIndex" : 0 , "faceOrdinal" : 19 , "globalFaceOrdinal" : 94 },
],
},
{
"sourceObjectId" : "hole:b" ,
"center" : [ 5.0 , 0.0 , 0.0 ],
"faceIds" : [ 87 , 96 ],
"scdmFaceLocators" : [
{ "bodyIndex" : 0 , "faceOrdinal" : 22 , "globalFaceOrdinal" : 87 },
{ "bodyIndex" : 0 , "faceOrdinal" : 29 , "globalFaceOrdinal" : 96 },
],
},
{
"sourceObjectId" : "hole:c" ,
"center" : [ 10.0 , 0.0 , 0.0 ],
"faceIds" : [ 89 , 98 ],
"scdmFaceLocators" : [
{ "bodyIndex" : 0 , "faceOrdinal" : 32 , "globalFaceOrdinal" : 89 },
{ "bodyIndex" : 0 , "faceOrdinal" : 39 , "globalFaceOrdinal" : 98 },
],
},
],
}
pattern_spacing_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "pattern-spacing-prepared" ,
backend = backend ,
capability_key = "pattern.spacing" ,
target_value = "7.5" ,
object_id = "pattern:holes-a-b-c" ,
object_signature = pattern_signature ,
)
_assert ( pattern_spacing_prepared . get ( "ok" ) is True , f "pattern.spacing should be productized and prepare an edit job: { pattern_spacing_prepared } " )
pattern_spacing_job = read_json ( pattern_spacing_prepared [ "job_path" ])
_assert ( pattern_spacing_job . get ( "target" , {}) . get ( "backendOperation" ) == "change_pattern_spacing" , f "pattern.spacing should route to change_pattern_spacing: { pattern_spacing_job } " )
_assert ( pattern_spacing_job . get ( "target" , {}) . get ( "value" ) == 7.5 , f "pattern.spacing target should be numeric: { pattern_spacing_job } " )
pattern_job_signature = pattern_spacing_job . get ( "object" , {}) . get ( "geometrySignature" , {})
_assert ( pattern_job_signature . get ( "spacing" ) == 5.0 , f "pattern spacing signature should be preserved: { pattern_spacing_job } " )
_assert ( len ( pattern_job_signature . get ( "patternInstances" ) or []) == 3 , f "pattern instance locators should be preserved: { pattern_spacing_job } " )
pattern_segment_signature = dict ( pattern_signature )
pattern_segment_signature [ "segmentIndex" ] = 1
pattern_segment_signature [ "movingSide" ] = "after"
pattern_segment_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "pattern-segment-spacing-prepared" ,
backend = backend ,
capability_key = "pattern.segment_spacing" ,
target_value = "6.5" ,
object_id = "pattern:holes-a-b-c" ,
object_signature = pattern_segment_signature ,
)
_assert ( pattern_segment_prepared . get ( "ok" ) is True , f "pattern.segment_spacing should prepare an edit job: { pattern_segment_prepared } " )
pattern_segment_job = read_json ( pattern_segment_prepared [ "job_path" ])
_assert (
pattern_segment_job . get ( "target" , {}) . get ( "backendOperation" ) == "change_pattern_segment_spacing" ,
f "pattern.segment_spacing should route to change_pattern_segment_spacing: { pattern_segment_job } " ,
)
_assert ( pattern_segment_job . get ( "target" , {}) . get ( "value" ) == 6.5 , f "pattern.segment_spacing target should be numeric: { pattern_segment_job } " )
_assert (
pattern_segment_job . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "segmentIndex" ) == 1 ,
f "pattern.segment_spacing should preserve the selected adjacent segment: { pattern_segment_job } " ,
)
pattern_segment_before_signature = dict ( pattern_segment_signature )
pattern_segment_before_signature [ "movingSide" ] = "before"
pattern_segment_before_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "pattern-segment-spacing-before-prepared" ,
backend = backend ,
capability_key = "pattern.segment_spacing" ,
target_value = "6.5" ,
object_id = "pattern:holes-a-b-c" ,
object_signature = pattern_segment_before_signature ,
)
_assert (
pattern_segment_before_prepared . get ( "ok" ) is True
and read_json ( pattern_segment_before_prepared [ "job_path" ]) . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "movingSide" ) == "before" ,
f "pattern.segment_spacing should preserve fix-right/move-left semantics: { pattern_segment_before_prepared } " ,
)
pattern_segment_split_signature = dict ( pattern_segment_signature )
pattern_segment_split_signature [ "movingSide" ] = "split"
pattern_segment_split_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "pattern-segment-spacing-split-prepared" ,
backend = backend ,
capability_key = "pattern.segment_spacing" ,
target_value = "6.5" ,
object_id = "pattern:holes-a-b-c" ,
object_signature = pattern_segment_split_signature ,
)
_assert (
pattern_segment_split_prepared . get ( "ok" ) is True
and read_json ( pattern_segment_split_prepared [ "job_path" ]) . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "movingSide" ) == "split" ,
f "pattern.segment_spacing should preserve split/keep-center semantics: { pattern_segment_split_prepared } " ,
)
2026-08-20 17:12:01 +08:00
pattern_segment_single_left_signature = dict ( pattern_segment_signature )
pattern_segment_single_left_signature [ "movingSide" ] = "single_left"
pattern_segment_single_left_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "pattern-segment-spacing-single-left-prepared" ,
backend = backend ,
capability_key = "pattern.segment_spacing" ,
target_value = "6.5" ,
object_id = "pattern:holes-a-b-c" ,
object_signature = pattern_segment_single_left_signature ,
)
_assert (
pattern_segment_single_left_prepared . get ( "ok" ) is True
and read_json ( pattern_segment_single_left_prepared [ "job_path" ]) . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "movingSide" ) == "single_left" ,
f "pattern.segment_spacing should preserve move-only-left-instance semantics: { pattern_segment_single_left_prepared } " ,
)
pattern_segment_single_right_signature = dict ( pattern_segment_signature )
pattern_segment_single_right_signature [ "movingSide" ] = "single_right"
pattern_segment_single_right_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "pattern-segment-spacing-single-right-prepared" ,
backend = backend ,
capability_key = "pattern.segment_spacing" ,
target_value = "6.5" ,
object_id = "pattern:holes-a-b-c" ,
object_signature = pattern_segment_single_right_signature ,
)
_assert (
pattern_segment_single_right_prepared . get ( "ok" ) is True
and read_json ( pattern_segment_single_right_prepared [ "job_path" ]) . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "movingSide" ) == "single_right" ,
f "pattern.segment_spacing should preserve move-only-right-instance semantics: { pattern_segment_single_right_prepared } " ,
)
pattern_instance_signature = dict ( pattern_signature [ "patternInstances" ][ 1 ])
pattern_instance_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "pattern-instance-position-prepared" ,
backend = backend ,
capability_key = "pattern.instance_position" ,
target_value = [ 6.0 , 0.0 , 0.0 ],
object_id = "pattern:holes-a-b-c:instance-b" ,
object_signature = pattern_instance_signature ,
)
_assert ( pattern_instance_prepared . get ( "ok" ) is True , f "pattern.instance_position should prepare an edit job when instance locators exist: { pattern_instance_prepared } " )
pattern_instance_job = read_json ( pattern_instance_prepared [ "job_path" ])
_assert ( pattern_instance_job . get ( "target" , {}) . get ( "backendOperation" ) == "move_pattern_instance" , f "pattern.instance_position should route to move_pattern_instance: { pattern_instance_job } " )
_assert ( pattern_instance_job . get ( "target" , {}) . get ( "value" ) == [ 6.0 , 0.0 , 0.0 ], f "pattern.instance_position target should be vector3: { pattern_instance_job } " )
pattern_instance_missing_locator = dict ( pattern_instance_signature )
pattern_instance_missing_locator . pop ( "scdmFaceLocators" , None )
pattern_instance_missing_locator . pop ( "faceOrdinals" , None )
pattern_instance_missing_locator . pop ( "globalFaceOrdinals" , None )
pattern_instance_missing_locator . pop ( "bodyLocators" , None )
pattern_instance_missing_locator . pop ( "componentLocators" , None )
pattern_instance_missing_locator . pop ( "bodyIndex" , None )
blocked_pattern_instance = prepare_scdm_edit_job (
step_path ,
output_dir = root / "pattern-instance-position-missing-locator" ,
backend = backend ,
capability_key = "pattern.instance_position" ,
target_value = [ 6.0 , 0.0 , 0.0 ],
object_id = "pattern:holes-a-b-c:instance-b" ,
object_signature = pattern_instance_missing_locator ,
)
_assert (
blocked_pattern_instance . get ( "ok" ) is False
and blocked_pattern_instance . get ( "reason" ) == "object-signature-missing-pattern-instance-locator" ,
f "pattern.instance_position should be blocked before SCDM when instance locator is missing: { blocked_pattern_instance } " ,
)
2026-08-19 18:02:47 +08:00
body_pattern_signature = {
"objectType" : "linear_pattern" ,
"patternKind" : "body" ,
"instanceKind" : "body" ,
"faceIds" : [ 300 , 301 , 302 ],
"bodyIndices" : [ 20 , 21 , 22 ],
"axis" : [ 0.0 , 1.0 , 0.0 ],
"spacing" : 5.0 ,
"pitch" : 5.0 ,
"instanceCount" : 3 ,
"instanceCenters" : [[ 0.0 , 20.0 , 0.0 ], [ 0.0 , 25.0 , 0.0 ], [ 0.0 , 30.0 , 0.0 ]],
"patternInstances" : [
{ "sourceObjectId" : "body:20" , "instanceKind" : "body" , "center" : [ 0.0 , 20.0 , 0.0 ], "bodyIndex" : 20 , "bodyLocators" : [{ "bodyIndex" : 20 }], "faceIds" : [ 300 ]},
{ "sourceObjectId" : "body:21" , "instanceKind" : "body" , "center" : [ 0.0 , 25.0 , 0.0 ], "bodyIndex" : 21 , "bodyLocators" : [{ "bodyIndex" : 21 }], "faceIds" : [ 301 ]},
{ "sourceObjectId" : "body:22" , "instanceKind" : "body" , "center" : [ 0.0 , 30.0 , 0.0 ], "bodyIndex" : 22 , "bodyLocators" : [{ "bodyIndex" : 22 }], "faceIds" : [ 302 ]},
],
}
body_pattern_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "body-pattern-spacing-prepared" ,
backend = backend ,
capability_key = "pattern.spacing" ,
target_value = "8" ,
object_id = "pattern:parts-20-22" ,
object_signature = body_pattern_signature ,
)
_assert (
body_pattern_prepared . get ( "ok" ) is False
and body_pattern_prepared . get ( "reason" ) == "body-pattern-spacing-missing-component-locators" ,
f "body pattern spacing should be blocked until component occurrence locators are available: { body_pattern_prepared } " ,
)
body_segment_pattern_signature = dict ( body_pattern_signature )
body_segment_pattern_signature [ "segmentIndex" ] = 0
body_segment_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "body-pattern-segment-spacing-prepared" ,
backend = backend ,
capability_key = "pattern.segment_spacing" ,
target_value = "6" ,
object_id = "pattern:parts-20-22" ,
object_signature = body_segment_pattern_signature ,
)
_assert (
body_segment_prepared . get ( "ok" ) is False
and body_segment_prepared . get ( "reason" ) == "body-pattern-spacing-missing-component-locators" ,
f "body pattern local segment spacing should also require component occurrence locators: { body_segment_prepared } " ,
)
component_body_pattern_signature = dict ( body_pattern_signature )
component_body_pattern_signature [ "componentInstanceCount" ] = 3
component_body_pattern_signature [ "patternInstances" ] = [
{
** dict ( item ),
"componentLocators" : [
{
"componentIndex" : index ,
"componentPath" : [ index ],
"componentBodyIndex" : 0 ,
"bodyIndex" : item . get ( "bodyIndex" ),
"componentName" : f "Part { index + 1 } " ,
}
],
}
for index , item in enumerate ( body_pattern_signature [ "patternInstances" ])
]
component_body_pattern_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "component-body-pattern-spacing-prepared" ,
backend = backend ,
capability_key = "pattern.spacing" ,
target_value = "8" ,
object_id = "pattern:components-20-22" ,
object_signature = component_body_pattern_signature ,
)
_assert (
component_body_pattern_prepared . get ( "ok" ) is True ,
f "body pattern spacing should prepare when every member has a component occurrence locator: { component_body_pattern_prepared } " ,
)
component_body_pattern_job = read_json ( component_body_pattern_prepared [ "job_path" ])
component_instances = component_body_pattern_job . get ( "object" , {}) . get ( "geometrySignature" , {}) . get ( "patternInstances" ) or []
_assert (
all ( item . get ( "componentLocators" ) for item in component_instances if isinstance ( item , dict )),
f "component occurrence locators should be preserved in pattern.spacing job: { component_body_pattern_job } " ,
)
component_body_segment_signature = dict ( component_body_pattern_signature )
component_body_segment_signature [ "segmentIndex" ] = 0
component_body_segment_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "component-body-pattern-segment-spacing-prepared" ,
backend = backend ,
capability_key = "pattern.segment_spacing" ,
target_value = "6" ,
object_id = "pattern:components-20-22" ,
object_signature = component_body_segment_signature ,
)
_assert (
component_body_segment_prepared . get ( "ok" ) is True ,
f "body pattern local segment spacing should prepare with component occurrence locators: { component_body_segment_prepared } " ,
)
blocked_pattern_signature = dict ( pattern_signature )
blocked_pattern_signature [ "supportPatternFit" ] = {
"supportFaceIds" : [ 92 ],
"localUnitScale" : 0.001 ,
"maxSpacing" : 0.0012 ,
"maxSpacingLocal" : 1.2 ,
"instanceCount" : 3 ,
}
blocked_pattern_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "body-pattern-spacing-blocked" ,
backend = backend ,
capability_key = "pattern.spacing" ,
target_value = 0.002 ,
object_id = "pattern:holes-a-b-c" ,
object_signature = blocked_pattern_signature ,
)
_assert (
blocked_pattern_prepared . get ( "ok" ) is False
and blocked_pattern_prepared . get ( "reason" ) == "pattern-spacing-exceeds-support" ,
f "pattern spacing should be blocked before SCDM when it exceeds support Face limits: { blocked_pattern_prepared } " ,
)
blocked_segment_signature = dict ( pattern_segment_signature )
blocked_segment_signature [ "supportPatternFit" ] = {
"supportFaceIds" : [ 92 ],
"localUnitScale" : 0.001 ,
"maxSegmentSpacing" : 0.0013 ,
"maxSegmentSpacingLocal" : 1.3 ,
"instanceCount" : 3 ,
}
blocked_segment_prepared = prepare_scdm_edit_job (
step_path ,
output_dir = root / "pattern-segment-spacing-blocked" ,
backend = backend ,
capability_key = "pattern.segment_spacing" ,
target_value = 0.002 ,
object_id = "pattern:holes-a-b-c" ,
object_signature = blocked_segment_signature ,
)
_assert (
blocked_segment_prepared . get ( "ok" ) is False
and blocked_segment_prepared . get ( "reason" ) == "pattern-spacing-exceeds-support" ,
f "pattern segment spacing should be blocked before SCDM when it exceeds support Face limits: { blocked_segment_prepared } " ,
)
2026-08-19 10:28:09 +08:00
unsupported = prepare_scdm_edit_job (
step_path ,
output_dir = root / "unsupported-unknown" ,
backend = backend ,
capability_key = "not.real" ,
target_value = "1" ,
object_signature = signature ,
)
_assert ( unsupported . get ( "ok" ) is False and unsupported . get ( "reason" ) == "unsupported-capability" , f "unknown capability should fail early: { unsupported } " )
success = run_scdm_edit_job (
step_path ,
output_dir = root / "success" ,
backend = backend ,
capability_key = "hole.diameter" ,
target_value = 0.9 ,
object_id = "hole:85-94" ,
object_signature = signature ,
runner = _successful_runner ,
)
_assert ( success . get ( "ok" ) is True , f "fake edit should succeed: { success } " )
_assert ( Path ( str ( success [ "output_step" ])) . is_file (), f "output STEP should exist: { success } " )
success_backend = success . get ( "backend" )
_assert ( isinstance ( success_backend , dict ), f "successful edit should carry backend status: { success } " )
_assert ( success_backend . get ( "runScriptOk" ) is True , f "successful edit should mark /RunScript usable: { success_backend } " )
_assert ( success_backend . get ( "licenseOk" ) is True , f "successful edit should mark license usable: { success_backend } " )
failed = run_scdm_edit_job (
step_path ,
output_dir = root / "failed" ,
backend = backend ,
capability_key = "hole.position" ,
target_value = [ 0.5 , 1.0 , 6.0 ],
object_id = "hole:85-94" ,
object_signature = signature ,
runner = _failure_runner ,
)
_assert ( failed . get ( "ok" ) is False and failed . get ( "reason" ) == "fake-failed" , f "error.json should drive failure reason: { failed } " )
slot_success = run_scdm_edit_job (
step_path ,
output_dir = root / "slot-success" ,
backend = backend ,
capability_key = "slot.position" ,
target_value = [ 1.0 , 2.0 , 6.0 ],
object_id = "slot:30-31-32" ,
object_signature = slot_signature ,
runner = _successful_runner ,
)
_assert ( slot_success . get ( "ok" ) is True , f "fake slot.position edit should succeed: { slot_success } " )
_assert ( slot_success . get ( "backend_operation" ) == "move_slot" , f "slot.position should report move_slot: { slot_success } " )
2026-08-19 18:02:47 +08:00
slot_width_success = run_scdm_edit_job (
step_path ,
output_dir = root / "slot-width-success" ,
backend = backend ,
capability_key = "slot.width" ,
target_value = 2.5 ,
object_id = "slot:30-31-32" ,
object_signature = slot_signature ,
runner = _successful_runner ,
)
_assert ( slot_width_success . get ( "ok" ) is True , f "fake slot.width edit should succeed: { slot_width_success } " )
_assert ( slot_width_success . get ( "backend_operation" ) == "change_slot_width" , f "slot.width should report change_slot_width: { slot_width_success } " )
slot_depth_success = run_scdm_edit_job (
step_path ,
output_dir = root / "slot-depth-success" ,
backend = backend ,
capability_key = "slot.depth" ,
target_value = 2.0 ,
object_id = "slot:30-31-32" ,
object_signature = slot_signature ,
runner = _successful_runner ,
)
_assert ( slot_depth_success . get ( "ok" ) is True , f "fake slot.depth edit should succeed: { slot_depth_success } " )
_assert ( slot_depth_success . get ( "backend_operation" ) == "change_slot_depth" , f "slot.depth should report change_slot_depth: { slot_depth_success } " )
2026-08-19 10:28:09 +08:00
boss_success = run_scdm_edit_job (
step_path ,
output_dir = root / "boss-success" ,
backend = backend ,
capability_key = "boss.position" ,
target_value = [ 3.0 , 0.0 , 2.0 ],
object_id = "boss:50-51-52" ,
object_signature = boss_signature ,
runner = _successful_runner ,
)
_assert ( boss_success . get ( "ok" ) is True , f "fake boss.position edit should succeed: { boss_success } " )
_assert ( boss_success . get ( "backend_operation" ) == "move_boss" , f "boss.position should report move_boss: { boss_success } " )
2026-08-19 18:02:47 +08:00
boss_height_success = run_scdm_edit_job (
step_path ,
output_dir = root / "boss-height-success" ,
backend = backend ,
capability_key = "boss.height" ,
target_value = 5.5 ,
object_id = "boss:50-51-52" ,
object_signature = boss_signature ,
runner = _successful_runner ,
)
_assert ( boss_height_success . get ( "ok" ) is True , f "fake boss.height edit should succeed: { boss_height_success } " )
_assert ( boss_height_success . get ( "backend_operation" ) == "change_boss_height" , f "boss.height should report change_boss_height: { boss_height_success } " )
boss_diameter_success = run_scdm_edit_job (
step_path ,
output_dir = root / "boss-diameter-success" ,
backend = backend ,
capability_key = "boss.diameter" ,
target_value = 4.5 ,
object_id = "boss:50-51-52" ,
object_signature = boss_signature ,
runner = _successful_runner ,
)
_assert ( boss_diameter_success . get ( "ok" ) is True , f "fake boss.diameter edit should succeed: { boss_diameter_success } " )
_assert ( boss_diameter_success . get ( "backend_operation" ) == "change_boss_diameter" , f "boss.diameter should report change_boss_diameter: { boss_diameter_success } " )
round_radius_success = run_scdm_edit_job (
step_path ,
output_dir = root / "round-radius-success" ,
backend = backend ,
capability_key = "round.radius" ,
target_value = 0.75 ,
object_id = "round:60" ,
object_signature = round_signature ,
runner = _successful_runner ,
)
_assert ( round_radius_success . get ( "ok" ) is True , f "fake round.radius edit should succeed: { round_radius_success } " )
_assert ( round_radius_success . get ( "backend_operation" ) == "change_round_radius" , f "round.radius should report change_round_radius: { round_radius_success } " )
round_delete_success = run_scdm_edit_job (
step_path ,
output_dir = root / "round-delete-success" ,
backend = backend ,
capability_key = "feature.delete_round_or_chamfer" ,
target_value = "" ,
object_id = "round:60" ,
object_signature = round_signature ,
runner = _successful_runner ,
)
_assert ( round_delete_success . get ( "ok" ) is True , f "fake round/chamfer delete edit should succeed: { round_delete_success } " )
_assert ( round_delete_success . get ( "backend_operation" ) == "delete_round_or_chamfer" , f "round/chamfer delete should report delete_round_or_chamfer: { round_delete_success } " )
chamfer_distance_success = run_scdm_edit_job (
step_path ,
output_dir = root / "chamfer-distance-success" ,
backend = backend ,
capability_key = "chamfer.distance" ,
target_value = 1.2 ,
object_id = "chamfer:63" ,
object_signature = chamfer_signature ,
runner = _successful_runner ,
)
_assert ( chamfer_distance_success . get ( "ok" ) is True , f "fake chamfer.distance edit should succeed: { chamfer_distance_success } " )
_assert ( chamfer_distance_success . get ( "backend_operation" ) == "change_chamfer_distance" , f "chamfer.distance should report change_chamfer_distance: { chamfer_distance_success } " )
2026-08-20 17:12:01 +08:00
shell_thickness_success = run_scdm_edit_job (
step_path ,
output_dir = root / "shell-thickness-success" ,
backend = backend ,
capability_key = "shell.thickness" ,
target_value = 1.6 ,
object_id = "thin_wall:101-102" ,
object_signature = shell_signature ,
runner = _successful_runner ,
)
_assert ( shell_thickness_success . get ( "ok" ) is True , f "fake shell.thickness edit should succeed: { shell_thickness_success } " )
_assert ( shell_thickness_success . get ( "backend_operation" ) == "change_shell_thickness" , f "shell.thickness should report change_shell_thickness: { shell_thickness_success } " )
2026-08-19 18:02:47 +08:00
pattern_spacing_success = run_scdm_edit_job (
step_path ,
output_dir = root / "pattern-spacing-success" ,
backend = backend ,
capability_key = "pattern.spacing" ,
target_value = 7.5 ,
object_id = "pattern:holes-a-b-c" ,
object_signature = pattern_signature ,
runner = _successful_runner ,
)
_assert ( pattern_spacing_success . get ( "ok" ) is True , f "fake pattern.spacing edit should succeed: { pattern_spacing_success } " )
_assert ( pattern_spacing_success . get ( "backend_operation" ) == "change_pattern_spacing" , f "pattern.spacing should report change_pattern_spacing: { pattern_spacing_success } " )
pattern_segment_success = run_scdm_edit_job (
step_path ,
output_dir = root / "pattern-segment-spacing-success" ,
backend = backend ,
capability_key = "pattern.segment_spacing" ,
target_value = 6.5 ,
object_id = "pattern:holes-a-b-c" ,
object_signature = pattern_segment_signature ,
runner = _successful_runner ,
)
_assert ( pattern_segment_success . get ( "ok" ) is True , f "fake pattern.segment_spacing edit should succeed: { pattern_segment_success } " )
_assert (
pattern_segment_success . get ( "backend_operation" ) == "change_pattern_segment_spacing" ,
f "pattern.segment_spacing should report change_pattern_segment_spacing: { pattern_segment_success } " ,
)
2026-08-20 17:12:01 +08:00
pattern_instance_success = run_scdm_edit_job (
step_path ,
output_dir = root / "pattern-instance-position-success" ,
backend = backend ,
capability_key = "pattern.instance_position" ,
target_value = [ 6.0 , 0.0 , 0.0 ],
object_id = "pattern:holes-a-b-c:instance-b" ,
object_signature = pattern_instance_signature ,
runner = _successful_runner ,
)
_assert ( pattern_instance_success . get ( "ok" ) is True , f "fake pattern.instance_position edit should succeed: { pattern_instance_success } " )
_assert ( pattern_instance_success . get ( "backend_operation" ) == "move_pattern_instance" , f "pattern.instance_position should report move_pattern_instance: { pattern_instance_success } " )
2026-08-19 10:28:09 +08:00
missing_output = run_scdm_edit_job (
step_path ,
output_dir = root / "missing-output" ,
backend = backend ,
capability_key = "face.offset" ,
target_value = 5 ,
object_id = "face:9" ,
object_signature = { "objectType" : "face" , "faceIds" : [ 9 ], "bodyIndex" : 0 , "faceOrdinal" : 3 },
runner = _missing_output_runner ,
)
_assert ( missing_output . get ( "ok" ) is False and missing_output . get ( "reason" ) == "missing-output-step" , f "missing output STEP should be rejected: { missing_output } " )
empty_output = run_scdm_edit_job (
step_path ,
output_dir = root / "empty-output" ,
backend = backend ,
capability_key = "face.offset" ,
target_value = 5 ,
object_id = "face:9" ,
object_signature = { "objectType" : "face" , "faceIds" : [ 9 ], "bodyIndex" : 0 , "faceOrdinal" : 3 },
runner = _empty_output_runner ,
)
_assert ( empty_output . get ( "ok" ) is False and empty_output . get ( "reason" ) == "empty-output-step" , f "empty output STEP should be rejected: { empty_output } " )
print ( "scdm edit runner ok" )
return 0
if __name__ == "__main__" :
raise SystemExit ( main ())