""" NXOpen script — import rib profile JSON and replace sandbox geometry. Input: rib_profile_.json (or rib_profile.json) Responsibilities: - Recreate closed polylines from profile coordinate arrays - Build sheet region for sandbox - Replace sandbox face geometry only - Sew/unite with neighboring reserved faces """ from __future__ import annotations import argparse import json from pathlib import Path from typing import Any, Dict, Iterable, List, Sequence, Tuple Point2D = Tuple[float, float] Point3D = Tuple[float, float, float] def _add(a: Sequence[float], b: Sequence[float]) -> Point3D: return (a[0] + b[0], a[1] + b[1], a[2] + b[2]) def _mul(v: Sequence[float], s: float) -> Point3D: return (v[0] * s, v[1] * s, v[2] * s) def load_json(path: Path) -> Dict[str, Any]: return json.loads(path.read_text()) def map_2d_to_3d(p: Point2D, transform: Dict[str, List[float]]) -> Point3D: origin = transform["origin"] x_axis = transform["x_axis"] y_axis = transform["y_axis"] return _add(_add(origin, _mul(x_axis, p[0])), _mul(y_axis, p[1])) def _ensure_closed(coords: List[Point2D]) -> List[Point2D]: if not coords: return coords if coords[0] != coords[-1]: coords.append(coords[0]) return coords def _create_polyline_curve(work_part: Any, pts3d: List[Point3D]) -> Any: """ Create a closed polyline curve in NX. API notes: this can be implemented with StudioSplineBuilderEx, PolygonBuilder, or line segments + composite curve depending on NX version/license. """ # Line-segment fallback (works in all NX versions) curves = [] for i in range(len(pts3d) - 1): p1 = work_part.Points.CreatePoint(pts3d[i]) p2 = work_part.Points.CreatePoint(pts3d[i + 1]) curves.append(work_part.Curves.CreateLine(p1, p2)) return curves def build_profile_curves(work_part: Any, profile: Dict[str, Any], transform: Dict[str, List[float]]) -> Dict[str, List[Any]]: created: Dict[str, List[Any]] = {"outer": [], "pockets": [], "holes": []} outer = _ensure_closed([(float(x), float(y)) for x, y in profile["outer_boundary"]]) outer_3d = [map_2d_to_3d(p, transform) for p in outer] created["outer"] = _create_polyline_curve(work_part, outer_3d) for pocket in profile.get("pockets", []): coords = _ensure_closed([(float(x), float(y)) for x, y in pocket]) pts3d = [map_2d_to_3d(p, transform) for p in coords] created["pockets"].extend(_create_polyline_curve(work_part, pts3d)) for hole in profile.get("hole_boundaries", []): coords = _ensure_closed([(float(x), float(y)) for x, y in hole]) pts3d = [map_2d_to_3d(p, transform) for p in coords] created["holes"].extend(_create_polyline_curve(work_part, pts3d)) return created def _find_sandbox_face(work_part: Any, sandbox_id: str) -> Any: for body in getattr(work_part.Bodies, "ToArray", lambda: work_part.Bodies)(): for face in body.GetFaces(): try: tag = face.GetStringUserAttribute("ISOGRID_SANDBOX", -1) except Exception: tag = None if tag == sandbox_id: return face raise RuntimeError(f"Sandbox face not found for id={sandbox_id}") def replace_sandbox_face_geometry(work_part: Any, sandbox_face: Any, created_curves: Dict[str, List[Any]]) -> None: """ Replace sandbox surface region from generated profile curves. This operation depends on the model topology and NX license package. Typical implementation: 1) Build bounded plane/sheet from outer and inner loops 2) Trim/split host face by new boundaries 3) Delete old sandbox patch 4) Sew new patch with reserved neighboring faces 5) Unite if multiple sheet bodies are produced """ # Recommended implementation hook points. # - Through Curve Mesh / Bounded Plane builders in NXOpen.Features # - SewBuilder in NXOpen.Features # - DeleteFace + ReplaceFace in synchronous modeling toolkit raise NotImplementedError( "Sandbox face replacement is model-specific. Implement with NXOpen feature builders " "(bounded sheet + replace face + sew/unite) in target NX environment." ) def run_in_nx( profile_path: Path, geometry_path: Path, sandbox_id: str, ) -> None: import NXOpen # type: ignore session = NXOpen.Session.GetSession() work_part = session.Parts.Work if work_part is None: raise RuntimeError("No active NX work part.") profile = load_json(profile_path) geometry = load_json(geometry_path) transform = geometry.get("transform") if not transform: raise ValueError(f"Missing transform in {geometry_path}") sandbox_face = _find_sandbox_face(work_part, sandbox_id) created_curves = build_profile_curves(work_part, profile, transform) replace_sandbox_face_geometry(work_part, sandbox_face, created_curves) print(f"[import_profile] Imported profile for {sandbox_id}: {profile_path}") def main(argv: Sequence[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Import rib profile JSON into NX sandbox face") parser.add_argument("--profile", required=True, help="Path to rib_profile json") parser.add_argument("--geometry", required=True, help="Path to geometry_sandbox json") parser.add_argument("--sandbox-id", required=True, help="Sandbox id (e.g. sandbox_1)") args = parser.parse_args(argv) run_in_nx( profile_path=Path(args.profile), geometry_path=Path(args.geometry), sandbox_id=args.sandbox_id, ) return 0 if __name__ == "__main__": raise SystemExit(main())