from __future__ import annotations from pathlib import Path import sys import tempfile PROJECT_ROOT = Path(__file__).resolve().parent.parent SCRIPTS_DIR = Path(__file__).resolve().parent for path in (PROJECT_ROOT, SCRIPTS_DIR): if str(path) not in sys.path: sys.path.insert(0, str(path)) from step_editor.model import StepModel from verify_boss_resize import ( # noqa: E402 _axis_center, _first_boss_face, _first_circle_edge_adjacent_to_face, _write_boss_model, ) def _assert(condition: bool, message: str) -> None: if not condition: raise AssertionError(message) def _assert_common_topology(topology: dict[str, object], label: str) -> None: _assert(topology.get("topology_relation_depth") == 1, f"{label}: topology depth should be 1") _assert( topology.get("topology_relation_boundary") == "shared-edge", f"{label}: topology boundary should be shared-edge", ) ignored = tuple(topology.get("topology_ignored_relation_depths", ()) or ()) _assert("second-level" in ignored, f"{label}: second-level propagation should be explicitly ignored") _assert("third-level" in ignored, f"{label}: third-level propagation should be explicitly ignored") _assert( int(topology.get("cylindrical_feature_side_face_count", 0) or 0) >= 1, f"{label}: selected boss side Face is missing", ) _assert( int(topology.get("cylindrical_feature_boundary_edge_count", 0) or 0) >= 1, f"{label}: boss boundary Edges are missing", ) _assert( int(topology.get("cylindrical_feature_adjacent_face_count", 0) or 0) >= 1, f"{label}: direct adjacent Faces are missing", ) _assert( int(topology.get("cylindrical_feature_end_face_count", 0) or 0) >= 1, f"{label}: boss end/base cap Faces are missing", ) def _assert_plan_topology(plan: dict[str, object], label: str) -> None: _assert(plan.get("topology_relation_depth") == 1, f"{label}: plan topology depth should be 1") _assert( plan.get("topology_relation_status") == "ready", f"{label}: plan topology should be ready, got {plan.get('topology_relation_status')!r}", ) _assert( plan.get("first_level_topology_status") == "ready", f"{label}: first-level guard should be ready, got {plan.get('first_level_topology_status')!r}", ) _assert( int(plan.get("first_level_boundary_edge_count", 0) or 0) >= 1, f"{label}: plan boundary Edges are missing", ) _assert( int(plan.get("first_level_adjacent_face_count", 0) or 0) >= 1, f"{label}: plan adjacent Faces are missing", ) _assert( "second-level" in tuple(plan.get("topology_ignored_relation_depths", ()) or ()), f"{label}: plan should document ignored deeper topology", ) _assert( plan.get("first_level_topology_guard_note"), f"{label}: first-level topology guard note is missing", ) def _assert_edge_delegated_topology(plan: dict[str, object]) -> None: _assert(plan.get("circular_edge_cylinder_mode") == "boss", "circle Edge should delegate to boss axis move") _assert(plan.get("move_axis_topology_relation_depth") == 1, "delegated boss topology depth should be 1") _assert( plan.get("move_axis_topology_relation_status") == "ready", "delegated boss topology should be ready", ) _assert( plan.get("move_axis_first_level_topology_status") == "ready", "delegated boss first-level guard should be ready", ) _assert( int(plan.get("move_axis_first_level_adjacent_face_count", 0) or 0) >= 1, "delegated boss adjacent Faces are missing", ) def main() -> int: with tempfile.TemporaryDirectory(prefix="geom_param_boss_topology_") as temp_dir: model_path = Path(temp_dir) / "boss.step" _write_boss_model(model_path) model = StepModel.load(model_path) face_id = _first_boss_face(model) topology = model.cylindrical_feature_first_level_topology(face_id) _assert_common_topology(topology, "cylindrical boss") center = _axis_center(model, face_id) diameter_plan = model.cylindrical_boss_resize_plan(face_id, 8.0) height_plan = model.cylindrical_boss_height_plan(face_id, 7.0) axis_plan = model.cylindrical_boss_axis_move_plan(face_id, (center[0] + 2.0, center[1], center[2])) _assert_plan_topology(diameter_plan, "boss diameter plan") _assert_plan_topology(height_plan, "boss height plan") _assert_plan_topology(axis_plan, "boss axis plan") edge_id = _first_circle_edge_adjacent_to_face(model, face_id) edge_info = model.edge_info(edge_id) edge_center = edge_info.get("center") _assert(isinstance(edge_center, tuple), "boss circular Edge center is missing") target_edge_center = (float(edge_center[0]) + 2.0, float(edge_center[1]), float(edge_center[2])) edge_axis_plan = model.circular_edge_axis_move_plan(edge_id, target_edge_center) _assert_edge_delegated_topology(edge_axis_plan) result = model.resize_cylindrical_boss(face_id, 8.0) _assert("verified_face=" in result, "boss diameter edit should report verified target Face") _assert( "first_level_topology_matched=True" in result, f"boss diameter edit should verify first-level topology: {result}", ) print("boss first-level topology verification passed") return 0 if __name__ == "__main__": raise SystemExit(main())