Files
pythonocc-step-editor/scripts/verify_asitus_hole_bridge.py
T

208 lines
9.3 KiB
Python

from __future__ import annotations
from pathlib import Path
import sys
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.asitus_bridge import (
default_asitus_recognize_holes_path,
parse_asitus_hole_groups,
parse_asitus_probe_payload,
run_asitus_hole_recognition,
)
from step_editor.model import StepModel
from step_editor.recognition_graph import recognition_summary
MODEL_PATH = PROJECT_ROOT / "assets" / "models" / "ICEPAK-NATURAL.stp"
def _assert(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)
def _install_external_regions(model: StepModel, groups: list[tuple[int, ...]]) -> None:
model._asitus_hole_regions_attempted = True # noqa: SLF001
model._asitus_hole_region_cache.clear() # noqa: SLF001
for group in groups:
for face_id in group:
model._asitus_hole_region_cache[int(face_id)] = list(group) # noqa: SLF001
model._same_domain_face_ids_cache.clear() # noqa: SLF001
def main() -> int:
payload = {
"validBreP": True,
"faceCount": 158,
"aagNodeCount": 158,
"holeFaceIds": [85, 86, 87, 88, 95, 96, 97, 98],
"holeCount": 4,
"holes": [
{"index": 1, "faceIds": [85, 98]},
{"index": 2, "faceIds": [86, 97]},
{"index": 3, "faceIds": [87, 96]},
{"index": 4, "faceIds": [88, 95]},
],
"surfaceSummary": {"plane": 106, "cylinder": 52},
"angleSummary": {"smooth": 4, "convex": 8},
"geometricRelationMode": "all-pairs",
"geometricRelationSummary": {
"coaxial": 1,
"coplanar": 1,
"parallel": 1,
"parallel_axis": 1,
"perpendicular": 1,
"tangent": 1,
},
"faces": [
{"id": 88, "surface": "cylinder", "neighbors": [12, 95]},
{"id": 95, "surface": "cylinder", "neighbors": [88, 42]},
],
"adjacency": [
{"faceIds": [88, 95], "angleType": "smooth", "angleRad": 0.0, "edgeIds": [501, 502]},
],
"geometricRelations": [
{"faceIds": [88, 95], "type": "coaxial", "residual": 0.0, "source": "analysis-situs-probe"},
{"faceIds": [88, 95], "type": "tangent", "residual": 0.0, "source": "analysis-situs-aag-angle"},
{"faceIds": [88, 95], "type": "coplanar", "residual": 0.0, "source": "analysis-situs-probe"},
{"faceIds": [88, 95], "type": "parallel", "residual": 2.0, "source": "analysis-situs-probe"},
{"faceIds": [88, 95], "type": "perpendicular", "residual": 0.0, "source": "analysis-situs-probe"},
{"faceIds": [88, 95], "type": "parallel_axis", "residual": 0.0, "source": "analysis-situs-probe"},
],
}
raw_groups = parse_asitus_hole_groups(payload)
_assert(raw_groups == [(85, 98), (86, 97), (87, 96), (88, 95)], f"unexpected parsed groups: {raw_groups}")
parsed = parse_asitus_probe_payload(payload)
_assert(parsed.get("surface_summary") == {"plane": 106, "cylinder": 52}, f"bad surface summary: {parsed}")
_assert(parsed.get("geometric_relation_mode") == "all-pairs", f"bad geometric relation mode: {parsed}")
expected_geometric_summary = {
"coaxial": 1,
"coplanar": 1,
"parallel": 1,
"parallel_axis": 1,
"perpendicular": 1,
"tangent": 1,
}
_assert(
parsed.get("geometric_relation_summary") == expected_geometric_summary,
f"bad geometric summary: {parsed}",
)
_assert(len(tuple(parsed.get("faces", ()))) == 2, f"bad face summary parse: {parsed}")
_assert(len(tuple(parsed.get("adjacency", ()))) == 1, f"bad adjacency parse: {parsed}")
_assert(len(tuple(parsed.get("geometric_relations", ()))) == 6, f"bad geometric relation parse: {parsed}")
if not MODEL_PATH.exists():
print("asitus hole bridge mapping skipped: local ICEPAK-NATURAL.stp is not present")
return 0
model = StepModel.load(MODEL_PATH)
mapped = model._map_asitus_hole_groups(raw_groups) # noqa: SLF001
expected = [(84, 97), (85, 96), (86, 95), (87, 94)]
_assert(mapped == expected, f"Analysis Situs 1-based AAG face ids should map to Python face ids: {mapped}")
async_model = StepModel.load(MODEL_PATH)
_assert(async_model.begin_asitus_hole_region_load() is True, "background preload should be accepted once")
_assert(
async_model._asitus_hole_region_ids(87) == [], # noqa: SLF001
"selection should not synchronously run Analysis Situs while background preload is pending",
)
async_mapped = async_model.install_asitus_hole_recognition_result({"ok": True, "reason": "ok", "groups": raw_groups})
_assert(async_mapped == expected, f"background result should install mapped groups: {async_mapped}")
_assert(async_model.face_region_ids(87) == [87, 94], "installed background result should drive whole-hole selection")
relation_model = StepModel.load(MODEL_PATH)
relation_result = {
"ok": True,
"reason": "ok",
"groups": raw_groups,
"faces": parsed.get("faces", ()),
"adjacency": parsed.get("adjacency", ()),
"geometric_relations": parsed.get("geometric_relations", ()),
"surface_summary": parsed.get("surface_summary", {}),
"angle_summary": parsed.get("angle_summary", {}),
"geometric_relation_summary": parsed.get("geometric_relation_summary", {}),
}
relation_mapped = relation_model.install_asitus_hole_recognition_result(relation_result)
_assert(relation_mapped == expected, f"relation install should preserve hole mapping: {relation_mapped}")
relation_info = relation_model.quick_face_info(87)
_assert(relation_info.get("asitus_relation_status") == "ready", f"missing AAG relation fields: {relation_info}")
_assert(
relation_info.get("asitus_adjacent_face_ids") == (11, 94),
f"Analysis Situs 1-based neighbors should map to Python face ids: {relation_info}",
)
_assert(
"smooth:1" in str(relation_info.get("asitus_adjacent_relation_summary") or ""),
f"missing AAG angle relation summary: {relation_info}",
)
_assert(
"coaxial:1" in str(relation_info.get("asitus_geometric_relation_summary") or ""),
f"missing AAG geometric relation summary: {relation_info}",
)
_assert(
"tangent:1" in str(relation_info.get("asitus_geometric_relation_summary") or ""),
f"missing AAG tangent relation summary: {relation_info}",
)
graph_summary = recognition_summary(relation_model)
relation_counts = dict(graph_summary.get("relation_counts", {}) or {})
_assert(
relation_counts.get("external_coaxial", 0) >= 1,
f"recognition graph should include external coaxial evidence: {graph_summary}",
)
for relation_type in (
"external_coplanar",
"external_parallel",
"external_parallel_axis",
"external_perpendicular",
"external_tangent",
):
_assert(
relation_counts.get(relation_type, 0) >= 1,
f"recognition graph should include {relation_type} evidence: {graph_summary}",
)
_assert(
int(relation_info.get("recognition_external_relation_score_bonus") or 0) > 0,
f"feature summary should expose an external relation confidence bonus: {relation_info}",
)
candidates = relation_model.editable_feature_candidates(limit=160, detailed=False, max_scan_faces=120)
related_candidates = [
item
for item in candidates
if int(item.get("face_id", -1) or -1) == 87
or int(item.get("target_id", -1) or -1) == 87
]
_assert(related_candidates, f"editable scan should include the Analysis Situs-backed Face 87 candidate: {candidates}")
_assert(
any(int(item.get("recognition_external_relation_score_bonus") or 0) > 0 for item in related_candidates),
f"editable candidates should carry external relation sorting support: {related_candidates}",
)
_assert(
any("coaxial" in str(item.get("external_recognition_relation_summary") or "") for item in related_candidates),
f"editable candidates should expose external relation summary: {related_candidates}",
)
_install_external_regions(model, mapped)
_assert(model.face_region_ids(87) == [87, 94], "Face 87 should select the complete external hole group")
_assert(model.face_region_ids(94) == [87, 94], "Face 94 should select the complete external hole group")
info = model.quick_face_info(87)
_assert(list(info.get("feature_highlight_face_ids") or []) == [87, 94], "quick UI highlight should use external hole group")
cli = default_asitus_recognize_holes_path(PROJECT_ROOT)
if cli is not None:
result = run_asitus_hole_recognition(MODEL_PATH, project_root=PROJECT_ROOT, timeout_seconds=6.0)
_assert(result.get("ok") is True, f"local Analysis Situs CLI should recognize ICEPAK holes: {result}")
external_mapped = model._map_asitus_hole_groups(result.get("groups", ())) # noqa: SLF001
for group in expected:
_assert(group in external_mapped, f"local Analysis Situs CLI mapping missed {group}: {external_mapped}")
print("asitus hole bridge ok")
return 0
if __name__ == "__main__":
raise SystemExit(main())