feat: 完善 Face 参数化编辑和隔离执行

This commit is contained in:
2026-07-31 16:36:05 +08:00
parent 27e4f7236c
commit bb44e3920d
31 changed files with 5428 additions and 252 deletions
@@ -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())