feat: 完善参数化编辑语义和槽孔中心距
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
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
|
||||
|
||||
|
||||
def _write_plate_model(path: Path) -> None:
|
||||
shape = BRepPrimAPI_MakeBox(30.0, 20.0, 2.0).Shape()
|
||||
_write_step(shape, path)
|
||||
|
||||
|
||||
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 _bounds(model: StepModel) -> tuple[tuple[float, float, float], tuple[float, float, float], tuple[float, float, float]]:
|
||||
info = model.geometry_stats()
|
||||
return (
|
||||
tuple(float(value) for value in info["bbox_min"]),
|
||||
tuple(float(value) for value in info["bbox_max"]),
|
||||
tuple(float(value) for value in info["bbox_size"]),
|
||||
)
|
||||
|
||||
|
||||
def _thickness_axis(size: tuple[float, float, float]) -> int:
|
||||
return min(range(3), key=lambda index: size[index])
|
||||
|
||||
|
||||
def _verify_local_bounds(
|
||||
before_min: tuple[float, float, float],
|
||||
before_max: tuple[float, float, float],
|
||||
after_min: tuple[float, float, float],
|
||||
after_max: tuple[float, float, float],
|
||||
axis: int,
|
||||
target: float,
|
||||
tolerance: float,
|
||||
) -> None:
|
||||
after_size = after_max[axis] - after_min[axis]
|
||||
if abs(after_size - target) > tolerance:
|
||||
raise SystemExit(f"local shell thickness failed: target={target:g}, value={after_size:g}")
|
||||
min_unchanged = abs(after_min[axis] - before_min[axis]) <= tolerance
|
||||
max_unchanged = abs(after_max[axis] - before_max[axis]) <= tolerance
|
||||
if not (min_unchanged or max_unchanged):
|
||||
raise SystemExit(
|
||||
"local shell thickness did not keep either opposite side fixed: "
|
||||
f"before=({before_min[axis]:g}, {before_max[axis]:g}), "
|
||||
f"after=({after_min[axis]:g}, {after_max[axis]:g})"
|
||||
)
|
||||
|
||||
|
||||
def _verify_owning_bounds(
|
||||
before_min: tuple[float, float, float],
|
||||
before_max: tuple[float, float, float],
|
||||
after_min: tuple[float, float, float],
|
||||
after_max: tuple[float, float, float],
|
||||
axis: int,
|
||||
target: float,
|
||||
tolerance: float,
|
||||
) -> None:
|
||||
after_size = after_max[axis] - after_min[axis]
|
||||
if abs(after_size - target) > tolerance:
|
||||
raise SystemExit(f"owning shell thickness failed: target={target:g}, value={after_size:g}")
|
||||
before_center = (before_min[axis] + before_max[axis]) * 0.5
|
||||
after_center = (after_min[axis] + after_max[axis]) * 0.5
|
||||
if abs(after_center - before_center) > tolerance:
|
||||
raise SystemExit(
|
||||
"owning shell thickness did not keep the thickness center fixed: "
|
||||
f"before_center={before_center:g}, after_center={after_center:g}"
|
||||
)
|
||||
|
||||
|
||||
def _run_case(mode: str, source_thickness: float, target_thickness: float, tolerance: float) -> None:
|
||||
with tempfile.TemporaryDirectory(prefix=f"geom_param_shell_{mode}_") as temp_dir:
|
||||
model_path = Path(temp_dir) / "plate.step"
|
||||
_write_plate_model(model_path)
|
||||
model = StepModel.load(model_path)
|
||||
face_id = _first_shell_face(model, source_thickness, tolerance)
|
||||
before = model.stats()
|
||||
before_min, before_max, before_size = _bounds(model)
|
||||
axis = _thickness_axis(before_size)
|
||||
|
||||
if mode == "local":
|
||||
plan = model.shell_thickness_plan(face_id, target_thickness)
|
||||
if plan["status"] == "blocked":
|
||||
raise SystemExit(f"local shell plan was blocked: {plan['message']}")
|
||||
result = model.resize_shell_thickness(face_id, target_thickness)
|
||||
elif mode == "owning":
|
||||
plan = model.shell_thickness_owning_scale_plan(face_id, target_thickness)
|
||||
if plan["status"] == "blocked":
|
||||
raise SystemExit(f"owning shell plan was blocked: {plan['message']}")
|
||||
result = model.resize_shell_thickness_owning_scale(face_id, target_thickness)
|
||||
else:
|
||||
raise SystemExit(f"unsupported mode: {mode}")
|
||||
|
||||
after = model.stats()
|
||||
after_min, after_max, after_size = _bounds(model)
|
||||
if after.solids != before.solids:
|
||||
raise SystemExit(f"{mode} shell thickness changed solid count: before={before.solids}, after={after.solids}")
|
||||
if mode == "local":
|
||||
_verify_local_bounds(before_min, before_max, after_min, after_max, axis, target_thickness, tolerance)
|
||||
else:
|
||||
_verify_owning_bounds(before_min, before_max, after_min, after_max, axis, target_thickness, tolerance)
|
||||
|
||||
print(f"mode={mode}")
|
||||
print(f"face_id={face_id}")
|
||||
print(f"strategy={plan.get('resize_strategy')}")
|
||||
print(f"before={before}")
|
||||
print(f"after={after}")
|
||||
print(f"before_bbox_size={before_size}")
|
||||
print(f"after_bbox_size={after_size}")
|
||||
print(f"axis={axis}")
|
||||
print(f"source_thickness={source_thickness:.6f} target_thickness={target_thickness:.6f}")
|
||||
print(result.encode("ascii", "backslashreplace").decode("ascii"))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Verify shell/thin-wall thickness edit operations.")
|
||||
parser.add_argument("--mode", default="all", choices=["all", "local", "owning"])
|
||||
parser.add_argument("--source-thickness", type=float, default=2.0)
|
||||
parser.add_argument("--target-thickness", type=float, default=3.0)
|
||||
parser.add_argument("--tolerance", type=float, default=2e-4)
|
||||
args = parser.parse_args()
|
||||
|
||||
modes = ["local", "owning"] if args.mode == "all" else [args.mode]
|
||||
for mode in modes:
|
||||
_run_case(mode, args.source_thickness, args.target_thickness, args.tolerance)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user