from __future__ import annotations import sys import tempfile from pathlib import Path from OCC.Core.BRepAdaptor import BRepAdaptor_Surface from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace from OCC.Core.GeomAbs import GeomAbs_BSplineSurface from OCC.Core.GeomAPI import GeomAPI_PointsToBSplineSurface from OCC.Core.TColgp import TColgp_Array2OfPnt from OCC.Core.gp import 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 from step_editor.window_state import WindowStateMixin class _PropertySpecProbe(WindowStateMixin): def __init__(self, face_id: int, selected_kind: str = "face") -> None: self.model = object() self.operation_in_progress = False self.scan_in_progress = False self.load_in_progress = False self.selected_face_id = face_id self.selected_edge_id = None self.selected_kind = selected_kind self.selected_part_id = 1 self.selected_solid_id = -1 self.manual_bottom_face_id = None self.manual_slot_pair_face_id = None def _write_b_spline_patch(path: Path) -> None: points = TColgp_Array2OfPnt(1, 4, 1, 4) for row in range(1, 5): x = float(row - 1) * 10.0 for column in range(1, 5): y = float(column - 1) * 10.0 z = 1.2 * ((row - 2.5) ** 2 - (column - 2.5) ** 2) points.SetValue(row, column, gp_Pnt(x, y, z)) surface = GeomAPI_PointsToBSplineSurface(points).Surface() face = BRepBuilderAPI_MakeFace(surface, 1e-6).Face() _write_step(face, path) def _freeform_face_id(model: StepModel) -> int: for face_id, face in enumerate(model.faces): surf = BRepAdaptor_Surface(face) if surf.GetType() == GeomAbs_BSplineSurface: return face_id raise SystemExit("B-spline Face was not found") def _specs(info: dict[str, object], selected_kind: str = "face") -> list[dict[str, object]]: probe = _PropertySpecProbe(int(info.get("face_id", 0)), selected_kind=selected_kind) specs, _used = probe._editable_property_specs(info) return specs def _property_rows(info: dict[str, object], selected_kind: str = "face") -> list[dict[str, object]]: probe = _PropertySpecProbe(int(info.get("face_id", 0)), selected_kind=selected_kind) return probe._property_editor_specs(info, info) def _assert_no_editable_actions(specs: list[dict[str, object]], label: str) -> None: editable = [ str(spec.get("key")) for spec in specs if bool(spec.get("editable")) and bool(spec.get("enabled")) and bool(spec.get("action")) ] if editable: raise SystemExit(f"{label} should not expose editable actions for a freeform Face: {editable}") def main() -> int: with tempfile.TemporaryDirectory(prefix="geom_param_freeform_guard_") as temp_dir: path = Path(temp_dir) / "freeform_patch.step" _write_b_spline_patch(path) model = StepModel.load(path) face_id = _freeform_face_id(model) quick_info = model.quick_face_info(face_id) info = model.face_info(face_id) for label, selected_info in (("quick", quick_info), ("full", info)): if selected_info.get("surface") != "b-spline surface": raise SystemExit(f"{label} info should identify a B-spline surface: {selected_info}") if selected_info.get("freeform_face_status") != "blocked": raise SystemExit(f"{label} info should block freeform Face editing: {selected_info}") if selected_info.get("recognition_risk") != "blocked": raise SystemExit(f"{label} recognition risk should be blocked: {selected_info}") blockers = str(selected_info.get("freeform_face_blockers") or "") if "自由曲面" not in blockers or "历史参数" not in blockers: raise SystemExit(f"{label} blocker should explain the missing stable history parameter: {selected_info}") editable_specs = _specs(info) editable_keys = {str(spec.get("key")) for spec in editable_specs} if "freeform_surface_edit_semantics" not in editable_keys: raise SystemExit(f"freeform Face should expose a read-only edit semantics row: {editable_specs}") forbidden = { "area", "local_face_width", "local_face_height", "face_center_position", "face_target_normal_position", "cone_reference_radius", "sphere_radius", "torus_major_radius", } leaked = sorted(forbidden & editable_keys) if leaked: raise SystemExit(f"freeform Face should not expose parametric edit rows: {leaked}") _assert_no_editable_actions(editable_specs, "face mode editable specs") feature_rows = _property_rows(info, selected_kind="feature") feature_keys = {str(spec.get("key")) for spec in feature_rows} if "no_editable_feature_dimensions" not in feature_keys: raise SystemExit(f"feature mode should say there are no reliable dimensions: {feature_rows}") if "freeform_surface_edit_semantics" in feature_keys: raise SystemExit( "feature parameter table should keep freeform limitations in diagnostics, " f"not mixed into editable parameters: {feature_rows}" ) _assert_no_editable_actions(feature_rows, "feature mode rows") print( "freeform Face guard ok: " f"face_id={face_id}, surface={info.get('surface')}, " f"risk={info.get('recognition_risk')}, specs={sorted(editable_keys)}" ) return 0 if __name__ == "__main__": raise SystemExit(main())