feat: 完善 Face 一级关系编辑和稳定性校验
This commit is contained in:
@@ -4,12 +4,15 @@ import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeCylinder
|
||||
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut
|
||||
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
|
||||
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.geometry_utils import _finalize_boolean_result
|
||||
from step_editor.model import StepModel
|
||||
from step_editor.step_io import _write_step
|
||||
from step_editor.window_state import WindowStateMixin
|
||||
@@ -35,6 +38,54 @@ def _write_cylinder(path: Path) -> None:
|
||||
_write_step(cylinder, path)
|
||||
|
||||
|
||||
def _write_hollow_cylinder(path: Path) -> None:
|
||||
outer = BRepPrimAPI_MakeCylinder(8.0, 78.0).Shape()
|
||||
inner = BRepPrimAPI_MakeCylinder(
|
||||
gp_Ax2(gp_Pnt(0.0, 0.0, -1.0), gp_Dir(0.0, 0.0, 1.0)),
|
||||
3.0,
|
||||
80.0,
|
||||
).Shape()
|
||||
cut = BRepAlgoAPI_Cut(outer, inner)
|
||||
_write_step(_finalize_boolean_result(cut, "verify hollow cylinder cut", use_glue=False), path)
|
||||
|
||||
|
||||
def _write_hollow_cylinder_with_side_feature(path: Path) -> None:
|
||||
outer = BRepPrimAPI_MakeCylinder(8.0, 78.0).Shape()
|
||||
inner = BRepPrimAPI_MakeCylinder(
|
||||
gp_Ax2(gp_Pnt(0.0, 0.0, -1.0), gp_Dir(0.0, 0.0, 1.0)),
|
||||
3.0,
|
||||
80.0,
|
||||
).Shape()
|
||||
hollow = _finalize_boolean_result(BRepAlgoAPI_Cut(outer, inner), "verify complex hollow cylinder cut", use_glue=False)
|
||||
notch = BRepPrimAPI_MakeBox(gp_Pnt(5.0, -3.0, 20.0), gp_Pnt(12.0, 3.0, 35.0)).Shape()
|
||||
cut = BRepAlgoAPI_Cut(hollow, notch)
|
||||
_write_step(_finalize_boolean_result(cut, "verify complex hollow cylinder side notch", use_glue=False), path)
|
||||
|
||||
|
||||
def _write_hollow_cylinder_with_top_boundary_feature(path: Path) -> None:
|
||||
outer = BRepPrimAPI_MakeCylinder(8.0, 78.0).Shape()
|
||||
inner = BRepPrimAPI_MakeCylinder(
|
||||
gp_Ax2(gp_Pnt(0.0, 0.0, -1.0), gp_Dir(0.0, 0.0, 1.0)),
|
||||
3.0,
|
||||
80.0,
|
||||
).Shape()
|
||||
hollow = _finalize_boolean_result(
|
||||
BRepAlgoAPI_Cut(outer, inner),
|
||||
"verify hollow cylinder top-boundary base cut",
|
||||
use_glue=False,
|
||||
)
|
||||
notch = BRepPrimAPI_MakeBox(gp_Pnt(4.0, -1.0, 60.0), gp_Pnt(5.5, 1.0, 90.0)).Shape()
|
||||
cut = BRepAlgoAPI_Cut(hollow, notch)
|
||||
_write_step(_finalize_boolean_result(cut, "verify hollow cylinder top-boundary notch", use_glue=False), path)
|
||||
|
||||
|
||||
def _write_solid_cylinder_with_top_boundary_feature(path: Path) -> None:
|
||||
cylinder = BRepPrimAPI_MakeCylinder(8.0, 78.0).Shape()
|
||||
notch = BRepPrimAPI_MakeBox(gp_Pnt(2.0, -1.0, 60.0), gp_Pnt(4.5, 1.0, 90.0)).Shape()
|
||||
cut = BRepAlgoAPI_Cut(cylinder, notch)
|
||||
_write_step(_finalize_boolean_result(cut, "verify solid cylinder top-boundary notch", use_glue=False), path)
|
||||
|
||||
|
||||
def _first_planar_cap_face(model: StepModel) -> int:
|
||||
for face_id in range(len(model.faces)):
|
||||
info = model.face_info(face_id)
|
||||
@@ -43,6 +94,37 @@ def _first_planar_cap_face(model: StepModel) -> int:
|
||||
raise SystemExit("no planar cylinder cap Face was found")
|
||||
|
||||
|
||||
def _top_planar_cap_face(model: StepModel) -> int:
|
||||
best: tuple[float, int] | None = None
|
||||
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
|
||||
z_value = float(center[2])
|
||||
if best is None or z_value > best[0]:
|
||||
best = (z_value, face_id)
|
||||
if best is None:
|
||||
raise SystemExit("no top planar cylinder cap Face was found")
|
||||
return best[1]
|
||||
|
||||
|
||||
def _cylinder_radii(model: StepModel) -> list[float]:
|
||||
radii: list[float] = []
|
||||
for face_id in range(len(model.faces)):
|
||||
info = model.face_info(face_id)
|
||||
if info.get("surface") != "cylinder":
|
||||
continue
|
||||
radius = float(info.get("radius") or 0.0)
|
||||
if radius <= 0.0:
|
||||
continue
|
||||
if not any(abs(radius - existing) <= max(radius, existing, 1.0) * 1e-5 for existing in radii):
|
||||
radii.append(radius)
|
||||
return sorted(radii)
|
||||
|
||||
|
||||
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}")
|
||||
@@ -131,15 +213,38 @@ def main() -> int:
|
||||
raise SystemExit("planar cylinder cap push/pull scope should remain available")
|
||||
|
||||
huge_push_plan = model.push_pull_plan(face_id, 50.0)
|
||||
if huge_push_plan.get("status") != "blocked":
|
||||
raise SystemExit(f"huge planar cylinder cap push/pull should be blocked early: {huge_push_plan}")
|
||||
if huge_push_plan.get("status") == "blocked":
|
||||
raise SystemExit(f"huge simple cylinder cap push/pull should use analytic rebuild: {huge_push_plan}")
|
||||
old_height = float(huge_push_plan.get("cylindrical_cap_extension_old_height") or 0.0)
|
||||
new_height = float(huge_push_plan.get("cylindrical_cap_extension_new_height") or 0.0)
|
||||
if old_height <= 0.0 or new_height <= old_height * 3.0:
|
||||
raise SystemExit(f"huge cap push/pull should expose the blocked height growth: {huge_push_plan}")
|
||||
raise SystemExit(f"huge cap push/pull should expose the large height growth: {huge_push_plan}")
|
||||
if huge_push_plan.get("cylindrical_cap_extension_kind") != "solid-cylinder":
|
||||
raise SystemExit(f"huge simple cylinder cap push/pull should expose solid-cylinder kind: {huge_push_plan}")
|
||||
huge_message = str(huge_push_plan.get("message") or "")
|
||||
if "圆柱高度" not in huge_message and "cylindrical" not in huge_message.lower():
|
||||
raise SystemExit(f"huge cap push/pull should explain the cylinder-height risk: {huge_push_plan}")
|
||||
if "解析" not in huge_message and "analytic" not in huge_message.lower():
|
||||
raise SystemExit(f"huge cap push/pull should explain analytic rebuild: {huge_push_plan}")
|
||||
|
||||
large_model = StepModel.load(path)
|
||||
large_face_id = _first_planar_cap_face(large_model)
|
||||
large_result = large_model.push_pull_face(large_face_id, 50.0)
|
||||
if "analytic rebuild" not in large_result:
|
||||
raise SystemExit(f"large simple cylinder cap push/pull should use analytic rebuild: {large_result}")
|
||||
if abs(_bbox_height(large_model) - 58.0) > 1e-4:
|
||||
raise SystemExit(f"large simple cylinder cap push/pull should rebuild height to 58: {_bbox_height(large_model)}")
|
||||
|
||||
shrink_model = StepModel.load(path)
|
||||
shrink_face_id = _first_planar_cap_face(shrink_model)
|
||||
shrink_plan = shrink_model.push_pull_plan(shrink_face_id, -3.0)
|
||||
if shrink_plan.get("status") == "blocked":
|
||||
raise SystemExit(f"simple cylinder cap inward push/pull should use analytic shrink: {shrink_plan}")
|
||||
if shrink_plan.get("cylindrical_cap_operation") != "retract":
|
||||
raise SystemExit(f"simple cylinder cap inward push/pull should expose retract operation: {shrink_plan}")
|
||||
shrink_result = shrink_model.push_pull_face(shrink_face_id, -3.0)
|
||||
if "analytic rebuild" not in shrink_result or "cap_operation=retract" not in shrink_result:
|
||||
raise SystemExit(f"simple cylinder cap inward push/pull should use analytic retract: {shrink_result}")
|
||||
if abs(_bbox_height(shrink_model) - 5.0) > 1e-4:
|
||||
raise SystemExit(f"simple cylinder cap inward push/pull should shrink height to 5: {_bbox_height(shrink_model)}")
|
||||
|
||||
before_height = _bbox_height(model)
|
||||
push_plan = model.push_pull_plan(face_id, 1.0)
|
||||
@@ -158,6 +263,275 @@ def main() -> int:
|
||||
f"face_id={face_id}, blocker={blocker}, height={before_height:g}->{after_height:g}, "
|
||||
f"push_pull={push_result}"
|
||||
)
|
||||
|
||||
hollow_path = Path(temp_dir) / "hollow_cylinder.step"
|
||||
_write_hollow_cylinder(hollow_path)
|
||||
hollow = StepModel.load(hollow_path)
|
||||
hollow_face_id = _top_planar_cap_face(hollow)
|
||||
hollow_plan = hollow.push_pull_plan(hollow_face_id, 89.0)
|
||||
if hollow_plan.get("status") == "blocked":
|
||||
raise SystemExit(f"hollow cylinder cap push/pull should not be blocked: {hollow_plan}")
|
||||
if hollow_plan.get("cylindrical_cap_extension_kind") != "coaxial-tube":
|
||||
raise SystemExit(f"hollow cylinder cap should be recognized as coaxial-tube: {hollow_plan}")
|
||||
if abs(float(hollow_plan.get("cylindrical_cap_extension_inner_radius") or 0.0) - 3.0) > 1e-4:
|
||||
raise SystemExit(f"hollow cylinder cap should preserve inner radius in plan: {hollow_plan}")
|
||||
hollow_result = hollow.push_pull_face(hollow_face_id, 89.0)
|
||||
if "analytic rebuild" not in hollow_result or "coaxial-tube" not in hollow_result:
|
||||
raise SystemExit(f"hollow cylinder cap push/pull should use tube analytic rebuild: {hollow_result}")
|
||||
if abs(_bbox_height(hollow) - 167.0) > 1e-4:
|
||||
raise SystemExit(f"hollow cylinder cap push/pull should rebuild height to 167: {_bbox_height(hollow)}")
|
||||
radii = _cylinder_radii(hollow)
|
||||
if len(radii) != 2 or abs(radii[0] - 3.0) > 1e-4 or abs(radii[1] - 8.0) > 1e-4:
|
||||
raise SystemExit(f"hollow cylinder cap push/pull should preserve inner/outer cylindrical walls: {radii}")
|
||||
print(f"hollow cylinder cap analytic push/pull ok: radii={radii}, result={hollow_result}")
|
||||
|
||||
hollow_shrink = StepModel.load(hollow_path)
|
||||
hollow_shrink_face_id = _top_planar_cap_face(hollow_shrink)
|
||||
hollow_shrink_plan = hollow_shrink.push_pull_plan(hollow_shrink_face_id, -39.0)
|
||||
if hollow_shrink_plan.get("status") == "blocked":
|
||||
raise SystemExit(f"hollow cylinder cap inward push/pull should not be blocked: {hollow_shrink_plan}")
|
||||
if hollow_shrink_plan.get("cylindrical_cap_operation") != "retract":
|
||||
raise SystemExit(f"hollow cylinder cap inward push/pull should expose retract operation: {hollow_shrink_plan}")
|
||||
hollow_shrink_result = hollow_shrink.push_pull_face(hollow_shrink_face_id, -39.0)
|
||||
if "analytic rebuild" not in hollow_shrink_result or "coaxial-tube" not in hollow_shrink_result:
|
||||
raise SystemExit(f"hollow cylinder cap inward push/pull should use tube analytic shrink: {hollow_shrink_result}")
|
||||
if abs(_bbox_height(hollow_shrink) - 39.0) > 1e-4:
|
||||
raise SystemExit(f"hollow cylinder cap inward push/pull should shrink height to 39: {_bbox_height(hollow_shrink)}")
|
||||
shrink_radii = _cylinder_radii(hollow_shrink)
|
||||
if len(shrink_radii) != 2 or abs(shrink_radii[0] - 3.0) > 1e-4 or abs(shrink_radii[1] - 8.0) > 1e-4:
|
||||
raise SystemExit(f"hollow cylinder cap inward push/pull should preserve radii: {shrink_radii}")
|
||||
print(f"hollow cylinder cap analytic retraction ok: radii={shrink_radii}, result={hollow_shrink_result}")
|
||||
|
||||
complex_hollow_path = Path(temp_dir) / "hollow_cylinder_with_side_feature.step"
|
||||
_write_hollow_cylinder_with_side_feature(complex_hollow_path)
|
||||
complex_hollow = StepModel.load(complex_hollow_path)
|
||||
complex_hollow_face_id = _top_planar_cap_face(complex_hollow)
|
||||
complex_plan = complex_hollow.push_pull_plan(complex_hollow_face_id, 89.0)
|
||||
if complex_plan.get("status") == "blocked":
|
||||
raise SystemExit(f"complex hollow cylinder cap push/pull should not be blocked: {complex_plan}")
|
||||
if complex_plan.get("cylindrical_cap_extension_kind") != "coaxial-tube":
|
||||
raise SystemExit(f"complex hollow cylinder cap should be recognized as coaxial-tube: {complex_plan}")
|
||||
if not complex_plan.get("cylindrical_cap_extension_uses_local_segment"):
|
||||
raise SystemExit(f"complex hollow cylinder cap should expose local extension segment: {complex_plan}")
|
||||
complex_result = complex_hollow.push_pull_face(complex_hollow_face_id, 89.0)
|
||||
if "local extension" not in complex_result or "coaxial-tube" not in complex_result:
|
||||
raise SystemExit(f"complex hollow cylinder cap should use local extension segment: {complex_result}")
|
||||
if abs(_bbox_height(complex_hollow) - 167.0) > 1e-4:
|
||||
raise SystemExit(
|
||||
f"complex hollow cylinder cap push/pull should extend height to 167: {_bbox_height(complex_hollow)}"
|
||||
)
|
||||
complex_radii = _cylinder_radii(complex_hollow)
|
||||
if len(complex_radii) < 2 or abs(complex_radii[0] - 3.0) > 1e-4 or abs(complex_radii[-1] - 8.0) > 1e-4:
|
||||
raise SystemExit(
|
||||
f"complex hollow cylinder cap should preserve inner/outer cylindrical walls: {complex_radii}"
|
||||
)
|
||||
print(
|
||||
"complex hollow cylinder cap local extension ok: "
|
||||
f"radii={complex_radii}, result={complex_result}"
|
||||
)
|
||||
|
||||
complex_shrink = StepModel.load(complex_hollow_path)
|
||||
complex_shrink_face_id = _top_planar_cap_face(complex_shrink)
|
||||
complex_shrink_plan = complex_shrink.push_pull_plan(complex_shrink_face_id, -20.0)
|
||||
if complex_shrink_plan.get("status") == "blocked":
|
||||
raise SystemExit(f"complex hollow cylinder cap inward push/pull should not be blocked: {complex_shrink_plan}")
|
||||
if complex_shrink_plan.get("cylindrical_cap_operation") != "retract":
|
||||
raise SystemExit(f"complex hollow cylinder cap inward push/pull should expose retract: {complex_shrink_plan}")
|
||||
complex_shrink_result = complex_shrink.push_pull_face(complex_shrink_face_id, -20.0)
|
||||
if "local retraction" not in complex_shrink_result or "coaxial-tube" not in complex_shrink_result:
|
||||
raise SystemExit(f"complex hollow cylinder cap inward push/pull should use local retraction: {complex_shrink_result}")
|
||||
if abs(_bbox_height(complex_shrink) - 58.0) > 1e-4:
|
||||
raise SystemExit(
|
||||
f"complex hollow cylinder cap inward push/pull should shrink height to 58: {_bbox_height(complex_shrink)}"
|
||||
)
|
||||
complex_shrink_radii = _cylinder_radii(complex_shrink)
|
||||
if (
|
||||
len(complex_shrink_radii) < 2
|
||||
or abs(complex_shrink_radii[0] - 3.0) > 1e-4
|
||||
or abs(complex_shrink_radii[-1] - 8.0) > 1e-4
|
||||
):
|
||||
raise SystemExit(
|
||||
f"complex hollow cylinder cap inward push/pull should preserve radii: {complex_shrink_radii}"
|
||||
)
|
||||
print(
|
||||
"complex hollow cylinder cap local retraction ok: "
|
||||
f"radii={complex_shrink_radii}, result={complex_shrink_result}"
|
||||
)
|
||||
|
||||
top_boundary_path = Path(temp_dir) / "hollow_cylinder_with_top_boundary_feature.step"
|
||||
_write_hollow_cylinder_with_top_boundary_feature(top_boundary_path)
|
||||
top_boundary = StepModel.load(top_boundary_path)
|
||||
top_boundary_face_id = _top_planar_cap_face(top_boundary)
|
||||
top_boundary_plan = top_boundary.push_pull_plan(top_boundary_face_id, 89.0)
|
||||
if top_boundary_plan.get("status") == "blocked":
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder cap push/pull should preserve extra first-level openings: {top_boundary_plan}"
|
||||
)
|
||||
if top_boundary_plan.get("cylindrical_cap_extension_kind") != "coaxial-tube":
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder cap should be recognized as coaxial-tube: {top_boundary_plan}"
|
||||
)
|
||||
if top_boundary_plan.get("cylindrical_cap_extension_method") != "cap-profile-prism":
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder cap should use cap profile prism method: {top_boundary_plan}"
|
||||
)
|
||||
if int(top_boundary_plan.get("cap_extra_adjacent_face_count") or 0) <= 0:
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder cap should expose extra adjacent faces: {top_boundary_plan}"
|
||||
)
|
||||
top_boundary_message = str(top_boundary_plan.get("message") or "")
|
||||
if "真实轮廓拉伸" not in top_boundary_message:
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder cap should explain profile-prism extension: {top_boundary_plan}"
|
||||
)
|
||||
try:
|
||||
top_boundary.push_pull_face(top_boundary_face_id, 89.0)
|
||||
except ValueError as exc:
|
||||
blocker_text = str(exc)
|
||||
if "解析重建" not in blocker_text and "OCCT" not in blocker_text:
|
||||
raise SystemExit(
|
||||
"top-boundary hollow cylinder large cap push/pull should explain the fast blocker: "
|
||||
f"{blocker_text}"
|
||||
)
|
||||
else:
|
||||
raise SystemExit(
|
||||
"top-boundary hollow cylinder large cap push/pull should be blocked instead of entering slow Boolean."
|
||||
)
|
||||
print(
|
||||
"top-boundary hollow cylinder cap large profile-prism blocker ok: "
|
||||
f"extra_adjacent={top_boundary_plan.get('cap_extra_adjacent_face_count')}, "
|
||||
f"distance=89"
|
||||
)
|
||||
|
||||
top_boundary_shallow_cut = StepModel.load(top_boundary_path)
|
||||
shallow_face_id = _top_planar_cap_face(top_boundary_shallow_cut)
|
||||
shallow_plan = top_boundary_shallow_cut.push_pull_plan(shallow_face_id, -10.0)
|
||||
if shallow_plan.get("status") == "blocked":
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder shallow inward push/pull should stay available: {shallow_plan}"
|
||||
)
|
||||
if shallow_plan.get("cylindrical_cap_extension_method") != "cap-profile-prism":
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder shallow inward push/pull should use cap profile prism: {shallow_plan}"
|
||||
)
|
||||
shallow_result = top_boundary_shallow_cut.push_pull_face(shallow_face_id, -10.0)
|
||||
if "profile-prism retraction" not in shallow_result or "coaxial-tube" not in shallow_result:
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder shallow inward push/pull should use profile-prism retraction: {shallow_result}"
|
||||
)
|
||||
if abs(_bbox_height(top_boundary_shallow_cut) - 68.0) > 1e-4:
|
||||
raise SystemExit(
|
||||
"top-boundary hollow cylinder shallow inward push/pull should lower height to 68: "
|
||||
f"{_bbox_height(top_boundary_shallow_cut)}"
|
||||
)
|
||||
shallow_top_face = _top_planar_cap_face(top_boundary_shallow_cut)
|
||||
shallow_info = top_boundary_shallow_cut.face_info(shallow_top_face)
|
||||
if int(shallow_info.get("boundary_wires") or 0) < 3:
|
||||
raise SystemExit(
|
||||
"top-boundary hollow cylinder shallow inward push/pull should preserve openings: "
|
||||
f"face_id={shallow_top_face}, info={shallow_info}"
|
||||
)
|
||||
print(
|
||||
"top-boundary hollow cylinder cap profile-prism shallow retraction ok: "
|
||||
f"result={shallow_result}"
|
||||
)
|
||||
|
||||
top_boundary_deep_cut = StepModel.load(top_boundary_path)
|
||||
deep_face_id = _top_planar_cap_face(top_boundary_deep_cut)
|
||||
deep_plan = top_boundary_deep_cut.push_pull_plan(deep_face_id, -20.0)
|
||||
if deep_plan.get("status") != "blocked":
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder deep inward push/pull should be blocked: {deep_plan}"
|
||||
)
|
||||
deep_message = str(deep_plan.get("message") or "")
|
||||
if "越过" not in deep_message or "内侧终点" not in deep_message:
|
||||
raise SystemExit(
|
||||
f"top-boundary hollow cylinder deep inward blocker should explain the retract limit: {deep_plan}"
|
||||
)
|
||||
print(
|
||||
"top-boundary hollow cylinder cap deep retraction blocker ok: "
|
||||
f"message={deep_message}"
|
||||
)
|
||||
|
||||
solid_top_boundary_path = Path(temp_dir) / "solid_cylinder_with_top_boundary_feature.step"
|
||||
_write_solid_cylinder_with_top_boundary_feature(solid_top_boundary_path)
|
||||
solid_top_boundary = StepModel.load(solid_top_boundary_path)
|
||||
solid_top_face_id = _top_planar_cap_face(solid_top_boundary)
|
||||
solid_extend_plan = solid_top_boundary.push_pull_plan(solid_top_face_id, 20.0)
|
||||
if solid_extend_plan.get("status") == "blocked":
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder cap outward push/pull should stay available: {solid_extend_plan}"
|
||||
)
|
||||
if solid_extend_plan.get("cylindrical_cap_extension_kind") != "solid-cylinder":
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder cap should be recognized as solid-cylinder: {solid_extend_plan}"
|
||||
)
|
||||
if solid_extend_plan.get("cylindrical_cap_extension_method") != "cap-profile-prism":
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder cap should use profile prism extension: {solid_extend_plan}"
|
||||
)
|
||||
solid_extend_result = solid_top_boundary.push_pull_face(solid_top_face_id, 20.0)
|
||||
if "profile-prism extension" not in solid_extend_result or "solid-cylinder" not in solid_extend_result:
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder cap should use profile-prism extension: {solid_extend_result}"
|
||||
)
|
||||
if abs(_bbox_height(solid_top_boundary) - 98.0) > 1e-4:
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder cap outward push/pull should extend height to 98: {_bbox_height(solid_top_boundary)}"
|
||||
)
|
||||
solid_extended_top = _top_planar_cap_face(solid_top_boundary)
|
||||
solid_extended_info = solid_top_boundary.face_info(solid_extended_top)
|
||||
if int(solid_extended_info.get("boundary_wires") or 0) < 2:
|
||||
raise SystemExit(
|
||||
"solid top-boundary cylinder cap outward push/pull should preserve the notch opening: "
|
||||
f"face_id={solid_extended_top}, info={solid_extended_info}"
|
||||
)
|
||||
print(
|
||||
"solid top-boundary cylinder cap profile-prism extension ok: "
|
||||
f"result={solid_extend_result}"
|
||||
)
|
||||
|
||||
solid_shallow_cut = StepModel.load(solid_top_boundary_path)
|
||||
solid_shallow_face_id = _top_planar_cap_face(solid_shallow_cut)
|
||||
solid_shallow_plan = solid_shallow_cut.push_pull_plan(solid_shallow_face_id, -10.0)
|
||||
if solid_shallow_plan.get("status") == "blocked":
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder shallow inward push/pull should stay available: {solid_shallow_plan}"
|
||||
)
|
||||
if solid_shallow_plan.get("cylindrical_cap_extension_method") != "cap-profile-prism":
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder shallow inward push/pull should use profile prism: {solid_shallow_plan}"
|
||||
)
|
||||
solid_shallow_result = solid_shallow_cut.push_pull_face(solid_shallow_face_id, -10.0)
|
||||
if "profile-prism retraction" not in solid_shallow_result or "solid-cylinder" not in solid_shallow_result:
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder shallow inward push/pull should use profile-prism retraction: {solid_shallow_result}"
|
||||
)
|
||||
if abs(_bbox_height(solid_shallow_cut) - 68.0) > 1e-4:
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder shallow inward push/pull should lower height to 68: {_bbox_height(solid_shallow_cut)}"
|
||||
)
|
||||
print(
|
||||
"solid top-boundary cylinder cap profile-prism shallow retraction ok: "
|
||||
f"result={solid_shallow_result}"
|
||||
)
|
||||
|
||||
solid_deep_cut = StepModel.load(solid_top_boundary_path)
|
||||
solid_deep_face_id = _top_planar_cap_face(solid_deep_cut)
|
||||
solid_deep_plan = solid_deep_cut.push_pull_plan(solid_deep_face_id, -20.0)
|
||||
if solid_deep_plan.get("status") != "blocked":
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder deep inward push/pull should be blocked: {solid_deep_plan}"
|
||||
)
|
||||
solid_deep_message = str(solid_deep_plan.get("message") or "")
|
||||
if "越过" not in solid_deep_message or "内侧终点" not in solid_deep_message:
|
||||
raise SystemExit(
|
||||
f"solid top-boundary cylinder deep inward blocker should explain the retract limit: {solid_deep_plan}"
|
||||
)
|
||||
print(
|
||||
"solid top-boundary cylinder cap deep retraction blocker ok: "
|
||||
f"message={solid_deep_message}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user