1107 lines
47 KiB
Python
1107 lines
47 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
|
if str(PROJECT_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(PROJECT_ROOT))
|
|
SCRIPTS_ROOT = PROJECT_ROOT / "scripts"
|
|
if str(SCRIPTS_ROOT) not in sys.path:
|
|
sys.path.insert(0, str(SCRIPTS_ROOT))
|
|
|
|
from step_editor.model import StepModel
|
|
from verify_hole_resize import (
|
|
_axis_center,
|
|
_depth,
|
|
_first_hole_face,
|
|
_hole_face_ids,
|
|
_nearest_blind_depth,
|
|
_nearest_hole_by_diameter,
|
|
_volume,
|
|
_write_blind_hole_model,
|
|
_write_through_hole_model,
|
|
)
|
|
from verify_slot_resize import (
|
|
_first_slot_face,
|
|
_nearest_axis_center,
|
|
_nearest_metric,
|
|
_nearest_obround_axis_pair,
|
|
_nearest_obround_total_length,
|
|
_slot_axis_center,
|
|
_slot_metric,
|
|
_write_half_round_slot_model,
|
|
_write_obround_slot_model,
|
|
)
|
|
|
|
|
|
def _run_worker(request_path: Path) -> dict[str, object]:
|
|
completed = subprocess.run(
|
|
[sys.executable, "-m", "step_editor.isolated_edit_worker", str(request_path)],
|
|
cwd=PROJECT_ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
response_path = request_path.with_suffix(".response.json")
|
|
if completed.returncode != 0:
|
|
stderr = completed.stderr.strip()
|
|
response = response_path.read_text(encoding="utf-8-sig") if response_path.exists() else ""
|
|
raise SystemExit(
|
|
f"isolated worker failed: returncode={completed.returncode}, stderr={stderr}, response={response}"
|
|
)
|
|
if not response_path.exists():
|
|
raise SystemExit("isolated worker did not write a response JSON file")
|
|
response = json.loads(response_path.read_text(encoding="utf-8-sig"))
|
|
if not response.get("ok"):
|
|
raise SystemExit(f"isolated worker returned an error response: {response}")
|
|
return response
|
|
|
|
|
|
def _run_worker_allow_failure(request_path: Path) -> tuple[int, dict[str, object]]:
|
|
completed = subprocess.run(
|
|
[sys.executable, "-m", "step_editor.isolated_edit_worker", str(request_path)],
|
|
cwd=PROJECT_ROOT,
|
|
text=True,
|
|
capture_output=True,
|
|
check=False,
|
|
)
|
|
response_path = request_path.with_suffix(".response.json")
|
|
if not response_path.exists():
|
|
raise SystemExit(
|
|
f"isolated worker did not write a response JSON file; "
|
|
f"returncode={completed.returncode}, stderr={completed.stderr.strip()}"
|
|
)
|
|
response = json.loads(response_path.read_text(encoding="utf-8-sig"))
|
|
return completed.returncode, response
|
|
|
|
|
|
def _write_request(
|
|
request_path: Path,
|
|
input_path: Path,
|
|
output_path: Path,
|
|
operation: str,
|
|
args: list[object],
|
|
) -> None:
|
|
request_path.write_text(
|
|
json.dumps(
|
|
{
|
|
"input_path": str(input_path),
|
|
"output_path": str(output_path),
|
|
"operation": operation,
|
|
"args": args,
|
|
},
|
|
ensure_ascii=False,
|
|
indent=2,
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
def _assert_one_solid(model: StepModel, label: str) -> None:
|
|
if model.stats().solids != 1:
|
|
raise SystemExit(f"{label} should keep one Solid: {model.stats()}")
|
|
|
|
|
|
def _logical_region_has_diameter(model: StepModel, logical_id: int, target_diameter: float) -> bool:
|
|
tolerance = max(abs(target_diameter) * 1e-4, 1e-5)
|
|
for face_id in model.face_ids_for_logical_id(logical_id):
|
|
try:
|
|
diameter = float(model.face_info(face_id).get("diameter"))
|
|
except (TypeError, ValueError):
|
|
continue
|
|
if abs(diameter - target_diameter) <= tolerance:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _logical_region_has_slot_metric(model: StepModel, logical_id: int, key: str, target_value: float) -> bool:
|
|
tolerance = max(abs(target_value) * 1e-4, 1e-5)
|
|
for face_id in model.face_ids_for_logical_id(logical_id):
|
|
try:
|
|
feature = model.feature_info(face_id)
|
|
info = model.face_info(face_id)
|
|
value = feature.get(key)
|
|
if value is None:
|
|
value = info.get(key)
|
|
value = float(value)
|
|
except BaseException:
|
|
continue
|
|
if abs(value - target_value) <= tolerance:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _logical_region_has_blind_depth(model: StepModel, logical_id: int, target_depth: float) -> bool:
|
|
tolerance = max(abs(target_depth) * 1e-4, 1e-5)
|
|
for face_id in model.face_ids_for_logical_id(logical_id):
|
|
try:
|
|
depth = _depth(model, face_id)
|
|
except BaseException:
|
|
continue
|
|
if abs(depth - target_depth) <= tolerance:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _logical_region_has_hole_axis(
|
|
model: StepModel,
|
|
logical_id: int,
|
|
target_center: tuple[float, float, float],
|
|
target_diameter: float,
|
|
) -> bool:
|
|
for face_id in model.face_ids_for_logical_id(logical_id):
|
|
try:
|
|
center = _axis_center(model, face_id)
|
|
diameter = float(model.face_info(face_id).get("diameter"))
|
|
except BaseException:
|
|
continue
|
|
center_error = sum((center[index] - target_center[index]) ** 2 for index in range(3)) ** 0.5
|
|
diameter_error = abs(diameter - target_diameter)
|
|
if center_error <= 1e-4 and diameter_error <= max(abs(target_diameter) * 1e-4, 1e-5):
|
|
return True
|
|
return False
|
|
|
|
|
|
def _logical_region_has_slot_axis(
|
|
model: StepModel,
|
|
logical_id: int,
|
|
target_center: tuple[float, float, float],
|
|
) -> bool:
|
|
for face_id in model.face_ids_for_logical_id(logical_id):
|
|
try:
|
|
center = _slot_axis_center(model, face_id)
|
|
except BaseException:
|
|
continue
|
|
center_error = sum((center[index] - target_center[index]) ** 2 for index in range(3)) ** 0.5
|
|
if center_error <= 1e-4:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _logical_region_contains_any(model: StepModel, logical_id: int, face_ids: tuple[int, ...]) -> bool:
|
|
logical_faces = set(model.face_ids_for_logical_id(logical_id))
|
|
return any(face_id in logical_faces for face_id in face_ids)
|
|
|
|
|
|
def _window_probe_for(input_path: Path):
|
|
from step_editor.window_actions import WindowActionMixin
|
|
|
|
class _WindowProbe(WindowActionMixin):
|
|
def __init__(self) -> None:
|
|
self.model = StepModel.load(input_path)
|
|
self.step_path = input_path
|
|
self.current_info_values = {}
|
|
self.selected_pick_position = None
|
|
self.isolated_edit_cancel_requested = False
|
|
self.active_isolated_edit_process = None
|
|
|
|
return _WindowProbe()
|
|
|
|
|
|
def _verify_hole_diameter_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "through_hole.step"
|
|
output_path = temp_dir / "through_hole_out.step"
|
|
request_path = temp_dir / "hole_request.json"
|
|
_write_through_hole_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_hole_face(model, blind=False)
|
|
target_center = _axis_center(model, face_id)
|
|
target_diameter = 8.0
|
|
|
|
_write_request(request_path, input_path, output_path, "resize_cylindrical_hole", [face_id, target_diameter])
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, diameter, center, error = _nearest_hole_by_diameter(
|
|
result_model,
|
|
target_diameter,
|
|
target_center,
|
|
blind=False,
|
|
)
|
|
if error > 1e-4 or abs(diameter - target_diameter) > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated hole resize verification failed: face={verified_face}, "
|
|
f"diameter={diameter:g}, center={center}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated hole resize")
|
|
print(f"isolated hole resize ok: face={verified_face}, diameter={diameter:g}")
|
|
|
|
|
|
def _verify_hole_owning_scale_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "through_hole_scale.step"
|
|
output_path = temp_dir / "through_hole_scale_out.step"
|
|
request_path = temp_dir / "hole_scale_request.json"
|
|
_write_through_hole_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_hole_face(model, blind=False)
|
|
target_center = _axis_center(model, face_id)
|
|
target_diameter = 8.0
|
|
|
|
_write_request(request_path, input_path, output_path, "resize_cylindrical_owning_scale", [face_id, target_diameter])
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, diameter, center, error = _nearest_hole_by_diameter(
|
|
result_model,
|
|
target_diameter,
|
|
target_center,
|
|
blind=False,
|
|
)
|
|
if error > 1e-4 or abs(diameter - target_diameter) > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated hole owning-scale verification failed: face={verified_face}, "
|
|
f"diameter={diameter:g}, center={center}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated hole owning-scale")
|
|
print(f"isolated hole owning-scale ok: face={verified_face}, diameter={diameter:g}")
|
|
|
|
|
|
def _verify_hole_axis_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "through_hole_axis.step"
|
|
output_path = temp_dir / "through_hole_axis_out.step"
|
|
request_path = temp_dir / "hole_axis_request.json"
|
|
_write_through_hole_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_hole_face(model, blind=False)
|
|
current_center = _axis_center(model, face_id)
|
|
target_center = (current_center[0] + 2.0, current_center[1], current_center[2])
|
|
current_diameter = float(model.face_info(face_id)["diameter"])
|
|
|
|
_write_request(request_path, input_path, output_path, "move_cylindrical_hole_axis", [face_id, target_center])
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, diameter, center, error = _nearest_hole_by_diameter(
|
|
result_model,
|
|
current_diameter,
|
|
target_center,
|
|
blind=False,
|
|
)
|
|
if error > 1e-4 or abs(diameter - current_diameter) > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated hole axis move verification failed: face={verified_face}, "
|
|
f"diameter={diameter:g}, center={center}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated hole axis move")
|
|
print(f"isolated hole axis move ok: face={verified_face}, center={center}")
|
|
|
|
|
|
def _verify_hole_suppress_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "through_hole_suppress.step"
|
|
output_path = temp_dir / "through_hole_suppress_out.step"
|
|
request_path = temp_dir / "hole_suppress_request.json"
|
|
_write_through_hole_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_hole_face(model, blind=False)
|
|
before_volume = _volume(model)
|
|
|
|
_write_request(request_path, input_path, output_path, "suppress_cylindrical_hole", [face_id])
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
after_volume = _volume(result_model)
|
|
remaining_holes = _hole_face_ids(result_model)
|
|
full_box_volume = 30.0 * 20.0 * 8.0
|
|
if remaining_holes:
|
|
raise SystemExit(f"isolated hole suppress left hole faces: {remaining_holes}, response={response}")
|
|
if abs(after_volume - full_box_volume) > 1e-3:
|
|
raise SystemExit(
|
|
f"isolated hole suppress volume failed: before={before_volume:g}, "
|
|
f"after={after_volume:g}, target={full_box_volume:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated hole suppress")
|
|
print(f"isolated hole suppress ok: volume={after_volume:g}")
|
|
|
|
|
|
def _verify_blind_depth_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "blind_hole.step"
|
|
output_path = temp_dir / "blind_hole_out.step"
|
|
request_path = temp_dir / "blind_depth_request.json"
|
|
_write_blind_hole_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_hole_face(model, blind=True)
|
|
feature = model.feature_info(face_id)
|
|
bottom_face_ids = tuple(feature.get("feature_bottom_face_ids", ()))
|
|
bottom_face_id = int(bottom_face_ids[0]) if bottom_face_ids else None
|
|
target_depth = 8.0
|
|
|
|
_write_request(request_path, input_path, output_path, "resize_cylindrical_depth", [face_id, target_depth, bottom_face_id])
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, depth, error = _nearest_blind_depth(result_model, target_depth)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated blind depth verification failed: face={verified_face}, "
|
|
f"depth={depth:g}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated blind depth")
|
|
print(f"isolated blind depth ok: face={verified_face}, depth={depth:g}")
|
|
|
|
|
|
def _verify_blind_depth_owning_scale_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "blind_hole_scale.step"
|
|
output_path = temp_dir / "blind_hole_scale_out.step"
|
|
request_path = temp_dir / "blind_depth_scale_request.json"
|
|
_write_blind_hole_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_hole_face(model, blind=True)
|
|
feature = model.feature_info(face_id)
|
|
bottom_face_ids = tuple(feature.get("feature_bottom_face_ids", ()))
|
|
bottom_face_id = int(bottom_face_ids[0]) if bottom_face_ids else None
|
|
target_depth = 8.0
|
|
|
|
_write_request(
|
|
request_path,
|
|
input_path,
|
|
output_path,
|
|
"resize_cylindrical_depth_owning_scale",
|
|
[face_id, target_depth, bottom_face_id],
|
|
)
|
|
returncode, response = _run_worker_allow_failure(request_path)
|
|
if returncode != 0:
|
|
error = str(response.get("error", ""))
|
|
if "rolled back" not in error and "回滚" not in error:
|
|
raise SystemExit(f"isolated blind depth owning-scale failed without rollback message: {response}")
|
|
print("isolated blind depth owning-scale guarded ok: result rejected and rolled back")
|
|
return
|
|
|
|
if not response.get("ok"):
|
|
raise SystemExit(f"isolated blind depth owning-scale returned an error response: {response}")
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, depth, error = _nearest_blind_depth(result_model, target_depth)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated blind depth owning-scale verification failed: face={verified_face}, "
|
|
f"depth={depth:g}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated blind depth owning-scale")
|
|
print(f"isolated blind depth owning-scale ok: face={verified_face}, depth={depth:g}")
|
|
|
|
|
|
def _verify_slot_width_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "half_round_slot.step"
|
|
output_path = temp_dir / "half_round_slot_out.step"
|
|
request_path = temp_dir / "slot_request.json"
|
|
_write_half_round_slot_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_slot_face(model)
|
|
target_width = 8.0
|
|
|
|
_write_request(request_path, input_path, output_path, "resize_cylindrical_slot_width", [face_id, target_width, None])
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, width, error = _nearest_metric(result_model, "slot_chord_width_estimate", target_width)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated slot width verification failed: face={verified_face}, "
|
|
f"width={width:g}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated slot width resize")
|
|
print(f"isolated slot width resize ok: face={verified_face}, width={width:g}")
|
|
|
|
|
|
def _verify_slot_owning_scale_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "half_round_slot_scale.step"
|
|
output_path = temp_dir / "half_round_slot_scale_out.step"
|
|
request_path = temp_dir / "slot_scale_request.json"
|
|
_write_half_round_slot_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_slot_face(model)
|
|
target_diameter = 8.0
|
|
target_width = 8.0
|
|
|
|
_write_request(request_path, input_path, output_path, "resize_cylindrical_owning_scale", [face_id, target_diameter])
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, width, error = _nearest_metric(result_model, "slot_chord_width_estimate", target_width)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated slot owning-scale verification failed: face={verified_face}, "
|
|
f"width={width:g}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated slot owning-scale")
|
|
print(f"isolated slot owning-scale ok: face={verified_face}, width={width:g}")
|
|
|
|
|
|
def _verify_slot_metric_isolated(
|
|
temp_dir: Path,
|
|
stem: str,
|
|
operation: str,
|
|
target_value: float,
|
|
metric_key: str,
|
|
) -> None:
|
|
input_path = temp_dir / f"{stem}.step"
|
|
output_path = temp_dir / f"{stem}_out.step"
|
|
request_path = temp_dir / f"{stem}_request.json"
|
|
_write_half_round_slot_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_slot_face(model)
|
|
|
|
args: list[object] = [face_id, target_value]
|
|
if operation in {
|
|
"resize_cylindrical_slot_width",
|
|
"resize_cylindrical_slot_depth",
|
|
"resize_cylindrical_slot_arc_length",
|
|
}:
|
|
args.append(None)
|
|
_write_request(request_path, input_path, output_path, operation, args)
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, value, error = _nearest_metric(result_model, metric_key, target_value)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated {stem} verification failed: face={verified_face}, "
|
|
f"value={value:g}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, f"isolated {stem}")
|
|
print(f"isolated {stem} ok: face={verified_face}, value={value:g}")
|
|
|
|
|
|
def _verify_slot_axis_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "half_round_slot_axis.step"
|
|
output_path = temp_dir / "half_round_slot_axis_out.step"
|
|
request_path = temp_dir / "slot_axis_request.json"
|
|
_write_half_round_slot_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id = _first_slot_face(model)
|
|
current_center = _slot_axis_center(model, face_id)
|
|
target_center = (current_center[0], current_center[1] + 1.0, current_center[2])
|
|
|
|
_write_request(request_path, input_path, output_path, "move_cylindrical_slot_axis", [face_id, target_center])
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, center, error = _nearest_axis_center(result_model, target_center)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated slot axis move verification failed: face={verified_face}, "
|
|
f"center={center}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated slot axis move")
|
|
print(f"isolated slot axis move ok: face={verified_face}, center={center}")
|
|
|
|
|
|
def _verify_obround_slot_total_length_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "obround_total_length.step"
|
|
output_path = temp_dir / "obround_total_length_out.step"
|
|
request_path = temp_dir / "obround_total_length_request.json"
|
|
_write_obround_slot_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id, pair_face_id, _current_total_length, _current_center_distance, _ = _nearest_obround_total_length(model, 16.0)
|
|
target_total_length = 20.0
|
|
|
|
_write_request(
|
|
request_path,
|
|
input_path,
|
|
output_path,
|
|
"resize_cylindrical_slot_total_length",
|
|
[face_id, target_total_length, pair_face_id],
|
|
)
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, verified_pair, total_length, center_distance, error = _nearest_obround_total_length(
|
|
result_model,
|
|
target_total_length,
|
|
)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated obround total length verification failed: faces=({verified_face}, {verified_pair}), "
|
|
f"total_length={total_length:g}, center_distance={center_distance:g}, error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated obround total length")
|
|
print(f"isolated obround total length ok: faces=({verified_face}, {verified_pair}), total_length={total_length:g}")
|
|
|
|
|
|
def _verify_obround_slot_center_distance_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "obround_center_distance.step"
|
|
output_path = temp_dir / "obround_center_distance_out.step"
|
|
request_path = temp_dir / "obround_center_distance_request.json"
|
|
_write_obround_slot_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id, pair_face_id, current_total_length, current_center_distance, _ = _nearest_obround_total_length(model, 16.0)
|
|
slot_diameter = current_total_length - current_center_distance
|
|
target_center_distance = 14.0
|
|
target_total_length = target_center_distance + slot_diameter
|
|
|
|
_write_request(
|
|
request_path,
|
|
input_path,
|
|
output_path,
|
|
"resize_cylindrical_slot_center_distance",
|
|
[face_id, target_center_distance, pair_face_id],
|
|
)
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, verified_pair, total_length, center_distance, total_error = _nearest_obround_total_length(
|
|
result_model,
|
|
target_total_length,
|
|
)
|
|
center_error = abs(center_distance - target_center_distance)
|
|
if center_error > 1e-4 or total_error > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated obround center distance verification failed: faces=({verified_face}, {verified_pair}), "
|
|
f"total_length={total_length:g}, center_distance={center_distance:g}, "
|
|
f"center_error={center_error:g}, total_error={total_error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated obround center distance")
|
|
print(
|
|
"isolated obround center distance ok: "
|
|
f"faces=({verified_face}, {verified_pair}), center_distance={center_distance:g}"
|
|
)
|
|
|
|
|
|
def _verify_obround_slot_axis_isolated(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "obround_axis.step"
|
|
output_path = temp_dir / "obround_axis_out.step"
|
|
request_path = temp_dir / "obround_axis_request.json"
|
|
_write_obround_slot_model(input_path)
|
|
model = StepModel.load(input_path)
|
|
face_id, pair_face_id, _current_total_length, _current_center_distance, _ = _nearest_obround_total_length(model, 16.0)
|
|
current_center = _slot_axis_center(model, face_id)
|
|
pair_center = _slot_axis_center(model, pair_face_id)
|
|
target_center = (current_center[0], current_center[1] + 1.0, current_center[2])
|
|
target_pair_center = (pair_center[0], pair_center[1] + 1.0, pair_center[2])
|
|
|
|
_write_request(request_path, input_path, output_path, "move_cylindrical_slot_axis", [face_id, target_center])
|
|
response = _run_worker(request_path)
|
|
result_model = StepModel.load(output_path)
|
|
verified_face, verified_pair, center_1, center_2, error = _nearest_obround_axis_pair(
|
|
result_model,
|
|
target_center,
|
|
target_pair_center,
|
|
)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"isolated obround axis move verification failed: faces=({verified_face}, {verified_pair}), "
|
|
f"centers=({center_1}, {center_2}), error={error:g}, response={response}"
|
|
)
|
|
_assert_one_solid(result_model, "isolated obround axis move")
|
|
print(f"isolated obround axis move ok: faces=({verified_face}, {verified_pair}), error={error:g}")
|
|
|
|
|
|
def _verify_hole_window_isolation_keeps_logical_id(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "through_hole_window.step"
|
|
_write_through_hole_model(input_path)
|
|
|
|
probe = _window_probe_for(input_path)
|
|
face_id = _first_hole_face(probe.model, blind=False)
|
|
logical_id = probe.model.face_logical_id(face_id)
|
|
target_diameter = 8.0
|
|
target_center = _axis_center(probe.model, face_id)
|
|
plan = probe.model.cylindrical_resize_plan(face_id, target_diameter)
|
|
context = {
|
|
"operation_name": "调整圆柱孔径",
|
|
"target": f"Face {face_id}",
|
|
"parameters": {
|
|
"part_id": plan.get("part_id"),
|
|
"solid_id": plan.get("solid_id"),
|
|
"face_id": face_id,
|
|
"new_diameter": target_diameter,
|
|
"old_diameter": plan.get("current_diameter"),
|
|
"resize_strategy": plan.get("resize_strategy"),
|
|
},
|
|
"target_kind": "face",
|
|
"target_id": face_id,
|
|
"target_logical_id": logical_id,
|
|
"pick_position": None,
|
|
"show_same_domain_internal_edges": False,
|
|
"edit_result_deflection": 1.6,
|
|
}
|
|
if probe._context_is_face_parameter_edit(context):
|
|
raise SystemExit("hole resize should not be treated as a generic Face parameter edit")
|
|
if not probe._context_should_preserve_face_logical_id(context):
|
|
raise SystemExit("hole resize should still preserve the selected logical Face ID")
|
|
|
|
result = probe._run_isolated_edit_job(
|
|
context=context,
|
|
isolation={
|
|
"operation": "resize_cylindrical_hole",
|
|
"args": [face_id, target_diameter],
|
|
"timeout_seconds": 120.0,
|
|
},
|
|
snapshot=probe.model.snapshot(),
|
|
before_stats=probe.model.stats(),
|
|
before_part_stats=probe.model.part_topology_stats(int(plan.get("part_id") or 1)),
|
|
before_quality=probe._edit_quality_info_or_none(probe.model, context, int(plan.get("part_id") or 1)),
|
|
before_geometry={},
|
|
)
|
|
after_model = result.get("after_model")
|
|
if not isinstance(after_model, StepModel):
|
|
raise SystemExit(f"window hole isolation should return after_model: {result}")
|
|
verified_face, diameter, center, error = _nearest_hole_by_diameter(
|
|
after_model,
|
|
target_diameter,
|
|
target_center,
|
|
blind=False,
|
|
)
|
|
if error > 1e-4 or abs(diameter - target_diameter) > 1e-4:
|
|
raise SystemExit(
|
|
f"window hole isolation verification failed: face={verified_face}, "
|
|
f"diameter={diameter:g}, center={center}, error={error:g}, result={result}"
|
|
)
|
|
if not _logical_region_has_diameter(after_model, logical_id, target_diameter):
|
|
raise SystemExit(
|
|
f"window hole isolation did not preserve logical Face {logical_id} on the resized hole"
|
|
)
|
|
_assert_one_solid(after_model, "window hole isolation")
|
|
print(f"window hole isolation ok: logical={logical_id}, face={verified_face}, diameter={diameter:g}")
|
|
|
|
|
|
def _verify_hole_axis_window_isolation_keeps_logical_id(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "through_hole_axis_window.step"
|
|
_write_through_hole_model(input_path)
|
|
|
|
probe = _window_probe_for(input_path)
|
|
face_id = _first_hole_face(probe.model, blind=False)
|
|
logical_id = probe.model.face_logical_id(face_id)
|
|
current_center = _axis_center(probe.model, face_id)
|
|
current_diameter = float(probe.model.face_info(face_id)["diameter"])
|
|
target_center = (current_center[0] + 2.0, current_center[1], current_center[2])
|
|
plan = probe.model.cylindrical_axis_move_plan(face_id, target_center)
|
|
context = {
|
|
"operation_name": "move hole axis",
|
|
"target": f"Face {face_id}",
|
|
"parameters": {
|
|
"part_id": plan.get("part_id"),
|
|
"solid_id": plan.get("solid_id"),
|
|
"face_id": face_id,
|
|
"current_center": current_center,
|
|
"target_center": target_center,
|
|
"current_diameter": current_diameter,
|
|
"resize_strategy": plan.get("resize_strategy"),
|
|
},
|
|
"target_kind": "face",
|
|
"target_id": face_id,
|
|
"target_logical_id": logical_id,
|
|
"pick_position": None,
|
|
"show_same_domain_internal_edges": False,
|
|
"edit_result_deflection": 1.6,
|
|
}
|
|
if probe._context_is_face_parameter_edit(context):
|
|
raise SystemExit("hole axis move should not be treated as a generic Face parameter edit")
|
|
if not probe._context_should_preserve_face_logical_id(context):
|
|
raise SystemExit("hole axis move should still preserve the selected logical Face ID")
|
|
|
|
result = probe._run_isolated_edit_job(
|
|
context=context,
|
|
isolation={
|
|
"operation": "move_cylindrical_hole_axis",
|
|
"args": [face_id, target_center],
|
|
"timeout_seconds": 120.0,
|
|
},
|
|
snapshot=probe.model.snapshot(),
|
|
before_stats=probe.model.stats(),
|
|
before_part_stats=probe.model.part_topology_stats(int(plan.get("part_id") or 1)),
|
|
before_quality=probe._edit_quality_info_or_none(probe.model, context, int(plan.get("part_id") or 1)),
|
|
before_geometry={},
|
|
)
|
|
after_model = result.get("after_model")
|
|
if not isinstance(after_model, StepModel):
|
|
raise SystemExit(f"window hole-axis isolation should return after_model: {result}")
|
|
verified_face, diameter, center, error = _nearest_hole_by_diameter(
|
|
after_model,
|
|
current_diameter,
|
|
target_center,
|
|
blind=False,
|
|
)
|
|
if error > 1e-4 or abs(diameter - current_diameter) > 1e-4:
|
|
raise SystemExit(
|
|
f"window hole-axis isolation verification failed: face={verified_face}, "
|
|
f"diameter={diameter:g}, center={center}, error={error:g}, result={result}"
|
|
)
|
|
if not _logical_region_has_hole_axis(after_model, logical_id, target_center, current_diameter):
|
|
raise SystemExit(
|
|
f"window hole-axis isolation did not preserve logical Face {logical_id} on the moved hole"
|
|
)
|
|
_assert_one_solid(after_model, "window hole-axis isolation")
|
|
print(f"window hole-axis isolation ok: logical={logical_id}, face={verified_face}, center={center}")
|
|
|
|
|
|
def _verify_slot_window_isolation_keeps_logical_id(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "slot_window.step"
|
|
_write_half_round_slot_model(input_path)
|
|
|
|
probe = _window_probe_for(input_path)
|
|
face_id = _first_slot_face(probe.model)
|
|
logical_id = probe.model.face_logical_id(face_id)
|
|
target_width = 8.0
|
|
plan = probe.model.cylindrical_slot_resize_plan(face_id, target_width, "width")
|
|
context = {
|
|
"operation_name": "resize slot width",
|
|
"target": f"Face {face_id}",
|
|
"parameters": {
|
|
"part_id": plan.get("part_id"),
|
|
"solid_id": plan.get("solid_id"),
|
|
"face_id": face_id,
|
|
"target_width": target_width,
|
|
"current_width": plan.get("slot_current_width"),
|
|
"resize_strategy": plan.get("resize_strategy"),
|
|
"slot_pair_face_id": plan.get("slot_pair_face_id"),
|
|
},
|
|
"target_kind": "face",
|
|
"target_id": face_id,
|
|
"target_logical_id": logical_id,
|
|
"pick_position": None,
|
|
"show_same_domain_internal_edges": False,
|
|
"edit_result_deflection": 1.6,
|
|
}
|
|
if probe._context_is_face_parameter_edit(context):
|
|
raise SystemExit("slot width should not be treated as a generic Face parameter edit")
|
|
if not probe._context_should_preserve_face_logical_id(context):
|
|
raise SystemExit("slot width should still preserve the selected logical Face ID")
|
|
|
|
result = probe._run_isolated_edit_job(
|
|
context=context,
|
|
isolation={
|
|
"operation": "resize_cylindrical_slot_width",
|
|
"args": [face_id, target_width, None],
|
|
"timeout_seconds": 120.0,
|
|
},
|
|
snapshot=probe.model.snapshot(),
|
|
before_stats=probe.model.stats(),
|
|
before_part_stats=probe.model.part_topology_stats(int(plan.get("part_id") or 1)),
|
|
before_quality=probe._edit_quality_info_or_none(probe.model, context, int(plan.get("part_id") or 1)),
|
|
before_geometry={},
|
|
)
|
|
after_model = result.get("after_model")
|
|
if not isinstance(after_model, StepModel):
|
|
raise SystemExit(f"window slot isolation should return after_model: {result}")
|
|
verified_face, width, error = _nearest_metric(after_model, "slot_chord_width_estimate", target_width)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"window slot isolation verification failed: face={verified_face}, "
|
|
f"width={width:g}, error={error:g}, result={result}"
|
|
)
|
|
if not _logical_region_has_slot_metric(after_model, logical_id, "slot_chord_width_estimate", target_width):
|
|
raise SystemExit(
|
|
f"window slot isolation did not preserve logical Face {logical_id} on the resized slot"
|
|
)
|
|
_assert_one_solid(after_model, "window slot isolation")
|
|
print(f"window slot isolation ok: logical={logical_id}, face={verified_face}, width={width:g}")
|
|
|
|
|
|
def _verify_slot_axis_window_isolation_keeps_logical_id(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "slot_axis_window.step"
|
|
_write_half_round_slot_model(input_path)
|
|
|
|
probe = _window_probe_for(input_path)
|
|
face_id = _first_slot_face(probe.model)
|
|
logical_id = probe.model.face_logical_id(face_id)
|
|
current_center = _slot_axis_center(probe.model, face_id)
|
|
target_center = (current_center[0], current_center[1] + 1.0, current_center[2])
|
|
plan = probe.model.cylindrical_slot_axis_move_plan(face_id, target_center)
|
|
context = {
|
|
"operation_name": "move slot axis",
|
|
"target": f"Face {face_id}",
|
|
"parameters": {
|
|
"part_id": plan.get("part_id"),
|
|
"solid_id": plan.get("solid_id"),
|
|
"face_id": face_id,
|
|
"current_center": current_center,
|
|
"target_center": target_center,
|
|
"resize_strategy": plan.get("resize_strategy"),
|
|
},
|
|
"target_kind": "face",
|
|
"target_id": face_id,
|
|
"target_logical_id": logical_id,
|
|
"pick_position": None,
|
|
"show_same_domain_internal_edges": False,
|
|
"edit_result_deflection": 1.6,
|
|
}
|
|
if probe._context_is_face_parameter_edit(context):
|
|
raise SystemExit("slot axis move should not be treated as a generic Face parameter edit")
|
|
if not probe._context_should_preserve_face_logical_id(context):
|
|
raise SystemExit("slot axis move should still preserve the selected logical Face ID")
|
|
|
|
result = probe._run_isolated_edit_job(
|
|
context=context,
|
|
isolation={
|
|
"operation": "move_cylindrical_slot_axis",
|
|
"args": [face_id, target_center],
|
|
"timeout_seconds": 120.0,
|
|
},
|
|
snapshot=probe.model.snapshot(),
|
|
before_stats=probe.model.stats(),
|
|
before_part_stats=probe.model.part_topology_stats(int(plan.get("part_id") or 1)),
|
|
before_quality=probe._edit_quality_info_or_none(probe.model, context, int(plan.get("part_id") or 1)),
|
|
before_geometry={},
|
|
)
|
|
after_model = result.get("after_model")
|
|
if not isinstance(after_model, StepModel):
|
|
raise SystemExit(f"window slot-axis isolation should return after_model: {result}")
|
|
verified_face, center, error = _nearest_axis_center(after_model, target_center)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"window slot-axis isolation verification failed: face={verified_face}, "
|
|
f"center={center}, error={error:g}, result={result}"
|
|
)
|
|
if not _logical_region_has_slot_axis(after_model, logical_id, target_center):
|
|
raise SystemExit(
|
|
f"window slot-axis isolation did not preserve logical Face {logical_id} on the moved slot"
|
|
)
|
|
_assert_one_solid(after_model, "window slot-axis isolation")
|
|
print(f"window slot-axis isolation ok: logical={logical_id}, face={verified_face}, center={center}")
|
|
|
|
|
|
def _verify_blind_depth_window_isolation_keeps_logical_id(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "blind_depth_window.step"
|
|
_write_blind_hole_model(input_path)
|
|
|
|
probe = _window_probe_for(input_path)
|
|
face_id = _first_hole_face(probe.model, blind=True)
|
|
logical_id = probe.model.face_logical_id(face_id)
|
|
feature = probe.model.feature_info(face_id)
|
|
bottom_face_ids = tuple(feature.get("feature_bottom_face_ids", ()))
|
|
bottom_face_id = int(bottom_face_ids[0]) if bottom_face_ids else None
|
|
target_depth = 8.0
|
|
plan = probe.model.cylindrical_depth_plan(face_id, target_depth, bottom_face_id=bottom_face_id)
|
|
context = {
|
|
"operation_name": "resize blind hole depth",
|
|
"target": f"Face {face_id}",
|
|
"parameters": {
|
|
"part_id": plan.get("part_id"),
|
|
"solid_id": plan.get("solid_id"),
|
|
"face_id": face_id,
|
|
"target_depth": target_depth,
|
|
"current_depth": plan.get("current_depth"),
|
|
"resize_strategy": plan.get("resize_strategy"),
|
|
"manual_bottom_face_id": bottom_face_id,
|
|
},
|
|
"target_kind": "face",
|
|
"target_id": face_id,
|
|
"target_logical_id": logical_id,
|
|
"pick_position": None,
|
|
"show_same_domain_internal_edges": False,
|
|
"edit_result_deflection": 1.6,
|
|
}
|
|
if probe._context_is_face_parameter_edit(context):
|
|
raise SystemExit("blind depth should not be treated as a generic Face parameter edit")
|
|
if not probe._context_should_preserve_face_logical_id(context):
|
|
raise SystemExit("blind depth should still preserve the selected logical Face ID")
|
|
|
|
result = probe._run_isolated_edit_job(
|
|
context=context,
|
|
isolation={
|
|
"operation": "resize_cylindrical_depth",
|
|
"args": [face_id, target_depth, bottom_face_id],
|
|
"timeout_seconds": 120.0,
|
|
},
|
|
snapshot=probe.model.snapshot(),
|
|
before_stats=probe.model.stats(),
|
|
before_part_stats=probe.model.part_topology_stats(int(plan.get("part_id") or 1)),
|
|
before_quality=probe._edit_quality_info_or_none(probe.model, context, int(plan.get("part_id") or 1)),
|
|
before_geometry={},
|
|
)
|
|
after_model = result.get("after_model")
|
|
if not isinstance(after_model, StepModel):
|
|
raise SystemExit(f"window blind-depth isolation should return after_model: {result}")
|
|
verified_face, depth, error = _nearest_blind_depth(after_model, target_depth)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"window blind-depth isolation verification failed: face={verified_face}, "
|
|
f"depth={depth:g}, error={error:g}, result={result}"
|
|
)
|
|
if not _logical_region_has_blind_depth(after_model, logical_id, target_depth):
|
|
raise SystemExit(
|
|
f"window blind-depth isolation did not preserve logical Face {logical_id} on the resized feature"
|
|
)
|
|
_assert_one_solid(after_model, "window blind-depth isolation")
|
|
print(f"window blind-depth isolation ok: logical={logical_id}, face={verified_face}, depth={depth:g}")
|
|
|
|
|
|
def _verify_obround_total_length_window_isolation_keeps_logical_id(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "obround_total_length_window.step"
|
|
_write_obround_slot_model(input_path)
|
|
|
|
probe = _window_probe_for(input_path)
|
|
face_id, pair_face_id, _current_total_length, _current_center_distance, _ = _nearest_obround_total_length(
|
|
probe.model,
|
|
16.0,
|
|
)
|
|
logical_id = probe.model.face_logical_id(face_id)
|
|
target_total_length = 20.0
|
|
plan = probe.model.cylindrical_slot_total_length_plan(face_id, target_total_length, pair_face_id=pair_face_id)
|
|
context = {
|
|
"operation_name": "resize obround slot total length",
|
|
"target": f"Face {face_id}",
|
|
"parameters": {
|
|
"part_id": plan.get("part_id"),
|
|
"solid_id": plan.get("solid_id"),
|
|
"face_id": face_id,
|
|
"target_total_length": target_total_length,
|
|
"slot_pair_face_id": pair_face_id,
|
|
"resize_strategy": plan.get("resize_strategy"),
|
|
},
|
|
"target_kind": "face",
|
|
"target_id": face_id,
|
|
"target_logical_id": logical_id,
|
|
"pick_position": None,
|
|
"show_same_domain_internal_edges": False,
|
|
"edit_result_deflection": 1.6,
|
|
}
|
|
if probe._context_is_face_parameter_edit(context):
|
|
raise SystemExit("obround total length should not be treated as a generic Face parameter edit")
|
|
if not probe._context_should_preserve_face_logical_id(context):
|
|
raise SystemExit("obround total length should still preserve the selected logical Face ID")
|
|
|
|
result = probe._run_isolated_edit_job(
|
|
context=context,
|
|
isolation={
|
|
"operation": "resize_cylindrical_slot_total_length",
|
|
"args": [face_id, target_total_length, pair_face_id],
|
|
"timeout_seconds": 120.0,
|
|
},
|
|
snapshot=probe.model.snapshot(),
|
|
before_stats=probe.model.stats(),
|
|
before_part_stats=probe.model.part_topology_stats(int(plan.get("part_id") or 1)),
|
|
before_quality=probe._edit_quality_info_or_none(probe.model, context, int(plan.get("part_id") or 1)),
|
|
before_geometry={},
|
|
)
|
|
after_model = result.get("after_model")
|
|
if not isinstance(after_model, StepModel):
|
|
raise SystemExit(f"window obround-total-length isolation should return after_model: {result}")
|
|
verified_face, verified_pair, total_length, center_distance, error = _nearest_obround_total_length(
|
|
after_model,
|
|
target_total_length,
|
|
)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"window obround-total-length isolation verification failed: "
|
|
f"faces=({verified_face}, {verified_pair}), total_length={total_length:g}, "
|
|
f"center_distance={center_distance:g}, error={error:g}, result={result}"
|
|
)
|
|
if not _logical_region_contains_any(after_model, logical_id, (verified_face, verified_pair)):
|
|
raise SystemExit(
|
|
f"window obround-total-length isolation did not preserve logical Face {logical_id} "
|
|
"on the resized slot end"
|
|
)
|
|
_assert_one_solid(after_model, "window obround-total-length isolation")
|
|
print(
|
|
"window obround-total-length isolation ok: "
|
|
f"logical={logical_id}, faces=({verified_face}, {verified_pair}), total_length={total_length:g}"
|
|
)
|
|
|
|
|
|
def _verify_obround_axis_window_isolation_keeps_logical_id(temp_dir: Path) -> None:
|
|
input_path = temp_dir / "obround_axis_window.step"
|
|
_write_obround_slot_model(input_path)
|
|
|
|
probe = _window_probe_for(input_path)
|
|
face_id, pair_face_id, _current_total_length, _current_center_distance, _ = _nearest_obround_total_length(
|
|
probe.model,
|
|
16.0,
|
|
)
|
|
logical_id = probe.model.face_logical_id(face_id)
|
|
current_center = _slot_axis_center(probe.model, face_id)
|
|
pair_center = _slot_axis_center(probe.model, pair_face_id)
|
|
target_center = (current_center[0], current_center[1] + 1.0, current_center[2])
|
|
target_pair_center = (pair_center[0], pair_center[1] + 1.0, pair_center[2])
|
|
plan = probe.model.cylindrical_slot_axis_move_plan(face_id, target_center)
|
|
context = {
|
|
"operation_name": "move obround slot axis",
|
|
"target": f"Face {face_id}",
|
|
"parameters": {
|
|
"part_id": plan.get("part_id"),
|
|
"solid_id": plan.get("solid_id"),
|
|
"face_id": face_id,
|
|
"slot_pair_face_id": pair_face_id,
|
|
"target_center": target_center,
|
|
"target_pair_center": target_pair_center,
|
|
"resize_strategy": plan.get("resize_strategy"),
|
|
},
|
|
"target_kind": "face",
|
|
"target_id": face_id,
|
|
"target_logical_id": logical_id,
|
|
"pick_position": None,
|
|
"show_same_domain_internal_edges": False,
|
|
"edit_result_deflection": 1.6,
|
|
}
|
|
if probe._context_is_face_parameter_edit(context):
|
|
raise SystemExit("obround axis move should not be treated as a generic Face parameter edit")
|
|
if not probe._context_should_preserve_face_logical_id(context):
|
|
raise SystemExit("obround axis move should still preserve the selected logical Face ID")
|
|
|
|
result = probe._run_isolated_edit_job(
|
|
context=context,
|
|
isolation={
|
|
"operation": "move_cylindrical_slot_axis",
|
|
"args": [face_id, target_center],
|
|
"timeout_seconds": 120.0,
|
|
},
|
|
snapshot=probe.model.snapshot(),
|
|
before_stats=probe.model.stats(),
|
|
before_part_stats=probe.model.part_topology_stats(int(plan.get("part_id") or 1)),
|
|
before_quality=probe._edit_quality_info_or_none(probe.model, context, int(plan.get("part_id") or 1)),
|
|
before_geometry={},
|
|
)
|
|
after_model = result.get("after_model")
|
|
if not isinstance(after_model, StepModel):
|
|
raise SystemExit(f"window obround-axis isolation should return after_model: {result}")
|
|
verified_face, verified_pair, center_1, center_2, error = _nearest_obround_axis_pair(
|
|
after_model,
|
|
target_center,
|
|
target_pair_center,
|
|
)
|
|
if error > 1e-4:
|
|
raise SystemExit(
|
|
f"window obround-axis isolation verification failed: faces=({verified_face}, {verified_pair}), "
|
|
f"centers=({center_1}, {center_2}), error={error:g}, result={result}"
|
|
)
|
|
if not _logical_region_contains_any(after_model, logical_id, (verified_face, verified_pair)):
|
|
raise SystemExit(
|
|
f"window obround-axis isolation did not preserve logical Face {logical_id} on the moved slot end"
|
|
)
|
|
_assert_one_solid(after_model, "window obround-axis isolation")
|
|
print(f"window obround-axis isolation ok: logical={logical_id}, faces=({verified_face}, {verified_pair})")
|
|
|
|
|
|
def main() -> int:
|
|
with tempfile.TemporaryDirectory(prefix="geom_param_hole_slot_isolated_") as temp_name:
|
|
temp_dir = Path(temp_name)
|
|
_verify_hole_diameter_isolated(temp_dir)
|
|
_verify_hole_owning_scale_isolated(temp_dir)
|
|
_verify_hole_axis_isolated(temp_dir)
|
|
_verify_hole_suppress_isolated(temp_dir)
|
|
_verify_blind_depth_isolated(temp_dir)
|
|
_verify_blind_depth_owning_scale_isolated(temp_dir)
|
|
_verify_slot_width_isolated(temp_dir)
|
|
_verify_slot_owning_scale_isolated(temp_dir)
|
|
_verify_slot_metric_isolated(
|
|
temp_dir,
|
|
"slot_depth",
|
|
"resize_cylindrical_slot_depth",
|
|
4.0,
|
|
"slot_sagitta_depth_estimate",
|
|
)
|
|
_verify_slot_metric_isolated(
|
|
temp_dir,
|
|
"slot_arc_length",
|
|
"resize_cylindrical_slot_arc_length",
|
|
12.0,
|
|
"slot_arc_length_estimate",
|
|
)
|
|
_verify_slot_metric_isolated(
|
|
temp_dir,
|
|
"slot_angular_span",
|
|
"resize_cylindrical_slot_angular_span",
|
|
2.2,
|
|
"slot_angular_span",
|
|
)
|
|
_verify_slot_axis_isolated(temp_dir)
|
|
_verify_obround_slot_total_length_isolated(temp_dir)
|
|
_verify_obround_slot_center_distance_isolated(temp_dir)
|
|
_verify_obround_slot_axis_isolated(temp_dir)
|
|
_verify_hole_window_isolation_keeps_logical_id(temp_dir)
|
|
_verify_hole_axis_window_isolation_keeps_logical_id(temp_dir)
|
|
_verify_slot_window_isolation_keeps_logical_id(temp_dir)
|
|
_verify_slot_axis_window_isolation_keeps_logical_id(temp_dir)
|
|
_verify_blind_depth_window_isolation_keeps_logical_id(temp_dir)
|
|
_verify_obround_total_length_window_isolation_keeps_logical_id(temp_dir)
|
|
_verify_obround_axis_window_isolation_keeps_logical_id(temp_dir)
|
|
print("hole/slot isolated edit worker ok")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|