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())