from __future__ import annotations import sys import tempfile from pathlib import Path from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder from OCC.Core.gp import gp_Ax2, gp_Dir, gp_Pnt 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.geometry_utils import _finalize_boolean_result from step_editor.model import StepModel from step_editor.step_io import _write_step def _write_holed_plate(path: Path) -> None: plate = BRepPrimAPI_MakeBox(30.0, 20.0, 8.0).Shape() axis = gp_Ax2(gp_Pnt(15.0, 10.0, -1.0), gp_Dir(0.0, 0.0, 1.0)) cutter = BRepPrimAPI_MakeCylinder(axis, 3.0, 10.0).Shape() cut = BRepAlgoAPI_Cut(plate, cutter) shape = _finalize_boolean_result(cut, "verify holed planar face guard cut") _write_step(shape, path) def _first_holed_plane_face(model: StepModel) -> int: for face_id in range(len(model.faces)): info = model.face_info(face_id) if info.get("surface") != "plane": continue if bool(info.get("has_inner_boundaries")): return face_id raise SystemExit("no planar Face with an inner boundary was found") def _assert_blocked(plan: dict[str, object], label: str) -> None: if plan.get("status") != "blocked": raise SystemExit(f"{label} should be blocked for a holed planar Face: {plan}") def _assert_push_pull_keeps_single_solid(path: Path, distance: float) -> str: model = StepModel.load(path) face_id = _first_holed_plane_face(model) plan = model.push_pull_plan(face_id, distance) if plan.get("status") == "blocked": raise SystemExit(f"holed planar Face push/pull should remain available: {plan}") result = model.push_pull_face(face_id, distance) stats = model.stats() if stats.solids != 1: raise SystemExit(f"holed planar Face push/pull should keep one solid: {stats}") if "inner_wires=" not in result: raise SystemExit(f"holed planar Face push/pull should report inner-wire verification: {result}") target_position = plan.get("target_plane_position") direction = plan.get("plane_direction") or plan.get("outward_direction") if not isinstance(target_position, (int, float)) or not isinstance(direction, tuple) or len(direction) != 3: raise SystemExit(f"holed planar Face push/pull plan should expose a target plane position: {plan}") best: tuple[float, int, dict[str, object]] | None = None for candidate_id in range(len(model.faces)): info = model.face_info(candidate_id) if info.get("surface") != "plane": continue origin = info.get("plane_origin") if not isinstance(origin, tuple) or len(origin) != 3: continue actual_position = ( float(origin[0]) * float(direction[0]) + float(origin[1]) * float(direction[1]) + float(origin[2]) * float(direction[2]) ) error = abs(actual_position - float(target_position)) if best is None or error < best[0]: best = (error, candidate_id, info) if best is None: raise SystemExit("holed planar Face push/pull should leave a measurable target plane") _error, matched_face_id, matched_info = best if int(matched_info.get("inner_boundary_wires") or 0) < 1: raise SystemExit( "holed planar Face push/pull should preserve the target Face inner boundary: " f"matched_face={matched_face_id}, info={matched_info}, result={result}" ) return result def main() -> int: with tempfile.TemporaryDirectory(prefix="geom_param_face_boundary_") as temp_dir: path = Path(temp_dir) / "holed_plate.step" _write_holed_plate(path) model = StepModel.load(path) face_id = _first_holed_plane_face(model) info = model.face_info(face_id) if int(info.get("boundary_wires") or 0) < 2: raise SystemExit(f"Face {face_id} should report at least two boundary wires: {info}") if bool(info.get("local_face_deform_ready", True)): raise SystemExit(f"Face {face_id} should not allow local Face deformation: {info}") solid_raw = info.get("solid_id") solid_id = int(solid_raw) if solid_raw is not None else -1 if solid_id < 0 or solid_id not in model._local_face_deform_readiness_cache: raise SystemExit("local Face deformation readiness should be cached after face_info()") other_face_id = next( (item for item, item_solid_id in enumerate(model.face_solid_ids) if item_solid_id == solid_id and item != face_id), None, ) if other_face_id is None: raise SystemExit("holed plate should expose another Face on the same Solid") original_vertex_reader = model._local_deform_face_vertex_points def fail_on_cache_miss(*_args: object, **_kwargs: object) -> list[tuple[float, float, float]]: raise AssertionError("local Face readiness cache was not reused") model._local_deform_face_vertex_points = fail_on_cache_miss # type: ignore[method-assign] try: cached_readiness = model._local_face_deform_readiness(other_face_id) finally: model._local_deform_face_vertex_points = original_vertex_reader # type: ignore[method-assign] if bool(cached_readiness.get("local_face_deform_ready", True)): raise SystemExit(f"cached readiness should keep local deformation disabled: {cached_readiness}") center = info.get("area_center") or info.get("bbox_center") if not isinstance(center, tuple) or len(center) != 3: raise SystemExit(f"Face {face_id} does not expose a stable center") target_center = (float(center[0]), float(center[1]), float(center[2]) + 1.0) area = float(info.get("area") or 0.0) if area <= 0: raise SystemExit(f"Face {face_id} does not expose a stable area") width = float(info.get("local_face_width") or 0.0) if width <= 0: raise SystemExit(f"Face {face_id} does not expose a measured Face width") height = float(info.get("local_face_height") or 0.0) if height <= 0: raise SystemExit(f"Face {face_id} does not expose a measured Face height") _assert_blocked(model.face_center_local_move_plan(face_id, target_center), "Face center local move") _assert_blocked(model.face_area_local_resize_plan(face_id, area * 1.1), "Face area local resize") _assert_blocked(model.face_size_local_resize_plan(face_id, width * 1.1, axis="width"), "Face width local resize") _assert_blocked(model.face_size_local_resize_plan(face_id, height * 1.1, axis="height"), "Face height local resize") _assert_blocked(model.face_plane_offset_local_plan(face_id, 1.0), "Face plane offset local move") outward_result = _assert_push_pull_keeps_single_solid(path, 1.0) inward_result = _assert_push_pull_keeps_single_solid(path, -1.0) print( "holed planar Face local edit guard ok: " f"face_id={face_id}, boundary_wires={info.get('boundary_wires')}, " f"blocker={info.get('local_face_deform_blocker')}, " f"push_pull_outward={outward_result}, push_pull_inward={inward_result}" ) return 0 if __name__ == "__main__": raise SystemExit(main())