feat: 完善 Face 参数化编辑和隔离执行
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import math
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -52,6 +53,32 @@ def _nearest_sphere_radius(model: StepModel, target_radius: float) -> tuple[int,
|
||||
return best[0], best[1]
|
||||
|
||||
|
||||
def _nearest_cone_semi_angle(model: StepModel, target_angle_degrees: float) -> tuple[int, float]:
|
||||
best: tuple[int, float, float] | None = None
|
||||
for face_id in range(len(model.faces)):
|
||||
info = model.face_info(face_id)
|
||||
if info.get("surface") != "cone":
|
||||
continue
|
||||
angle = info.get("semi_angle")
|
||||
if not isinstance(angle, (int, float)):
|
||||
continue
|
||||
angle_degrees = abs(math.degrees(float(angle)))
|
||||
score = abs(angle_degrees - target_angle_degrees)
|
||||
if best is None or score < best[2]:
|
||||
best = (face_id, angle_degrees, score)
|
||||
if best is None:
|
||||
diameters = _thin_cap_diameters(model)
|
||||
if len(diameters) >= 2:
|
||||
small = min(diameters)
|
||||
large = max(diameters)
|
||||
height = _bbox_z_size(model)
|
||||
if height > 1e-9 and large > small:
|
||||
angle_degrees = math.degrees(math.atan(((large - small) * 0.5) / height))
|
||||
return -1, angle_degrees
|
||||
raise SystemExit("no cone Face remained after edit and cap diameters could not recover the semi-angle")
|
||||
return best[0], best[1]
|
||||
|
||||
|
||||
def _nearest_torus_radii(
|
||||
model: StepModel,
|
||||
target_major: float,
|
||||
@@ -73,17 +100,22 @@ def _nearest_torus_radii(
|
||||
|
||||
|
||||
def _has_thin_cap_diameter(model: StepModel, target_diameter: float, tolerance: float) -> bool:
|
||||
return any(abs(diameter - target_diameter) <= tolerance for diameter in _thin_cap_diameters(model))
|
||||
|
||||
|
||||
def _thin_cap_diameters(model: StepModel) -> list[float]:
|
||||
diameters: list[float] = []
|
||||
for face_id in range(len(model.faces)):
|
||||
info = model.face_info(face_id)
|
||||
size = info.get("bbox_size")
|
||||
if not isinstance(size, tuple) or len(size) != 3:
|
||||
continue
|
||||
dx, dy, dz = (float(size[0]), float(size[1]), float(size[2]))
|
||||
if dz > max(tolerance * 10.0, 1e-5):
|
||||
if dz > 1e-4:
|
||||
continue
|
||||
if abs(dx - target_diameter) <= tolerance and abs(dy - target_diameter) <= tolerance:
|
||||
return True
|
||||
return False
|
||||
if abs(dx - dy) <= max(max(abs(dx), abs(dy)) * 1e-5, 1e-5):
|
||||
diameters.append((dx + dy) * 0.5)
|
||||
return diameters
|
||||
|
||||
|
||||
def _bbox_z_size(model: StepModel) -> float:
|
||||
@@ -128,6 +160,51 @@ def _run_cone_case(target_reference_radius: float, tolerance: float) -> None:
|
||||
print(result.encode("ascii", "backslashreplace").decode("ascii"))
|
||||
|
||||
|
||||
def _run_cone_angle_case(target_angle_degrees: float, tolerance: float) -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="geom_param_cone_angle_") as temp_dir:
|
||||
model_path = Path(temp_dir) / "cone.step"
|
||||
_write_cone_model(model_path)
|
||||
model = StepModel.load(model_path)
|
||||
face_id = _first_face_by_surface(model, "cone")
|
||||
before = model.stats()
|
||||
info = model.face_info(face_id)
|
||||
current_reference_radius = float(info.get("reference_radius") or 0.0)
|
||||
current_semi_angle = abs(float(info.get("semi_angle") or 0.0))
|
||||
current_top_radius = 2.0
|
||||
scale = math.tan(math.radians(target_angle_degrees)) / math.tan(current_semi_angle)
|
||||
target_reference_radius = current_reference_radius * scale
|
||||
target_top_radius = current_top_radius * scale
|
||||
plan = model.conical_semi_angle_plan(face_id, target_angle_degrees)
|
||||
if plan["status"] == "blocked":
|
||||
raise SystemExit(f"cone angle plan was blocked: {plan['message']}")
|
||||
result = model.resize_conical_semi_angle(face_id, target_angle_degrees)
|
||||
after = model.stats()
|
||||
if after.solids != before.solids:
|
||||
raise SystemExit(f"cone angle resize changed solid count: before={before.solids}, after={after.solids}")
|
||||
if abs(_bbox_z_size(model) - 10.0) > tolerance:
|
||||
raise SystemExit(f"cone angle resize changed height unexpectedly: z_size={_bbox_z_size(model):g}")
|
||||
if not _has_thin_cap_diameter(model, target_reference_radius * 2.0, tolerance):
|
||||
raise SystemExit(f"cone angle bottom cap diameter was not resized to {target_reference_radius * 2.0:g}")
|
||||
if not _has_thin_cap_diameter(model, target_top_radius * 2.0, tolerance):
|
||||
raise SystemExit(f"cone angle top cap diameter was not resized to {target_top_radius * 2.0:g}")
|
||||
verified_face, angle_degrees = _nearest_cone_semi_angle(model, target_angle_degrees)
|
||||
if abs(angle_degrees - target_angle_degrees) > tolerance:
|
||||
raise SystemExit(
|
||||
f"cone semi-angle verification failed: target={target_angle_degrees:g}, value={angle_degrees:g}"
|
||||
)
|
||||
print("mode=cone_semi_angle")
|
||||
print(f"face_id={face_id}")
|
||||
print(f"strategy={plan.get('resize_strategy')}")
|
||||
print(f"before={before}")
|
||||
print(f"after={after}")
|
||||
print(f"current_semi_angle_degrees={math.degrees(current_semi_angle):.6f}")
|
||||
print(f"target_semi_angle_degrees={target_angle_degrees:.6f} verified={angle_degrees:.6f}")
|
||||
print(f"target_reference_radius={target_reference_radius:.6f}")
|
||||
print(f"target_top_radius={target_top_radius:.6f}")
|
||||
print(f"verified_face={verified_face}")
|
||||
print(result.encode("ascii", "backslashreplace").decode("ascii"))
|
||||
|
||||
|
||||
def _run_sphere_case(target_radius: float, tolerance: float) -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="geom_param_sphere_") as temp_dir:
|
||||
model_path = Path(temp_dir) / "sphere.step"
|
||||
@@ -199,19 +276,22 @@ def main() -> int:
|
||||
parser.add_argument(
|
||||
"--mode",
|
||||
default="all",
|
||||
choices=["all", "cone", "sphere", "torus_major", "torus_minor"],
|
||||
choices=["all", "cone", "cone_angle", "sphere", "torus_major", "torus_minor"],
|
||||
)
|
||||
parser.add_argument("--cone-reference-radius", type=float, default=5.0)
|
||||
parser.add_argument("--cone-semi-angle-degrees", type=float, default=16.0)
|
||||
parser.add_argument("--sphere-radius", type=float, default=6.25)
|
||||
parser.add_argument("--torus-major-radius", type=float, default=10.0)
|
||||
parser.add_argument("--torus-minor-radius", type=float, default=3.0)
|
||||
parser.add_argument("--tolerance", type=float, default=2e-4)
|
||||
args = parser.parse_args()
|
||||
|
||||
modes = ["cone", "sphere", "torus_major", "torus_minor"] if args.mode == "all" else [args.mode]
|
||||
modes = ["cone", "cone_angle", "sphere", "torus_major", "torus_minor"] if args.mode == "all" else [args.mode]
|
||||
for mode in modes:
|
||||
if mode == "cone":
|
||||
_run_cone_case(args.cone_reference_radius, args.tolerance)
|
||||
elif mode == "cone_angle":
|
||||
_run_cone_angle_case(args.cone_semi_angle_degrees, args.tolerance)
|
||||
elif mode == "sphere":
|
||||
_run_sphere_case(args.sphere_radius, args.tolerance)
|
||||
elif mode == "torus_major":
|
||||
|
||||
Reference in New Issue
Block a user