from __future__ import annotations import argparse from pathlib import Path import sys import tempfile from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeFace, BRepBuilderAPI_MakeWire from OCC.Core.gp import gp_Ax2, gp_Dir, gp_Elips, 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.model import StepModel from step_editor.step_io import _write_step def _write_ellipse_face_model(path: Path) -> None: edge = BRepBuilderAPI_MakeEdge( gp_Elips(gp_Ax2(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0)), 5.0, 2.0) ).Edge() wire = BRepBuilderAPI_MakeWire(edge).Wire() face = BRepBuilderAPI_MakeFace(wire).Face() _write_step(face, path) def _ellipse_edge_ids(model: StepModel) -> list[int]: return [edge_id for edge_id in range(len(model.edges)) if model.edge_info(edge_id).get("curve") == "ellipse"] def _run_case(axis_kind: str, target: float, expected_other: float, tolerance: float) -> None: with tempfile.TemporaryDirectory(prefix=f"geom_param_ellipse_{axis_kind}_") as temp_dir: model_path = Path(temp_dir) / "ellipse.step" _write_ellipse_face_model(model_path) model = StepModel.load(model_path) edge_ids = _ellipse_edge_ids(model) if not edge_ids: raise SystemExit("no ellipse Edge was recognized") edge_id = edge_ids[0] before = model.stats() plan = model.ellipse_edge_axis_radius_plan(edge_id, target, axis_kind=axis_kind) if plan["status"] == "blocked": raise SystemExit(f"{axis_kind} radius plan was blocked: {plan['message']}") expected_strategy = f"ellipse-edge-{axis_kind}-axis-affine" if plan.get("resize_strategy") != expected_strategy: raise SystemExit(f"expected {expected_strategy}, got {plan.get('resize_strategy')}") result = model.resize_ellipse_edge_axis_radius(edge_id, target, axis_kind=axis_kind) after = model.stats() sampled = model._ellipse_edge_axis_radius_sampled_result(plan) if sampled is None: raise SystemExit("could not sample the edited ellipse-like Edge") verified_edge, value, other, error = sampled other_error = abs(other - expected_other) if error > tolerance: raise SystemExit(f"{axis_kind} radius verification failed: target={target:g}, value={value:g}, error={error:g}") if other_error > tolerance: raise SystemExit( f"{axis_kind} radius changed the other radius too much: expected={expected_other:g}, " f"value={other:g}, error={other_error:g}" ) print(f"mode={axis_kind}") print(f"source_edge={edge_id}") print(f"strategy={plan.get('resize_strategy')}") print(f"before={before}") print(f"after={after}") print(f"verified_edge={verified_edge}") print(f"target={target:.6f} value={value:.6f} error={error:.6g}") print(f"other_radius={other:.6f} other_error={other_error:.6g}") print(result.encode("ascii", "backslashreplace").decode("ascii")) def main() -> int: parser = argparse.ArgumentParser(description="Verify ellipse Edge major/minor radius resize operations.") parser.add_argument("--mode", default="all", choices=["all", "major", "minor"]) parser.add_argument("--major-radius", type=float, default=7.5) parser.add_argument("--minor-radius", type=float, default=3.0) parser.add_argument("--tolerance", type=float, default=5e-3) args = parser.parse_args() if args.mode in {"all", "major"}: _run_case("major", args.major_radius, expected_other=2.0, tolerance=args.tolerance) if args.mode in {"all", "minor"}: _run_case("minor", args.minor_radius, expected_other=5.0, tolerance=args.tolerance) return 0 if __name__ == "__main__": raise SystemExit(main())