feat: 完善 SCDM-first 参数化编辑交付版
This commit is contained in:
@@ -175,10 +175,15 @@ def main() -> int:
|
||||
"change_boss_diameter",
|
||||
"change_round_radius",
|
||||
"change_chamfer_distance",
|
||||
"change_shell_thickness",
|
||||
"change_pattern_spacing",
|
||||
"change_pattern_segment_spacing",
|
||||
"move_pattern_instance",
|
||||
"segment_before",
|
||||
"segment_split",
|
||||
"segment_single_left",
|
||||
"segment_single_right",
|
||||
"wallFaceLocators",
|
||||
"pull_face_offset",
|
||||
"fill_feature",
|
||||
"delete_round_or_chamfer",
|
||||
@@ -207,6 +212,11 @@ def main() -> int:
|
||||
):
|
||||
_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")
|
||||
_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",
|
||||
)
|
||||
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")
|
||||
|
||||
@@ -253,6 +263,19 @@ def main() -> int:
|
||||
_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}")
|
||||
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}",
|
||||
)
|
||||
slot_depth_prepared = prepare_scdm_edit_job(
|
||||
step_path,
|
||||
output_dir=root / "slot-depth-prepared",
|
||||
@@ -270,6 +293,22 @@ def main() -> int:
|
||||
_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}")
|
||||
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}",
|
||||
)
|
||||
|
||||
boss_signature = {
|
||||
"objectType": "cylindrical_boss",
|
||||
@@ -316,6 +355,22 @@ def main() -> int:
|
||||
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}")
|
||||
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}",
|
||||
)
|
||||
boss_diameter_prepared = prepare_scdm_edit_job(
|
||||
step_path,
|
||||
output_dir=root / "boss-diameter-prepared",
|
||||
@@ -332,6 +387,48 @@ def main() -> int:
|
||||
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}")
|
||||
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}",
|
||||
)
|
||||
|
||||
round_signature = {
|
||||
"objectType": "round",
|
||||
@@ -404,6 +501,55 @@ def main() -> int:
|
||||
_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}")
|
||||
|
||||
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}",
|
||||
)
|
||||
|
||||
pattern_signature = {
|
||||
"objectType": "linear_pattern",
|
||||
"faceIds": [85, 94, 87, 96, 89, 98],
|
||||
@@ -514,6 +660,73 @@ def main() -> int:
|
||||
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}",
|
||||
)
|
||||
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}",
|
||||
)
|
||||
|
||||
body_pattern_signature = {
|
||||
"objectType": "linear_pattern",
|
||||
@@ -658,19 +871,6 @@ def main() -> int:
|
||||
f"pattern segment spacing should be blocked before SCDM when it exceeds support Face limits: {blocked_segment_prepared}",
|
||||
)
|
||||
|
||||
planned = prepare_scdm_edit_job(
|
||||
step_path,
|
||||
output_dir=root / "unsupported",
|
||||
backend=backend,
|
||||
capability_key="pattern.instance_position",
|
||||
target_value="1",
|
||||
object_signature=signature,
|
||||
)
|
||||
_assert(
|
||||
planned.get("ok") is False and planned.get("reason") == "capability-not-productized",
|
||||
f"planned capability should fail early with a roadmap reason: {planned}",
|
||||
)
|
||||
|
||||
unsupported = prepare_scdm_edit_job(
|
||||
step_path,
|
||||
output_dir=root / "unsupported-unknown",
|
||||
@@ -823,6 +1023,19 @@ def main() -> int:
|
||||
_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}")
|
||||
|
||||
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}")
|
||||
|
||||
pattern_spacing_success = run_scdm_edit_job(
|
||||
step_path,
|
||||
output_dir=root / "pattern-spacing-success",
|
||||
@@ -850,6 +1063,18 @@ def main() -> int:
|
||||
pattern_segment_success.get("backend_operation") == "change_pattern_segment_spacing",
|
||||
f"pattern.segment_spacing should report change_pattern_segment_spacing: {pattern_segment_success}",
|
||||
)
|
||||
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}")
|
||||
|
||||
missing_output = run_scdm_edit_job(
|
||||
step_path,
|
||||
|
||||
Reference in New Issue
Block a user