536 lines
23 KiB
Python
536 lines
23 KiB
Python
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
import tempfile
|
|
|
|
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut
|
|
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCone, BRepPrimAPI_MakeSphere
|
|
from OCC.Core.gp import gp_Ax2, gp_Dir, gp_Pnt
|
|
|
|
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_types import TopologyStats
|
|
from step_editor.geometry_utils import _finalize_boolean_result
|
|
from step_editor.model import StepModel
|
|
from step_editor.step_io import _write_step
|
|
from step_editor.window_actions import WindowActionMixin
|
|
from verify_face_coplanar_push_pull import _top_faces, _write_split_top_box # noqa: E402
|
|
|
|
|
|
class _Probe(WindowActionMixin):
|
|
pass
|
|
|
|
|
|
def _face_context(strategy: str = "local-face-area-only-deform") -> dict[str, object]:
|
|
return {
|
|
"operation_name": "Face integrity guard probe",
|
|
"target_kind": "face",
|
|
"target_id": 0,
|
|
"parameters": {"resize_strategy": strategy},
|
|
}
|
|
|
|
|
|
def _edge_context() -> dict[str, object]:
|
|
return {
|
|
"operation_name": "Edge integrity guard probe",
|
|
"target_kind": "edge",
|
|
"target_id": 0,
|
|
"parameters": {"resize_strategy": "local-edge-length-deform"},
|
|
}
|
|
|
|
|
|
def _expect_blocked(label: str, callback) -> None:
|
|
try:
|
|
callback()
|
|
except RuntimeError as exc:
|
|
print(f"{label}: blocked as expected: {exc}")
|
|
return
|
|
raise SystemExit(f"{label}: expected a RuntimeError")
|
|
|
|
|
|
def _expect_allowed(label: str, callback) -> list[str]:
|
|
try:
|
|
warnings = callback()
|
|
except RuntimeError as exc:
|
|
raise SystemExit(f"{label}: should have been allowed, got {exc}") from exc
|
|
print(f"{label}: allowed, warnings={len(warnings)}")
|
|
return warnings
|
|
|
|
|
|
def _write_embedded_countersink(path: Path) -> None:
|
|
block = BRepPrimAPI_MakeBox(30.0, 30.0, 10.0).Shape()
|
|
cutter = BRepPrimAPI_MakeCone(
|
|
gp_Ax2(gp_Pnt(15.0, 15.0, 5.0), gp_Dir(0.0, 0.0, 1.0)),
|
|
2.0,
|
|
5.0,
|
|
5.0,
|
|
).Shape()
|
|
cut = BRepAlgoAPI_Cut(block, cutter)
|
|
_write_step(_finalize_boolean_result(cut, "verify Face cone integrity countersink cut"), path)
|
|
|
|
|
|
def _first_face_by_surface(model: StepModel, surface: str) -> int:
|
|
for face_id in range(len(model.faces)):
|
|
if model.face_info(face_id).get("surface") == surface:
|
|
return face_id
|
|
raise SystemExit(f"no {surface} Face was recognized")
|
|
|
|
|
|
def main() -> int:
|
|
probe = _Probe()
|
|
before = TopologyStats(parts=1, solids=1, faces=6, edges=12, vertices=8)
|
|
after_ok = TopologyStats(parts=1, solids=1, faces=6, edges=12, vertices=8)
|
|
after_no_solid = TopologyStats(parts=1, solids=0, faces=0, edges=0, vertices=0)
|
|
after_two_solids = TopologyStats(parts=1, solids=2, faces=8, edges=16, vertices=12)
|
|
before_quality_ok = {"quality_status": "ok", "brep_valid": True, "quality_warnings": ""}
|
|
after_quality_ok = {"quality_status": "ok", "brep_valid": True, "quality_warnings": ""}
|
|
after_quality_bad = {
|
|
"quality_status": "warning",
|
|
"brep_valid": False,
|
|
"quality_warnings": "B-Rep invalid after edit",
|
|
}
|
|
before_quality_bad = {
|
|
"quality_status": "warning",
|
|
"brep_valid": False,
|
|
"quality_warnings": "B-Rep invalid before edit",
|
|
}
|
|
|
|
_expect_blocked(
|
|
"Face edit losing solids",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
_face_context(),
|
|
before,
|
|
after_no_solid,
|
|
before,
|
|
after_no_solid,
|
|
before_quality_ok,
|
|
after_quality_ok,
|
|
),
|
|
)
|
|
_expect_blocked(
|
|
"Face edit changing target solid count",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
_face_context(),
|
|
before,
|
|
after_two_solids,
|
|
before,
|
|
after_two_solids,
|
|
before_quality_ok,
|
|
after_quality_ok,
|
|
),
|
|
)
|
|
_expect_blocked(
|
|
"New invalid B-Rep",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
_face_context(),
|
|
before,
|
|
after_ok,
|
|
before,
|
|
after_ok,
|
|
before_quality_ok,
|
|
after_quality_bad,
|
|
),
|
|
)
|
|
warnings = _expect_allowed(
|
|
"Already-warning B-Rep stays warning",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
_face_context(),
|
|
before,
|
|
after_ok,
|
|
before,
|
|
after_ok,
|
|
before_quality_bad,
|
|
after_quality_bad,
|
|
),
|
|
)
|
|
if not warnings:
|
|
raise SystemExit("existing quality warning should still be reported")
|
|
cube = StepModel.load(PROJECT_ROOT / "assets" / "models" / "cube_10mm.step")
|
|
cube_stats = cube.stats()
|
|
cube_part_id = int(cube.face_part_ids[0])
|
|
cube_solid_id = int(cube.face_solid_ids[0])
|
|
cube_context = {
|
|
"operation_name": "Face target area integrity probe",
|
|
"target_kind": "face",
|
|
"target_id": 0,
|
|
"target_logical_id": cube.face_region_logical_id(0),
|
|
"parameters": {
|
|
"resize_strategy": "local-face-area-only-deform",
|
|
"part_id": cube_part_id,
|
|
"solid_id": cube_solid_id,
|
|
"surface": "plane",
|
|
"target_area": 144.0,
|
|
},
|
|
}
|
|
_expect_blocked(
|
|
"Face target area mismatch",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
cube_context,
|
|
cube_stats,
|
|
cube_stats,
|
|
cube.part_topology_stats(cube_part_id),
|
|
cube.part_topology_stats(cube_part_id),
|
|
before_quality_ok,
|
|
after_quality_ok,
|
|
after_model=cube,
|
|
),
|
|
)
|
|
edited_cube = StepModel.load(PROJECT_ROOT / "assets" / "models" / "cube_10mm.step")
|
|
edited_before_stats = edited_cube.stats()
|
|
edited_before_part_stats = edited_cube.part_topology_stats(cube_part_id)
|
|
edited_cube.resize_face_area_local(0, 144.0)
|
|
_expect_allowed(
|
|
"Face target area reached",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
cube_context,
|
|
edited_before_stats,
|
|
edited_cube.stats(),
|
|
edited_before_part_stats,
|
|
edited_cube.part_topology_stats(cube_part_id),
|
|
before_quality_ok,
|
|
after_quality_ok,
|
|
after_model=edited_cube,
|
|
),
|
|
)
|
|
rollback_cube = StepModel.load(PROJECT_ROOT / "assets" / "models" / "cube_10mm.step")
|
|
rollback_before_stats = rollback_cube.stats()
|
|
rollback_plan = rollback_cube.face_area_local_resize_plan(0, 144.0)
|
|
rollback_plan["target_area"] = 999.0
|
|
_expect_blocked(
|
|
"Face model-layer rollback on failed target check",
|
|
lambda: rollback_cube._run_checked_face_edit(
|
|
rollback_plan,
|
|
lambda: rollback_cube._apply_local_face_deform(rollback_plan),
|
|
),
|
|
)
|
|
if rollback_cube.stats() != rollback_before_stats:
|
|
raise SystemExit(
|
|
"Face model-layer rollback should restore topology stats: "
|
|
f"before={rollback_before_stats}, after={rollback_cube.stats()}"
|
|
)
|
|
if abs(float(rollback_cube.face_info(0).get("area") or 0.0) - 100.0) > 1e-5:
|
|
raise SystemExit("Face model-layer rollback should restore the original Face area")
|
|
topology_guard_cube = StepModel.load(PROJECT_ROOT / "assets" / "models" / "cube_10mm.step")
|
|
topology_before_stats = topology_guard_cube.stats()
|
|
topology_plan = topology_guard_cube.face_area_local_resize_plan(0, 144.0)
|
|
original_topology_method = topology_guard_cube.face_first_level_topology
|
|
|
|
def apply_face_then_weaken_first_level_topology():
|
|
topology_guard_cube._apply_local_face_deform(topology_plan)
|
|
|
|
def weak_topology(face_id: int) -> dict[str, object]:
|
|
topology = dict(original_topology_method(face_id))
|
|
topology["first_level_boundary_edge_count"] = 3
|
|
topology["first_level_boundary_vertex_count"] = 3
|
|
topology["first_level_adjacent_face_count"] = 1
|
|
return topology
|
|
|
|
topology_guard_cube.face_first_level_topology = weak_topology
|
|
|
|
try:
|
|
_expect_blocked(
|
|
"Face model-layer rollback on weakened first-level topology",
|
|
lambda: topology_guard_cube._run_checked_face_edit(
|
|
topology_plan,
|
|
apply_face_then_weaken_first_level_topology,
|
|
),
|
|
)
|
|
finally:
|
|
topology_guard_cube.face_first_level_topology = original_topology_method
|
|
if topology_guard_cube.stats() != topology_before_stats:
|
|
raise SystemExit(
|
|
"Face model-layer topology rollback should restore topology stats: "
|
|
f"before={topology_before_stats}, after={topology_guard_cube.stats()}"
|
|
)
|
|
if abs(float(topology_guard_cube.face_info(0).get("area") or 0.0) - 100.0) > 1e-5:
|
|
raise SystemExit("Face model-layer topology rollback should restore the original Face area")
|
|
|
|
fact_guard_cube = StepModel.load(PROJECT_ROOT / "assets" / "models" / "cube_10mm.step")
|
|
fact_before_stats = fact_guard_cube.stats()
|
|
fact_plan = fact_guard_cube.face_area_local_resize_plan(0, 144.0)
|
|
original_fact_method = fact_guard_cube.face_first_level_facts
|
|
|
|
def apply_face_then_weaken_first_level_facts():
|
|
fact_guard_cube._apply_local_face_deform(fact_plan)
|
|
|
|
def weak_facts(face_id: int, scope: str = "auto") -> dict[str, object]:
|
|
facts = dict(original_fact_method(face_id, scope=scope))
|
|
facts["first_level_fact_boundary_edge_count"] = 1
|
|
facts["first_level_fact_boundary_vertex_count"] = 1
|
|
facts["first_level_fact_adjacent_face_count"] = 0
|
|
facts["first_level_fact_ignored_relation_depths"] = ("second-level",)
|
|
return facts
|
|
|
|
fact_guard_cube.face_first_level_facts = weak_facts
|
|
|
|
try:
|
|
_expect_blocked(
|
|
"Face model-layer rollback on weakened first-level fact graph",
|
|
lambda: fact_guard_cube._run_checked_face_edit(
|
|
fact_plan,
|
|
apply_face_then_weaken_first_level_facts,
|
|
),
|
|
)
|
|
finally:
|
|
fact_guard_cube.face_first_level_facts = original_fact_method
|
|
if fact_guard_cube.stats() != fact_before_stats:
|
|
raise SystemExit(
|
|
"Face model-layer fact-graph rollback should restore topology stats: "
|
|
f"before={fact_before_stats}, after={fact_guard_cube.stats()}"
|
|
)
|
|
if abs(float(fact_guard_cube.face_info(0).get("area") or 0.0) - 100.0) > 1e-5:
|
|
raise SystemExit("Face model-layer fact-graph rollback should restore the original Face area")
|
|
|
|
with tempfile.TemporaryDirectory(prefix="geom_param_face_guard_coplanar_area_") as temp_dir:
|
|
split_path = Path(temp_dir) / "split_top_box.step"
|
|
_write_split_top_box(split_path)
|
|
split_model = StepModel.load(split_path)
|
|
top_faces = _top_faces(split_model, 10.0, 2e-4)
|
|
if len(top_faces) != 2:
|
|
raise SystemExit(f"coplanar area guard expected two top Faces, got {top_faces}")
|
|
split_plan = split_model.push_pull_plan(top_faces[0], 1.0)
|
|
if split_plan["status"] == "blocked":
|
|
raise SystemExit(f"coplanar area guard plan was blocked: {split_plan['message']}")
|
|
original_scope_area = float(split_plan.get("push_pull_scope_area") or 0.0)
|
|
if abs(original_scope_area - 100.0) > 2e-4:
|
|
raise SystemExit(f"coplanar area guard expected source area 100, got {original_scope_area:g}")
|
|
split_model.push_pull_face(top_faces[0], 1.0)
|
|
split_plan["push_pull_scope_area"] = original_scope_area * 2.0
|
|
_expect_blocked(
|
|
"Face push/pull scope area mismatch",
|
|
lambda: split_model._checked_face_edit_result_summary(split_plan),
|
|
)
|
|
|
|
plan_fact_guard_cube = StepModel.load(PROJECT_ROOT / "assets" / "models" / "cube_10mm.step")
|
|
plan_info = plan_fact_guard_cube.face_info(0)
|
|
plan_center = plan_info.get("area_center") or plan_info.get("bbox_center")
|
|
if not isinstance(plan_center, tuple) or len(plan_center) != 3:
|
|
raise SystemExit(f"Face plan guard probe needs a stable center: {plan_info}")
|
|
plan_width = float(plan_info.get("local_face_width") or 0.0)
|
|
plan_height = float(plan_info.get("local_face_height") or 0.0)
|
|
if plan_width <= 0 or plan_height <= 0:
|
|
raise SystemExit(f"Face plan guard probe needs local width/height: {plan_info}")
|
|
original_fact_plan_method = plan_fact_guard_cube._first_level_fact_plan_fields
|
|
|
|
def unavailable_fact_plan(face_id: int, scope: str) -> dict[str, object]:
|
|
return {
|
|
"first_level_fact_model": "STEP/B-Rep first-level fact graph",
|
|
"first_level_fact_source_model": scope,
|
|
"first_level_fact_status": "unavailable",
|
|
"first_level_fact_relation_depth": 1,
|
|
"first_level_fact_relation_boundary": "shared-edge",
|
|
"first_level_fact_scope": scope,
|
|
"first_level_fact_subject_role": "selected Face",
|
|
"first_level_fact_subject_face_ids": (face_id,),
|
|
"first_level_fact_subject_face_count": 1,
|
|
"first_level_fact_boundary_edge_ids": (),
|
|
"first_level_fact_boundary_edge_count": 0,
|
|
"first_level_fact_boundary_vertex_points": (),
|
|
"first_level_fact_boundary_vertex_count": 0,
|
|
"first_level_fact_adjacent_face_ids": (),
|
|
"first_level_fact_adjacent_face_count": 0,
|
|
"first_level_fact_included_face_ids": (face_id,),
|
|
"first_level_fact_included_face_count": 1,
|
|
"first_level_fact_role_groups": (),
|
|
"first_level_fact_ignored_relation_depths": ("second-level", "third-level", "deeper"),
|
|
"first_level_fact_summary": "test-only unavailable first-level fact graph",
|
|
}
|
|
|
|
plan_fact_guard_cube._first_level_fact_plan_fields = unavailable_fact_plan
|
|
try:
|
|
local_plans = (
|
|
(
|
|
"Face local center plan",
|
|
plan_fact_guard_cube.face_center_local_move_plan(
|
|
0,
|
|
(float(plan_center[0]) + 1.0, float(plan_center[1]), float(plan_center[2])),
|
|
),
|
|
),
|
|
("Face local area plan", plan_fact_guard_cube.face_area_local_resize_plan(0, 144.0)),
|
|
("Face local width plan", plan_fact_guard_cube.face_size_local_resize_plan(0, plan_width * 1.2, "width")),
|
|
("Face local height plan", plan_fact_guard_cube.face_size_local_resize_plan(0, plan_height * 1.2, "height")),
|
|
("Face local plane offset plan", plan_fact_guard_cube.face_plane_offset_local_plan(0, 1.0)),
|
|
)
|
|
for label, plan in local_plans:
|
|
if plan.get("status") != "blocked":
|
|
raise SystemExit(f"{label} should be blocked when first-level facts are unavailable: {plan}")
|
|
blocker_text = str(plan.get("message") or plan.get("blockers") or "")
|
|
if "一级事实图" not in blocker_text:
|
|
raise SystemExit(f"{label} should explain the first-level fact blocker: {plan}")
|
|
|
|
owning_plan = plan_fact_guard_cube.face_center_owning_translation_plan(
|
|
0,
|
|
(float(plan_center[0]) + 1.0, float(plan_center[1]), float(plan_center[2])),
|
|
)
|
|
if owning_plan.get("status") == "blocked":
|
|
raise SystemExit(
|
|
"Owning-shape Face translation should not be blocked by local first-level fact guard: "
|
|
f"{owning_plan}"
|
|
)
|
|
finally:
|
|
plan_fact_guard_cube._first_level_fact_plan_fields = original_fact_plan_method
|
|
with tempfile.TemporaryDirectory(prefix="geom_param_face_guard_sphere_") as temp_dir:
|
|
sphere_path = Path(temp_dir) / "sphere.step"
|
|
_write_step(BRepPrimAPI_MakeSphere(5.0).Shape(), sphere_path)
|
|
sphere = StepModel.load(sphere_path)
|
|
sphere_face_id = next(
|
|
face_id for face_id in range(len(sphere.faces)) if sphere.face_info(face_id).get("surface") == "sphere"
|
|
)
|
|
sphere_part_id = int(sphere.face_part_ids[sphere_face_id])
|
|
sphere_solid_id = int(sphere.face_solid_ids[sphere_face_id])
|
|
sphere_context = {
|
|
"operation_name": "Face target sphere radius integrity probe",
|
|
"target_kind": "face",
|
|
"target_id": sphere_face_id,
|
|
"target_logical_id": sphere.face_region_logical_id(sphere_face_id),
|
|
"parameters": {
|
|
"resize_strategy": "sphere-radius-owning-scale",
|
|
"part_id": sphere_part_id,
|
|
"solid_id": sphere_solid_id,
|
|
"surface": "sphere",
|
|
"target_radius": 6.25,
|
|
},
|
|
}
|
|
_expect_blocked(
|
|
"Face target sphere radius mismatch",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
sphere_context,
|
|
sphere.stats(),
|
|
sphere.stats(),
|
|
sphere.part_topology_stats(sphere_part_id),
|
|
sphere.part_topology_stats(sphere_part_id),
|
|
before_quality_ok,
|
|
after_quality_ok,
|
|
after_model=sphere,
|
|
),
|
|
)
|
|
edited_sphere = StepModel.load(sphere_path)
|
|
edited_sphere.resize_spherical_radius(sphere_face_id, 6.25)
|
|
_expect_allowed(
|
|
"Face target sphere radius reached",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
sphere_context,
|
|
sphere.stats(),
|
|
edited_sphere.stats(),
|
|
sphere.part_topology_stats(sphere_part_id),
|
|
edited_sphere.part_topology_stats(sphere_part_id),
|
|
before_quality_ok,
|
|
after_quality_ok,
|
|
after_model=edited_sphere,
|
|
),
|
|
)
|
|
with tempfile.TemporaryDirectory(prefix="geom_param_face_guard_cone_") as temp_dir:
|
|
cone_path = Path(temp_dir) / "embedded_countersink.step"
|
|
_write_embedded_countersink(cone_path)
|
|
cone = StepModel.load(cone_path)
|
|
cone_face_id = _first_face_by_surface(cone, "cone")
|
|
cone_part_id = int(cone.face_part_ids[cone_face_id])
|
|
cone_solid_id = int(cone.face_solid_ids[cone_face_id])
|
|
stale_plane_id = 0
|
|
if cone.face_info(stale_plane_id).get("surface") == "cone":
|
|
raise SystemExit("cone integrity test expected Face 0 to be a stale non-cone candidate")
|
|
cone_context = {
|
|
"operation_name": "Face target cone boundary integrity probe",
|
|
"target_kind": "face",
|
|
"target_id": stale_plane_id,
|
|
"target_logical_id": stale_plane_id,
|
|
"parameters": {
|
|
"resize_strategy": "bounded-cone-recut-preserve-angle-reference-radius",
|
|
"part_id": cone_part_id,
|
|
"solid_id": cone_solid_id,
|
|
"surface": "cone",
|
|
"target_reference_radius": 3.0,
|
|
"embedded_cone_target_small_radius": 3.0,
|
|
"embedded_cone_target_large_radius": 6.0,
|
|
},
|
|
}
|
|
_expect_blocked(
|
|
"Face target cone boundary mismatch",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
cone_context,
|
|
cone.stats(),
|
|
cone.stats(),
|
|
cone.part_topology_stats(cone_part_id),
|
|
cone.part_topology_stats(cone_part_id),
|
|
before_quality_ok,
|
|
after_quality_ok,
|
|
after_model=cone,
|
|
),
|
|
)
|
|
edited_cone = StepModel.load(cone_path)
|
|
edited_before_stats = edited_cone.stats()
|
|
edited_before_part_stats = edited_cone.part_topology_stats(cone_part_id)
|
|
edited_cone.resize_conical_reference_radius(cone_face_id, 3.0)
|
|
_expect_allowed(
|
|
"Face target cone boundary reached despite stale id",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
cone_context,
|
|
edited_before_stats,
|
|
edited_cone.stats(),
|
|
edited_before_part_stats,
|
|
edited_cone.part_topology_stats(cone_part_id),
|
|
before_quality_ok,
|
|
after_quality_ok,
|
|
after_model=edited_cone,
|
|
),
|
|
)
|
|
with tempfile.TemporaryDirectory(prefix="geom_param_face_guard_cone_rollback_") as temp_dir:
|
|
cone_path = Path(temp_dir) / "simple_cone.step"
|
|
_write_step(BRepPrimAPI_MakeCone(4.0, 2.0, 10.0).Shape(), cone_path)
|
|
rollback_cone = StepModel.load(cone_path)
|
|
cone_face_id = _first_face_by_surface(rollback_cone, "cone")
|
|
before_stats = rollback_cone.stats()
|
|
before_radius = float(rollback_cone.face_info(cone_face_id).get("reference_radius") or 0.0)
|
|
rollback_plan = rollback_cone.conical_reference_radius_plan(cone_face_id, 5.0)
|
|
|
|
def apply_cone_then_fail_target_check():
|
|
applied = rollback_cone._apply_conical_analytic_rebuild_if_simple(rollback_plan)
|
|
rollback_plan["target_reference_radius"] = 999.0
|
|
return applied
|
|
|
|
_expect_blocked(
|
|
"Conical Face model-layer rollback on failed target check",
|
|
lambda: rollback_cone._run_checked_face_edit(rollback_plan, apply_cone_then_fail_target_check),
|
|
)
|
|
if rollback_cone.stats() != before_stats:
|
|
raise SystemExit(
|
|
"Conical Face model-layer rollback should restore topology stats: "
|
|
f"before={before_stats}, after={rollback_cone.stats()}"
|
|
)
|
|
restored_radius = float(rollback_cone.face_info(cone_face_id).get("reference_radius") or 0.0)
|
|
if abs(restored_radius - before_radius) > 1e-5:
|
|
raise SystemExit(
|
|
"Conical Face model-layer rollback should restore the original reference radius: "
|
|
f"before={before_radius:g}, after={restored_radius:g}"
|
|
)
|
|
_expect_allowed(
|
|
"Non-Face edit solid-count change only warns",
|
|
lambda: probe._verified_edit_quality_warnings(
|
|
_edge_context(),
|
|
before,
|
|
after_two_solids,
|
|
before,
|
|
after_two_solids,
|
|
before_quality_ok,
|
|
after_quality_ok,
|
|
),
|
|
)
|
|
if probe._isolation_for_plan({"risk": "medium"}, "resize_face_area_local", [0, 125.0]) is None:
|
|
raise SystemExit("medium-risk Face edits should use isolated execution")
|
|
if probe._isolation_for_plan({"risk": "medium"}, "resize_edge_length", [0, 12.0]) is not None:
|
|
raise SystemExit("unsupported medium-risk Edge edits should not use Face isolation")
|
|
if probe._isolation_for_plan({"risk": "low"}, "resize_face_area_local", [0, 125.0]) is None:
|
|
raise SystemExit("low-risk Face parameter edits should still use isolated execution")
|
|
if probe._isolation_for_plan({"risk": "high"}, "resize_edge_length", [0, 12.0]) is not None:
|
|
raise SystemExit("unsupported high-risk Edge edits should not enter the Face isolation worker")
|
|
|
|
print("Face edit integrity guards ok")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|