185 lines
7.5 KiB
Python
185 lines
7.5 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from pathlib import Path
|
||
|
|
import sys
|
||
|
|
import tempfile
|
||
|
|
|
||
|
|
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
|
||
|
|
|
||
|
|
|
||
|
|
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
|
||
|
|
|
||
|
|
|
||
|
|
DEFAULT_MODEL = PROJECT_ROOT / "assets" / "models" / "cube_10mm.step"
|
||
|
|
|
||
|
|
|
||
|
|
def _first_plane_face(model: StepModel) -> int:
|
||
|
|
for face_id in range(len(model.faces)):
|
||
|
|
info = model.face_info(face_id)
|
||
|
|
if info.get("surface") == "plane":
|
||
|
|
return face_id
|
||
|
|
raise SystemExit("no plane Face found")
|
||
|
|
|
||
|
|
|
||
|
|
def _first_shell_face(model: StepModel, thickness: float, tolerance: float) -> int:
|
||
|
|
candidates: list[tuple[int, int]] = []
|
||
|
|
for face_id in range(len(model.faces)):
|
||
|
|
info = model.feature_info(face_id)
|
||
|
|
if info.get("surface") != "plane":
|
||
|
|
continue
|
||
|
|
if info.get("shell_region_status") != "candidate":
|
||
|
|
continue
|
||
|
|
current = float(info.get("shell_thickness_estimate") or 0.0)
|
||
|
|
if abs(current - thickness) > tolerance:
|
||
|
|
continue
|
||
|
|
confidence_rank = {"high": 0, "medium": 1, "low": 2}.get(str(info.get("shell_confidence")), 3)
|
||
|
|
candidates.append((confidence_rank, face_id))
|
||
|
|
if not candidates:
|
||
|
|
raise SystemExit(f"no shell thickness candidate near {thickness:g}")
|
||
|
|
candidates.sort()
|
||
|
|
return candidates[0][1]
|
||
|
|
|
||
|
|
|
||
|
|
def _assert_extreme_blocked(label: str, plan: dict[str, object], expected_scale: float) -> None:
|
||
|
|
_assert_blocked(label, plan)
|
||
|
|
|
||
|
|
scale = (
|
||
|
|
plan.get("local_face_area_scale")
|
||
|
|
or plan.get("face_size_scale")
|
||
|
|
or plan.get("affine_scale")
|
||
|
|
)
|
||
|
|
if scale is None:
|
||
|
|
raise SystemExit(f"{label} plan did not expose the resulting scale: {plan}")
|
||
|
|
if abs(float(scale) - expected_scale) > max(abs(expected_scale) * 1e-6, 1e-9):
|
||
|
|
raise SystemExit(f"{label} scale mismatch: got={scale}, expected={expected_scale}")
|
||
|
|
|
||
|
|
|
||
|
|
def _assert_blocked(label: str, plan: dict[str, object]) -> None:
|
||
|
|
status = str(plan.get("status", ""))
|
||
|
|
message = str(plan.get("message") or plan.get("blockers") or "")
|
||
|
|
if status != "blocked":
|
||
|
|
raise SystemExit(f"{label} extreme target was not blocked: status={status}, message={message}")
|
||
|
|
|
||
|
|
|
||
|
|
def _run_cube_face_extreme_targets() -> None:
|
||
|
|
model = StepModel.load(DEFAULT_MODEL)
|
||
|
|
face_id = _first_plane_face(model)
|
||
|
|
info = model.face_info(face_id)
|
||
|
|
area = float(info.get("area") or 0.0)
|
||
|
|
width = float(info.get("local_face_width") or 0.0)
|
||
|
|
height = float(info.get("local_face_height") or 0.0)
|
||
|
|
if area <= 0 or width <= 0 or height <= 0:
|
||
|
|
raise SystemExit(f"selected Face is missing stable size values: area={area}, width={width}, height={height}")
|
||
|
|
current_center = info.get("area_center") or info.get("bbox_center")
|
||
|
|
if not isinstance(current_center, tuple) or len(current_center) != 3:
|
||
|
|
raise SystemExit("selected Face is missing a stable center")
|
||
|
|
|
||
|
|
_assert_extreme_blocked(
|
||
|
|
"Face area current face, too small",
|
||
|
|
model.face_area_local_resize_plan(face_id, area * 0.0001),
|
||
|
|
0.01,
|
||
|
|
)
|
||
|
|
_assert_extreme_blocked(
|
||
|
|
"Face area current face, too large",
|
||
|
|
model.face_area_local_resize_plan(face_id, area * 100.0),
|
||
|
|
10.0,
|
||
|
|
)
|
||
|
|
_assert_extreme_blocked(
|
||
|
|
"Face area owning feature, too small",
|
||
|
|
model.face_area_scale_plan(face_id, area * 0.0001),
|
||
|
|
0.01,
|
||
|
|
)
|
||
|
|
_assert_extreme_blocked(
|
||
|
|
"Face area owning feature, too large",
|
||
|
|
model.face_area_scale_plan(face_id, area * 100.0),
|
||
|
|
10.0,
|
||
|
|
)
|
||
|
|
|
||
|
|
for axis, current in (("width", width), ("height", height)):
|
||
|
|
_assert_extreme_blocked(
|
||
|
|
f"Face {axis} current face, too small",
|
||
|
|
model.face_size_local_resize_plan(face_id, current * 0.01, axis),
|
||
|
|
0.01,
|
||
|
|
)
|
||
|
|
_assert_extreme_blocked(
|
||
|
|
f"Face {axis} current face, too large",
|
||
|
|
model.face_size_local_resize_plan(face_id, current * 10.0, axis),
|
||
|
|
10.0,
|
||
|
|
)
|
||
|
|
_assert_extreme_blocked(
|
||
|
|
f"Face {axis} owning feature, too small",
|
||
|
|
model.face_size_owning_scale_plan(face_id, current * 0.01, axis),
|
||
|
|
0.01,
|
||
|
|
)
|
||
|
|
_assert_extreme_blocked(
|
||
|
|
f"Face {axis} owning feature, too large",
|
||
|
|
model.face_size_owning_scale_plan(face_id, current * 10.0, axis),
|
||
|
|
10.0,
|
||
|
|
)
|
||
|
|
|
||
|
|
far_center = (float(current_center[0]) + 500.0, float(current_center[1]), float(current_center[2]))
|
||
|
|
local_center_plan = model.face_center_local_move_plan(face_id, far_center)
|
||
|
|
_assert_blocked("Face center current face, too far", local_center_plan)
|
||
|
|
if float(local_center_plan.get("face_center_move_ratio") or 0.0) <= 5.0:
|
||
|
|
raise SystemExit(f"Face center current face ratio did not exceed the guard: {local_center_plan}")
|
||
|
|
|
||
|
|
owning_center_plan = model.face_center_owning_translation_plan(face_id, far_center)
|
||
|
|
_assert_blocked("Face center owning feature, too far", owning_center_plan)
|
||
|
|
if float(owning_center_plan.get("face_center_move_ratio") or 0.0) <= 5.0:
|
||
|
|
raise SystemExit(f"Face center owning feature ratio did not exceed the guard: {owning_center_plan}")
|
||
|
|
|
||
|
|
offset_distance = 500.0
|
||
|
|
local_offset_plan = model.face_plane_offset_local_plan(face_id, offset_distance)
|
||
|
|
_assert_blocked("Face offset current face, too large", local_offset_plan)
|
||
|
|
if float(local_offset_plan.get("face_center_move_ratio") or 0.0) <= 5.0:
|
||
|
|
raise SystemExit(f"Face offset current face ratio did not exceed the guard: {local_offset_plan}")
|
||
|
|
|
||
|
|
push_pull_plan = model.push_pull_plan(face_id, offset_distance)
|
||
|
|
_assert_blocked("Face offset push/pull, too large", push_pull_plan)
|
||
|
|
push_pull_ratio = offset_distance / max(float(push_pull_plan.get("bbox_diagonal") or 0.0), 1e-9)
|
||
|
|
if push_pull_ratio <= 5.0:
|
||
|
|
raise SystemExit(f"Face push/pull ratio did not exceed the guard: {push_pull_plan}")
|
||
|
|
|
||
|
|
owning_offset_plan = model.face_plane_offset_owning_translation_plan(face_id, offset_distance)
|
||
|
|
_assert_blocked("Face offset owning feature, too large", owning_offset_plan)
|
||
|
|
if float(owning_offset_plan.get("face_offset_distance_ratio") or 0.0) <= 5.0:
|
||
|
|
raise SystemExit(f"Face offset owning feature ratio did not exceed the guard: {owning_offset_plan}")
|
||
|
|
|
||
|
|
|
||
|
|
def _run_shell_extreme_targets() -> None:
|
||
|
|
with tempfile.TemporaryDirectory(prefix="geom_param_face_extreme_shell_") as temp_dir:
|
||
|
|
model_path = Path(temp_dir) / "plate.step"
|
||
|
|
_write_step(BRepPrimAPI_MakeBox(30.0, 20.0, 2.0).Shape(), model_path)
|
||
|
|
model = StepModel.load(model_path)
|
||
|
|
face_id = _first_shell_face(model, 2.0, 2e-4)
|
||
|
|
current = float(model.feature_info(face_id).get("shell_thickness_estimate") or 0.0)
|
||
|
|
if current <= 0:
|
||
|
|
raise SystemExit("shell thickness candidate is missing a positive thickness")
|
||
|
|
|
||
|
|
for target, scale in ((current * 0.01, 0.01), (current * 10.0, 10.0)):
|
||
|
|
_assert_blocked(
|
||
|
|
f"thin wall current face target scale={scale:g}",
|
||
|
|
model.shell_thickness_plan(face_id, target),
|
||
|
|
)
|
||
|
|
_assert_extreme_blocked(
|
||
|
|
f"thin wall owning feature target scale={scale:g}",
|
||
|
|
model.shell_thickness_owning_scale_plan(face_id, target),
|
||
|
|
scale,
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def main() -> int:
|
||
|
|
_run_cube_face_extreme_targets()
|
||
|
|
_run_shell_extreme_targets()
|
||
|
|
print("Face extreme target guards ok")
|
||
|
|
return 0
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
raise SystemExit(main())
|