from __future__ import annotations from pathlib import Path import sys import tempfile from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox 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.window_state import WindowStateMixin, _feature_dimension_keys from step_editor.step_io import _write_step DEFAULT_MODEL = PROJECT_ROOT / "assets" / "models" / "cube_10mm.step" def close_to(value: object, expected: float, tolerance: float = 1e-6) -> bool: try: return abs(float(value) - expected) <= tolerance except (TypeError, ValueError): return False def find_feature(model: StepModel, feature_type: str) -> dict[str, object]: matches = [model.feature_info(face_id) for face_id in range(len(model.faces))] matches = [info for info in matches if info.get("feature_type") == feature_type] if not matches: observed = sorted({str(model.feature_info(face_id).get("feature_type")) for face_id in range(len(model.faces))}) raise AssertionError(f"expected {feature_type}, observed {observed}") return matches[0] def assert_prismatic_sizes( info: dict[str, object], length: float, width: float, depth: float ) -> None: expected = { "prismatic_length": length, "prismatic_width": width, "prismatic_extrusion_estimate": depth, } for key, value in expected.items(): if not close_to(info.get(key), value): raise AssertionError(f"{key}={info.get(key)!r}, expected {value}") def main() -> int: model = StepModel.load(DEFAULT_MODEL) if len(model.faces) != 6: raise AssertionError(f"expected 6 cube faces, got {len(model.faces)}") for face_id in range(len(model.faces)): info = model.feature_info(face_id) if info.get("prismatic_profile_status") != "candidate": raise AssertionError(f"Face {face_id} was not recognized as a rectangular profile") if info.get("prismatic_extrusion_status") != "candidate": raise AssertionError(f"Face {face_id} was not recognized as a connected extrusion") for key in ("prismatic_length", "prismatic_width", "prismatic_extrusion_estimate"): if not close_to(info.get(key), 10.0): raise AssertionError(f"Face {face_id} {key}={info.get(key)!r}, expected 10") if len(tuple(info.get("prismatic_connected_side_face_ids", ()))) != 4: raise AssertionError(f"Face {face_id} does not have four connected side faces") info = model.feature_info(0) expected_keys = ("local_face_width", "local_face_height", "shell_thickness_estimate") if _feature_dimension_keys(info) != expected_keys: raise AssertionError(f"unexpected cube feature dimensions: {_feature_dimension_keys(info)}") state = object.__new__(WindowStateMixin) state.model = model state.operation_in_progress = False state.scan_in_progress = False state.load_in_progress = False state.selected_face_id = 0 state.selected_edge_id = None state.selected_kind = "feature" state.selected_part_id = int(info["part_id"]) state.selected_solid_id = int(info["solid_id"]) specs, _used = state._editable_property_specs(info) filtered = state._feature_property_specs(specs, info) dimensions = [spec for spec in filtered if spec.get("parameter_role") == "dimension"] actual = tuple( ( str(spec.get("key")), str(spec.get("label")), str(spec.get("current_text")), ) for spec in dimensions ) expected = ( ("local_face_width", "长度", "10"), ("local_face_height", "宽度", "10"), ("shell_thickness_estimate", "高度/深度", "10"), ) if actual != expected: raise AssertionError(f"unexpected prismatic UI parameters: {actual}") with tempfile.TemporaryDirectory(prefix="step-editor-prismatic-") as temp_dir: temp_path = Path(temp_dir) base = BRepPrimAPI_MakeBox(30.0, 20.0, 5.0).Shape() pocket_tool = BRepPrimAPI_MakeBox(gp_Pnt(10.0, 6.0, 2.0), 10.0, 8.0, 4.0).Shape() pocket_path = temp_path / "rectangular-pocket.step" _write_step(BRepAlgoAPI_Cut(base, pocket_tool).Shape(), pocket_path) pocket_info = find_feature(StepModel.load(pocket_path), "矩形口袋候选") assert_prismatic_sizes(pocket_info, 10.0, 8.0, 3.0) if pocket_info.get("prismatic_reference_source") != "side-wall-topology": raise AssertionError(f"unexpected pocket reference: {pocket_info.get('prismatic_reference_source')}") boss_tool = BRepPrimAPI_MakeBox(gp_Pnt(10.0, 6.0, 5.0), 10.0, 8.0, 3.0).Shape() boss_path = temp_path / "rectangular-boss.step" _write_step(BRepAlgoAPI_Fuse(base, boss_tool).Shape(), boss_path) boss_info = find_feature(StepModel.load(boss_path), "矩形凸台候选") assert_prismatic_sizes(boss_info, 10.0, 8.0, 3.0) if boss_info.get("prismatic_reference_source") not in { "side-wall-topology", "overlapping-plane" }: raise AssertionError(f"unexpected boss reference: {boss_info.get('prismatic_reference_source')}") print("prismatic feature recognition ok") return 0 if __name__ == "__main__": raise SystemExit(main())