164 lines
5.6 KiB
Python
164 lines
5.6 KiB
Python
|
|
"""
|
||
|
|
NXOpen script — import rib profile JSON and replace sandbox geometry.
|
||
|
|
|
||
|
|
Input:
|
||
|
|
rib_profile_<sandbox_id>.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())
|