feat: 完善 Face 参数化编辑和隔离执行
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
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())
|
||||
Reference in New Issue
Block a user