166 lines
7.2 KiB
Python
166 lines
7.2 KiB
Python
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
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
|
|
|
|
|
|
DEFAULT_MODEL = PROJECT_ROOT / "assets" / "models" / "cube_10mm.step"
|
|
TOLERANCE = 1e-5
|
|
|
|
|
|
def _plane_metrics(model: StepModel) -> list[tuple[int, float, float, float, tuple[float, float, float]]]:
|
|
metrics: list[tuple[int, float, float, float, tuple[float, float, float]]] = []
|
|
for face_id in range(len(model.faces)):
|
|
info = model.face_info(face_id)
|
|
if info.get("surface") != "plane":
|
|
continue
|
|
center = info.get("area_center") or info.get("bbox_center")
|
|
if not isinstance(center, tuple) or len(center) != 3:
|
|
continue
|
|
metrics.append(
|
|
(
|
|
face_id,
|
|
float(info.get("area") or 0.0),
|
|
float(info.get("local_face_width") or 0.0),
|
|
float(info.get("local_face_height") or 0.0),
|
|
(float(center[0]), float(center[1]), float(center[2])),
|
|
)
|
|
)
|
|
return metrics
|
|
|
|
|
|
def _first_plane_face_near_size(model: StepModel, width: float, height: float) -> int:
|
|
for face_id, _area, face_width, face_height, _center in _plane_metrics(model):
|
|
if abs(face_width - width) <= TOLERANCE and abs(face_height - height) <= TOLERANCE:
|
|
return face_id
|
|
raise SystemExit(f"no plane Face near {width:g} x {height:g}")
|
|
|
|
|
|
def _distance(a: tuple[float, float, float], b: tuple[float, float, float]) -> float:
|
|
return ((a[0] - b[0]) ** 2 + (a[1] - b[1]) ** 2 + (a[2] - b[2]) ** 2) ** 0.5
|
|
|
|
|
|
def _center(info: dict[str, object]) -> tuple[float, float, float]:
|
|
center = info.get("area_center") or info.get("bbox_center")
|
|
if not isinstance(center, tuple) or len(center) != 3:
|
|
raise SystemExit(f"Face lacks a stable center: {info}")
|
|
return float(center[0]), float(center[1]), float(center[2])
|
|
|
|
|
|
def _assert_logical_face(
|
|
model: StepModel,
|
|
logical_id: int,
|
|
label: str,
|
|
*,
|
|
area: float | None = None,
|
|
width: float | None = None,
|
|
height: float | None = None,
|
|
center: tuple[float, float, float] | None = None,
|
|
) -> int:
|
|
matches = model.face_ids_for_logical_id(logical_id)
|
|
if not matches:
|
|
raise SystemExit(f"{label}: logical Face {logical_id} was not retained")
|
|
resolved = model.resolve_face_selection_id(logical_id)
|
|
if resolved is None or resolved not in matches:
|
|
raise SystemExit(f"{label}: logical Face {logical_id} did not resolve into retained matches {matches}")
|
|
info = model.face_info(resolved)
|
|
if info.get("surface") != "plane":
|
|
raise SystemExit(f"{label}: retained logical Face should be planar, got {info.get('surface')}")
|
|
if area is not None and abs(float(info.get("area") or 0.0) - area) > TOLERANCE:
|
|
raise SystemExit(f"{label}: retained Face area should be {area:g}, got {info.get('area')}")
|
|
if width is not None and abs(float(info.get("local_face_width") or 0.0) - width) > TOLERANCE:
|
|
raise SystemExit(f"{label}: retained Face width should be {width:g}, got {info.get('local_face_width')}")
|
|
if height is not None and abs(float(info.get("local_face_height") or 0.0) - height) > TOLERANCE:
|
|
raise SystemExit(f"{label}: retained Face height should be {height:g}, got {info.get('local_face_height')}")
|
|
if center is not None and _distance(_center(info), center) > TOLERANCE:
|
|
raise SystemExit(f"{label}: retained Face center should be {center}, got {_center(info)}")
|
|
return resolved
|
|
|
|
|
|
def _fresh_selected_face() -> tuple[StepModel, int, int, dict[str, object]]:
|
|
model = StepModel.load(DEFAULT_MODEL)
|
|
face_id = _first_plane_face_near_size(model, 10.0, 10.0)
|
|
return model, face_id, model.face_region_logical_id(face_id), model.face_info(face_id)
|
|
|
|
|
|
def _target_center(info: dict[str, object], offset: tuple[float, float, float]) -> tuple[float, float, float]:
|
|
center = _center(info)
|
|
return center[0] + offset[0], center[1] + offset[1], center[2] + offset[2]
|
|
|
|
|
|
def _offset_target_center(model: StepModel, face_id: int, info: dict[str, object], distance: float) -> tuple[float, float, float]:
|
|
frame = model.face_plane_offset_frame(face_id)
|
|
if frame is None:
|
|
raise SystemExit("selected Face lacks a stable plane offset frame")
|
|
_origin, direction, _position = frame
|
|
center = _center(info)
|
|
return (
|
|
center[0] + direction[0] * distance,
|
|
center[1] + direction[1] * distance,
|
|
center[2] + direction[2] * distance,
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
model, face_id, logical_id, _info = _fresh_selected_face()
|
|
model.resize_face_size_local(face_id, 15.0, "width")
|
|
resolved = _assert_logical_face(model, logical_id, "local Face width", width=15.0, height=10.0)
|
|
print(f"local width logical {logical_id} -> Face {resolved}")
|
|
|
|
model, face_id, logical_id, _info = _fresh_selected_face()
|
|
model.resize_face_size_owning_scale(face_id, 15.0, "width")
|
|
resolved = _assert_logical_face(model, logical_id, "owning Face width", width=15.0, height=10.0)
|
|
print(f"owning width logical {logical_id} -> Face {resolved}")
|
|
|
|
model, face_id, logical_id, _info = _fresh_selected_face()
|
|
model.resize_face_area_local(face_id, 144.0)
|
|
resolved = _assert_logical_face(model, logical_id, "local Face area", area=144.0)
|
|
print(f"local area logical {logical_id} -> Face {resolved}")
|
|
|
|
model, face_id, logical_id, _info = _fresh_selected_face()
|
|
model.resize_face_area(face_id, 144.0)
|
|
resolved = _assert_logical_face(model, logical_id, "owning Face area", area=144.0)
|
|
print(f"owning area logical {logical_id} -> Face {resolved}")
|
|
|
|
model, face_id, logical_id, info = _fresh_selected_face()
|
|
center = _target_center(info, (2.0, 0.0, 3.0))
|
|
model.move_face_center_local(face_id, center)
|
|
resolved = _assert_logical_face(model, logical_id, "local Face center", center=center)
|
|
print(f"local center logical {logical_id} -> Face {resolved}")
|
|
|
|
model, face_id, logical_id, info = _fresh_selected_face()
|
|
center = _target_center(info, (2.0, 0.0, 3.0))
|
|
model.move_face_center_owning(face_id, center)
|
|
resolved = _assert_logical_face(model, logical_id, "owning Face center", center=center)
|
|
print(f"owning center logical {logical_id} -> Face {resolved}")
|
|
|
|
for label, operation, distance in (
|
|
("local Face plane offset", "local", 1.0),
|
|
("owning Face plane offset", "owning", 1.0),
|
|
("push/pull Face offset", "push_pull", 1.0),
|
|
("push/pull inward Face offset", "push_pull", -1.0),
|
|
):
|
|
model, face_id, logical_id, info = _fresh_selected_face()
|
|
target_center = _offset_target_center(model, face_id, info, distance)
|
|
if operation == "local":
|
|
model.move_face_plane_offset_local(face_id, distance)
|
|
elif operation == "owning":
|
|
model.translate_face_plane_offset_owning(face_id, distance)
|
|
else:
|
|
model.push_pull_face(face_id, distance)
|
|
resolved = _assert_logical_face(model, logical_id, label, center=target_center)
|
|
print(f"{label} logical {logical_id} -> Face {resolved}")
|
|
|
|
print("Face logical selection retention ok")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|