from __future__ import annotations import argparse import json import traceback from pathlib import Path from .model import StepModel def _optional_int(value: object) -> int | None: if value is None or value == "": return None return int(value) def _point3(value: object, operation: str) -> tuple[float, float, float]: point = list(value) if isinstance(value, (list, tuple)) else [] if len(point) != 3: raise ValueError(f"{operation} requires a 3D target center.") return (float(point[0]), float(point[1]), float(point[2])) def _execute(model: StepModel, operation: str, args: list[object]) -> str: if operation == "push_pull_face": return model.push_pull_face(int(args[0]), float(args[1])) if operation == "push_pull_face_keep_relations": return model.push_pull_face_keep_relations(int(args[0]), float(args[1])) if operation == "move_face_plane_offset_local": return model.move_face_plane_offset_local(int(args[0]), float(args[1])) if operation == "translate_face_plane_offset_owning": return model.translate_face_plane_offset_owning(int(args[0]), float(args[1])) if operation == "resize_face_area_local": return model.resize_face_area_local(int(args[0]), float(args[1])) if operation == "resize_face_area": return model.resize_face_area(int(args[0]), float(args[1])) if operation == "resize_face_size_local": return model.resize_face_size_local(int(args[0]), float(args[1]), str(args[2])) if operation == "resize_face_size_local_keep_relations": return model.resize_face_size_local_keep_relations(int(args[0]), float(args[1]), str(args[2])) if operation == "resize_face_size_owning_scale": return model.resize_face_size_owning_scale(int(args[0]), float(args[1]), str(args[2])) if operation == "move_face_center_local": center = list(args[1]) if len(center) != 3: raise ValueError("move_face_center_local requires a 3D target center.") return model.move_face_center_local( int(args[0]), (float(center[0]), float(center[1]), float(center[2])), ) if operation == "move_face_center_local_keep_relations": center = list(args[1]) if len(center) != 3: raise ValueError("move_face_center_local_keep_relations requires a 3D target center.") return model.move_face_center_local_keep_relations( int(args[0]), (float(center[0]), float(center[1]), float(center[2])), ) if operation == "resize_shell_thickness": return model.resize_shell_thickness(int(args[0]), float(args[1])) if operation == "resize_shell_thickness_owning_scale": return model.resize_shell_thickness_owning_scale(int(args[0]), float(args[1])) if operation == "resize_cylindrical_height": return model.resize_cylindrical_height(int(args[0]), float(args[1])) if operation == "resize_cylindrical_boss_height": return model.resize_cylindrical_boss_height(int(args[0]), float(args[1])) if operation == "resize_cylindrical_boss": return model.resize_cylindrical_boss(int(args[0]), float(args[1])) if operation == "resize_cylindrical_height_owning_scale": return model.resize_cylindrical_height_owning_scale(int(args[0]), float(args[1])) if operation == "resize_cone_reference_radius": return model.resize_conical_reference_radius(int(args[0]), float(args[1])) if operation == "resize_cone_semi_angle": return model.resize_conical_semi_angle(int(args[0]), float(args[1])) if operation == "resize_sphere_radius": return model.resize_spherical_radius(int(args[0]), float(args[1])) if operation == "resize_torus_radius": return model.resize_toroidal_radius(int(args[0]), float(args[1]), str(args[2])) if operation == "resize_cylindrical_hole": return model.resize_cylindrical_hole(int(args[0]), float(args[1])) if operation == "resize_cylindrical_owning_scale": return model.resize_cylindrical_owning_scale(int(args[0]), float(args[1])) if operation == "move_cylindrical_hole_axis": return model.move_cylindrical_hole_axis(int(args[0]), _point3(args[1], operation)) if operation == "move_cylindrical_boss_axis": return model.move_cylindrical_boss_axis(int(args[0]), _point3(args[1], operation)) if operation == "suppress_cylindrical_hole": return model.suppress_cylindrical_hole(int(args[0])) if operation == "resize_cylindrical_depth": bottom_face_id = _optional_int(args[2]) if len(args) > 2 else None return model.resize_cylindrical_depth(int(args[0]), float(args[1]), bottom_face_id=bottom_face_id) if operation == "resize_cylindrical_depth_owning_scale": bottom_face_id = _optional_int(args[2]) if len(args) > 2 else None return model.resize_cylindrical_depth_owning_scale(int(args[0]), float(args[1]), bottom_face_id=bottom_face_id) if operation == "move_cylindrical_slot_axis": return model.move_cylindrical_slot_axis(int(args[0]), _point3(args[1], operation)) if operation == "resize_cylindrical_slot_width": pair_face_id = _optional_int(args[2]) if len(args) > 2 else None return model.resize_cylindrical_slot_width(int(args[0]), float(args[1]), pair_face_id=pair_face_id) if operation == "resize_cylindrical_slot_depth": pair_face_id = _optional_int(args[2]) if len(args) > 2 else None return model.resize_cylindrical_slot_depth(int(args[0]), float(args[1]), pair_face_id=pair_face_id) if operation == "resize_cylindrical_slot_arc_length": pair_face_id = _optional_int(args[2]) if len(args) > 2 else None return model.resize_cylindrical_slot_arc_length(int(args[0]), float(args[1]), pair_face_id=pair_face_id) if operation == "resize_cylindrical_slot_angular_span": return model.resize_cylindrical_slot_angular_span(int(args[0]), float(args[1])) if operation == "resize_cylindrical_slot_total_length": pair_face_id = _optional_int(args[2]) if len(args) > 2 else None return model.resize_cylindrical_slot_total_length(int(args[0]), float(args[1]), pair_face_id=pair_face_id) if operation == "resize_cylindrical_slot_center_distance": pair_face_id = _optional_int(args[2]) if len(args) > 2 else None return model.resize_cylindrical_slot_center_distance( int(args[0]), float(args[1]), pair_face_id=pair_face_id, ) if operation == "resize_general_edge_length": anchor_mode = str(args[2]) if len(args) > 2 else "auto" strategy_mode = str(args[3]) if len(args) > 3 else "auto" return model.resize_general_edge_length( int(args[0]), float(args[1]), anchor_mode=anchor_mode, strategy_mode=strategy_mode, ) if operation == "move_edge_endpoint": return model.move_edge_endpoint(int(args[0]), str(args[1]), _point3(args[2], operation)) if operation == "move_edge_center": return model.move_edge_center(int(args[0]), _point3(args[1], operation)) if operation == "move_circular_edge_axis_center": return model.move_circular_edge_axis_center(int(args[0]), _point3(args[1], operation)) if operation == "resize_ellipse_edge_axis_radius": axis_kind = str(args[2]) if len(args) > 2 else "major" return model.resize_ellipse_edge_axis_radius(int(args[0]), float(args[1]), axis_kind=axis_kind) if operation == "resize_existing_fillet": return model.resize_existing_fillet(int(args[0]), float(args[1])) if operation == "resize_existing_chamfer": return model.resize_existing_chamfer(int(args[0]), float(args[1])) if operation == "fillet_edge": return model.fillet_edge(int(args[0]), float(args[1])) if operation == "chamfer_edge": return model.chamfer_edge(int(args[0]), float(args[1])) if operation == "chamfer_edge_asymmetric": reference_face_id = _optional_int(args[3]) if len(args) > 3 else None return model.chamfer_edge_asymmetric(int(args[0]), float(args[1]), float(args[2]), reference_face_id) if operation == "chamfer_edge_distance_angle": reference_face_id = _optional_int(args[3]) if len(args) > 3 else None return model.chamfer_edge_distance_angle(int(args[0]), float(args[1]), float(args[2]), reference_face_id) raise ValueError(f"Unsupported isolated edit operation: {operation}") def _load_request_model(path: Path, file_format: str) -> StepModel: if file_format == "brep": return StepModel.load_internal_brep(path) if file_format == "step": return StepModel.load(path) raise ValueError(f"Unsupported isolated edit input format: {file_format}") def _export_response_model(model: StepModel, path: Path, file_format: str) -> None: if file_format == "brep": model.export_internal_brep(path) return if file_format == "step": model.export_all(path) return raise ValueError(f"Unsupported isolated edit output format: {file_format}") def run_request(request: str | Path) -> int: request_path = Path(request) response_path = request_path.with_suffix(".response.json") try: request = json.loads(request_path.read_text(encoding="utf-8-sig")) input_path = Path(str(request["input_path"])) output_path = Path(str(request["output_path"])) operation = str(request["operation"]) args = list(request.get("args") or []) input_format = str(request.get("input_format") or "step").strip().lower() output_format = str(request.get("output_format") or input_format).strip().lower() model = _load_request_model(input_path, input_format) message = _execute(model, operation, args) _export_response_model(model, output_path, output_format) response_path.write_text( json.dumps( { "ok": True, "message": message, "stats": model.stats().__dict__, "output_path": str(output_path), "output_format": output_format, }, ensure_ascii=False, indent=2, ), encoding="utf-8", ) return 0 except Exception as exc: response_path.write_text( json.dumps( { "ok": False, "error": str(exc), "traceback": traceback.format_exc(), }, ensure_ascii=False, indent=2, ), encoding="utf-8", ) return 2 def main() -> int: parser = argparse.ArgumentParser(description="Run one high-risk geometry edit in an isolated process.") parser.add_argument("request", help="JSON request file.") parsed = parser.parse_args() return run_request(parsed.request) if __name__ == "__main__": raise SystemExit(main())