feat: 完善 Face 参数化编辑和隔离执行
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeCylinder
|
||||
|
||||
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
|
||||
from step_editor.window_state import WindowStateMixin
|
||||
|
||||
|
||||
class _PropertySpecProbe(WindowStateMixin):
|
||||
def __init__(self, face_id: int, info: dict[str, object]) -> None:
|
||||
self.model = object()
|
||||
self.operation_in_progress = False
|
||||
self.scan_in_progress = False
|
||||
self.load_in_progress = False
|
||||
self.selected_face_id = face_id
|
||||
self.selected_edge_id = None
|
||||
self.selected_kind = "face"
|
||||
self.selected_part_id = int(info.get("part_id", 1))
|
||||
self.selected_solid_id = int(info.get("solid_id", 1))
|
||||
self.manual_bottom_face_id = None
|
||||
self.manual_slot_pair_face_id = None
|
||||
|
||||
|
||||
def _write_cylinder(path: Path) -> None:
|
||||
cylinder = BRepPrimAPI_MakeCylinder(4.0, 8.0).Shape()
|
||||
_write_step(cylinder, path)
|
||||
|
||||
|
||||
def _first_planar_cap_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 planar cylinder cap Face was found")
|
||||
|
||||
|
||||
def _assert_blocked(plan: dict[str, object], label: str) -> None:
|
||||
if plan.get("status") != "blocked":
|
||||
raise SystemExit(f"{label} should be blocked for a planar Face on a curved Solid: {plan}")
|
||||
message = str(plan.get("message") or plan.get("blockers") or "")
|
||||
if "曲面" not in message:
|
||||
raise SystemExit(f"{label} should explain that the owning Solid contains curved faces: {plan}")
|
||||
|
||||
|
||||
def _specs(face_id: int, info: dict[str, object]) -> list[dict[str, object]]:
|
||||
probe = _PropertySpecProbe(face_id, info)
|
||||
specs, _used = probe._editable_property_specs(info)
|
||||
return specs
|
||||
|
||||
|
||||
def _spec(specs: list[dict[str, object]], key: str) -> dict[str, object]:
|
||||
for item in specs:
|
||||
if item.get("key") == key:
|
||||
return item
|
||||
raise SystemExit(f"{key} spec was not found")
|
||||
|
||||
|
||||
def _scope_mode(specs: list[dict[str, object]], key: str, mode: str) -> dict[str, object]:
|
||||
spec = _spec(specs, key)
|
||||
modes = spec.get("scope_modes")
|
||||
if not isinstance(modes, dict) or mode not in modes:
|
||||
raise SystemExit(f"{key} has no scope mode {mode}")
|
||||
selected = modes[mode]
|
||||
if not isinstance(selected, dict):
|
||||
raise SystemExit(f"{key} scope mode {mode} is invalid")
|
||||
return selected
|
||||
|
||||
|
||||
def _assert_local_scope_explains_curved_owner(specs: list[dict[str, object]], key: str) -> None:
|
||||
local_mode = _scope_mode(specs, key, "local")
|
||||
if bool(local_mode.get("enabled", True)):
|
||||
raise SystemExit(f"{key}/local should be disabled for a planar Face on a curved Solid")
|
||||
disabled_tip = str(local_mode.get("disabled_tip") or "")
|
||||
if "曲面" not in disabled_tip:
|
||||
raise SystemExit(f"{key}/local disabled tip should mention curved owner: {disabled_tip}")
|
||||
|
||||
|
||||
def _bbox_height(model: StepModel) -> float:
|
||||
info = model.part_info(1)
|
||||
bbox_size = info.get("bbox_size")
|
||||
if not isinstance(bbox_size, tuple) or len(bbox_size) != 3:
|
||||
raise SystemExit(f"part bbox_size is missing: {info}")
|
||||
return float(bbox_size[2])
|
||||
|
||||
|
||||
def main() -> int:
|
||||
with tempfile.TemporaryDirectory(prefix="geom_param_face_mixed_surface_") as temp_dir:
|
||||
path = Path(temp_dir) / "cylinder.step"
|
||||
_write_cylinder(path)
|
||||
model = StepModel.load(path)
|
||||
face_id = _first_planar_cap_face(model)
|
||||
info = model.face_info(face_id)
|
||||
|
||||
if bool(info.get("local_face_deform_ready", True)):
|
||||
raise SystemExit(f"planar cylinder cap should not allow local Face deformation: {info}")
|
||||
blocker = str(info.get("local_face_deform_blocker") or "")
|
||||
if "曲面" not in blocker:
|
||||
raise SystemExit(f"planar cylinder cap blocker should mention curved owner: {info}")
|
||||
|
||||
center = info.get("area_center") or info.get("bbox_center")
|
||||
if not isinstance(center, tuple) or len(center) != 3:
|
||||
raise SystemExit(f"Face {face_id} does not expose a stable center")
|
||||
target_center = (float(center[0]), float(center[1]), float(center[2]) + 1.0)
|
||||
area = float(info.get("area") or 0.0)
|
||||
if area <= 0:
|
||||
raise SystemExit(f"Face {face_id} does not expose a stable area")
|
||||
|
||||
_assert_blocked(model.face_center_local_move_plan(face_id, target_center), "Face center local move")
|
||||
_assert_blocked(model.face_area_local_resize_plan(face_id, area * 1.1), "Face area local resize")
|
||||
_assert_blocked(model.face_plane_offset_local_plan(face_id, 1.0), "Face plane offset local move")
|
||||
|
||||
specs = _specs(face_id, info)
|
||||
semantics = _spec(specs, "face_edit_semantics")
|
||||
if "不能只改当前面" not in str(semantics.get("current_text") or ""):
|
||||
raise SystemExit(f"Face edit semantics should summarize the local blocker: {semantics}")
|
||||
if "曲面" not in str(semantics.get("disabled_tip") or ""):
|
||||
raise SystemExit(f"Face edit semantics tip should include the curved-owner blocker: {semantics}")
|
||||
for key in ("area", "face_center_position", "face_target_normal_position"):
|
||||
_assert_local_scope_explains_curved_owner(specs, key)
|
||||
push_pull_mode = _scope_mode(specs, "face_target_normal_position", "push_pull")
|
||||
if not bool(push_pull_mode.get("enabled", False)):
|
||||
raise SystemExit("planar cylinder cap push/pull scope should remain available")
|
||||
|
||||
before_height = _bbox_height(model)
|
||||
push_plan = model.push_pull_plan(face_id, 1.0)
|
||||
if push_plan.get("status") == "blocked":
|
||||
raise SystemExit(f"planar cylinder cap push/pull should remain available: {push_plan}")
|
||||
push_result = model.push_pull_face(face_id, 1.0)
|
||||
stats = model.stats()
|
||||
if stats.solids != 1:
|
||||
raise SystemExit(f"planar cylinder cap push/pull should keep one solid: {stats}")
|
||||
after_height = _bbox_height(model)
|
||||
if after_height <= before_height:
|
||||
raise SystemExit(f"planar cylinder cap push/pull should increase bbox height: {before_height} -> {after_height}")
|
||||
|
||||
print(
|
||||
"mixed-surface planar Face guard ok: "
|
||||
f"face_id={face_id}, blocker={blocker}, height={before_height:g}->{after_height:g}, "
|
||||
f"push_pull={push_result}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user