feat: 推进SCDM-first后端接入和大模型编辑优化
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from step_editor.scdm_result_validator import ( # noqa: E402
|
||||
build_scdm_id_mapping,
|
||||
check_scdm_summary_delta,
|
||||
check_scdm_unedited_objects,
|
||||
check_scdm_target,
|
||||
match_scdm_object_by_signature,
|
||||
rewrite_scdm_relation_formula_ids,
|
||||
validate_scdm_edit_result,
|
||||
)
|
||||
|
||||
|
||||
def _assert(condition: bool, message: str) -> None:
|
||||
if not condition:
|
||||
raise AssertionError(message)
|
||||
|
||||
|
||||
def _hole(object_id: str, face_id: int, *, diameter: float, center: tuple[float, float, float]) -> dict[str, object]:
|
||||
return {
|
||||
"objectId": object_id,
|
||||
"objectType": "hole",
|
||||
"geometrySignature": {
|
||||
"objectType": "hole",
|
||||
"faceIds": [face_id],
|
||||
"surfaceType": "cylinder",
|
||||
"center": list(center),
|
||||
"axis": [0.0, 0.0, 1.0],
|
||||
"diameter": diameter,
|
||||
},
|
||||
"capabilities": [
|
||||
{"key": "hole.diameter", "currentValue": diameter},
|
||||
{"key": "hole.position", "currentValue": list(center)},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _cache(*objects: dict[str, object]) -> dict[str, object]:
|
||||
return {
|
||||
"schemaVersion": 1,
|
||||
"source": "SCDM",
|
||||
"objects": list(objects),
|
||||
"diagnostics": {},
|
||||
}
|
||||
|
||||
|
||||
def _cache_with_summary(summary: dict[str, int], *objects: dict[str, object]) -> dict[str, object]:
|
||||
cache = _cache(*objects)
|
||||
cache["diagnostics"] = {"raw_summary": dict(summary)}
|
||||
return cache
|
||||
|
||||
|
||||
def main() -> int:
|
||||
with tempfile.TemporaryDirectory(prefix="step_editor_scdm_validate_") as temp:
|
||||
root = Path(temp)
|
||||
output_step = root / "result.step"
|
||||
output_step.write_text("ISO-10303-21;\nEND-ISO-10303-21;\n", encoding="utf-8")
|
||||
|
||||
before = _cache(
|
||||
_hole("hole:85", 85, diameter=0.5, center=(0.5, 1.0, 9.5)),
|
||||
_hole("hole:87", 87, diameter=0.5, center=(2.0, 1.0, 9.5)),
|
||||
)
|
||||
after = _cache(
|
||||
_hole("hole:90", 90, diameter=0.75, center=(0.5, 1.0, 9.5)),
|
||||
_hole("hole:91", 91, diameter=0.5, center=(2.0, 1.0, 9.5)),
|
||||
)
|
||||
before_signature = before["objects"][0]["geometrySignature"] # type: ignore[index]
|
||||
|
||||
match = match_scdm_object_by_signature(before_signature, after, capability_key="hole.diameter")
|
||||
_assert(match.get("status") == "unique", f"changed diameter should still match by center/axis/type: {match}")
|
||||
_assert(match.get("object", {}).get("objectId") == "hole:90", f"wrong match: {match}")
|
||||
|
||||
ok = validate_scdm_edit_result(
|
||||
{"ok": True, "output_step": str(output_step)},
|
||||
before_signature=before_signature,
|
||||
before_cache=before,
|
||||
after_cache=after,
|
||||
capability_key="hole.diameter",
|
||||
expected_target=0.75,
|
||||
edited_object_id="hole:85",
|
||||
brep_validator=lambda path: {"ok": path.is_file(), "reason": "ok"},
|
||||
)
|
||||
_assert(ok.get("ok") is True, f"validated edit should pass: {ok}")
|
||||
_assert(ok.get("targetCheck", {}).get("ok") is True, f"target diameter should be checked: {ok}")
|
||||
_assert(ok.get("topologyCheck", {}).get("ok") is True, f"unchanged objects should be checked: {ok}")
|
||||
|
||||
drift_after = _cache(_hole("hole:90", 90, diameter=0.75, center=(0.5, 1.0, 9.5)))
|
||||
drift = validate_scdm_edit_result(
|
||||
{"ok": True, "output_step": str(output_step)},
|
||||
before_signature=before_signature,
|
||||
before_cache=before,
|
||||
after_cache=drift_after,
|
||||
capability_key="hole.diameter",
|
||||
expected_target=0.75,
|
||||
edited_object_id="hole:85",
|
||||
)
|
||||
_assert(drift.get("ok") is False and drift.get("reason") == "unexpected-object-drift", f"missing unrelated hole should fail: {drift}")
|
||||
direct_drift = check_scdm_unedited_objects(before, drift_after, edited_object_id="hole:85", edited_signature=before_signature)
|
||||
_assert(direct_drift.get("ok") is False and direct_drift.get("checked") == 1, f"direct drift check should inspect one unedited object: {direct_drift}")
|
||||
|
||||
mismatch = validate_scdm_edit_result(
|
||||
{"ok": True, "output_step": str(output_step)},
|
||||
before_signature=before_signature,
|
||||
after_cache=after,
|
||||
capability_key="hole.diameter",
|
||||
expected_target=0.9,
|
||||
)
|
||||
_assert(mismatch.get("ok") is False and mismatch.get("reason") == "target-mismatch", f"wrong target should fail: {mismatch}")
|
||||
|
||||
missing = validate_scdm_edit_result({"ok": True, "output_step": str(root / "missing.step")})
|
||||
_assert(missing.get("ok") is False and missing.get("reason") == "missing-output-step", f"missing result STEP should fail: {missing}")
|
||||
|
||||
mapping = build_scdm_id_mapping(before, after, capability_key="hole.diameter")
|
||||
_assert(mapping.get("faceIdMap") == {85: 90, 87: 91}, f"face IDs should remap through signatures: {mapping}")
|
||||
rewritten = rewrite_scdm_relation_formula_ids("Face87.直径 = Face85.半径", mapping)
|
||||
_assert(rewritten == "Face91.直径 = Face90.半径", f"formula IDs should follow SCDM remap: {rewritten}")
|
||||
|
||||
position_after = _cache(_hole("hole:91", 91, diameter=0.5, center=(2.0, 1.0, 6.0)))
|
||||
position_check = check_scdm_target(position_after["objects"][0], capability_key="hole.position", expected_target=[2.0, 1.0, 6.0])
|
||||
_assert(position_check.get("ok") is True, f"position target should pass: {position_check}")
|
||||
|
||||
summary_before = _cache_with_summary(
|
||||
{"bodyCount": 13, "objectCount": 554, "faceCount": 158, "edgeCount": 396},
|
||||
_hole("hole:85", 85, diameter=0.5, center=(0.5, 1.0, 9.5)),
|
||||
)
|
||||
summary_after_ok = _cache_with_summary(
|
||||
{"bodyCount": 13, "objectCount": 550, "faceCount": 157, "edgeCount": 390},
|
||||
_hole("hole:90", 90, diameter=0.75, center=(0.5, 1.0, 9.5)),
|
||||
)
|
||||
summary_ok = check_scdm_summary_delta(summary_before, summary_after_ok, capability_key="hole.diameter")
|
||||
_assert(summary_ok.get("ok") is True, f"small summary changes should pass: {summary_ok}")
|
||||
summary_after_bad = _cache_with_summary(
|
||||
{"bodyCount": 13, "objectCount": 80, "faceCount": 20, "edgeCount": 45},
|
||||
_hole("hole:90", 90, diameter=0.75, center=(0.5, 1.0, 9.5)),
|
||||
)
|
||||
summary_bad = validate_scdm_edit_result(
|
||||
{"ok": True, "output_step": str(output_step)},
|
||||
before_signature=summary_before["objects"][0]["geometrySignature"], # type: ignore[index]
|
||||
before_cache=summary_before,
|
||||
after_cache=summary_after_bad,
|
||||
capability_key="hole.diameter",
|
||||
expected_target=0.75,
|
||||
)
|
||||
_assert(summary_bad.get("ok") is False and summary_bad.get("reason") == "summary-drift", f"large summary drift should fail: {summary_bad}")
|
||||
summary_fill = check_scdm_summary_delta(summary_before, summary_after_bad, capability_key="feature.fill")
|
||||
_assert(summary_fill.get("ok") is None and summary_fill.get("reason") == "skipped-command-feature", f"fill should skip summary count guard: {summary_fill}")
|
||||
|
||||
ambiguous_after = _cache(
|
||||
_hole("hole:100", 100, diameter=0.75, center=(0.5, 1.0, 9.5)),
|
||||
_hole("hole:101", 101, diameter=0.75, center=(0.5, 1.0, 9.5)),
|
||||
)
|
||||
ambiguous = match_scdm_object_by_signature(before_signature, ambiguous_after, capability_key="hole.diameter")
|
||||
_assert(ambiguous.get("status") == "multiple", f"ambiguous matches should be reported: {ambiguous}")
|
||||
|
||||
print("scdm result validator ok")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user