167 lines
5.3 KiB
Python
167 lines
5.3 KiB
Python
"""
|
|
NXOpen script — remesh full plate, solve, and export results.json.
|
|
|
|
Outputs:
|
|
{
|
|
"nodes_xy": [[x, y], ...],
|
|
"stress_values": [...],
|
|
"disp_values": [...],
|
|
"strain_values": [...],
|
|
"mass": 0.0
|
|
}
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import csv
|
|
import json
|
|
from pathlib import Path
|
|
from typing import Any, Dict, List, Sequence, Tuple
|
|
|
|
|
|
def remesh_full_plate(sim_part: Any) -> None:
|
|
"""
|
|
Trigger mesh regeneration for all FEM collectors associated with the plate.
|
|
"""
|
|
# This hook should iterate mesh managers/collectors in your SIM/FEM template.
|
|
# API often used: FEModel.UpdateFemodel(), mesh manager GenerateMesh/Update.
|
|
fe_model = sim_part.FindObject("FEModel")
|
|
fe_model.UpdateFemodel()
|
|
|
|
|
|
def solve_active_solution(sim_part: Any, solution_name: str | None = None) -> Any:
|
|
"""
|
|
Solve the requested solution (or first available).
|
|
"""
|
|
import NXOpen # type: ignore
|
|
import NXOpen.CAE # type: ignore
|
|
|
|
simulation = sim_part.FindObject("Simulation")
|
|
|
|
target_solution = None
|
|
if solution_name:
|
|
try:
|
|
target_solution = simulation.FindObject(f"Solution[{solution_name}]")
|
|
except Exception:
|
|
target_solution = None
|
|
|
|
if target_solution is None:
|
|
target_solution = simulation.FindObject("Solution[Solution 1]")
|
|
|
|
solve_mgr = NXOpen.CAE.SimSolveManager.GetSimSolveManager(NXOpen.Session.GetSession())
|
|
solve_mgr.SubmitSolves([target_solution])
|
|
return target_solution
|
|
|
|
|
|
def _parse_csv_results(path: Path) -> Tuple[List[List[float]], List[float]]:
|
|
"""
|
|
Parse generic CSV with columns: x,y,value (header-insensitive).
|
|
"""
|
|
coords: List[List[float]] = []
|
|
values: List[float] = []
|
|
with path.open(newline="") as f:
|
|
reader = csv.DictReader(f)
|
|
for row in reader:
|
|
keys = {k.lower(): k for k in row.keys()}
|
|
x = float(row[keys.get("x", keys.get("x_mm", "x"))])
|
|
y = float(row[keys.get("y", keys.get("y_mm", "y"))])
|
|
v = float(row[keys.get("value", keys.get("von_mises", "value"))])
|
|
coords.append([x, y])
|
|
values.append(v)
|
|
return coords, values
|
|
|
|
|
|
def extract_results_nxopen(sim_part: Any, sandbox_ids: List[str]) -> Dict[str, Any]:
|
|
"""
|
|
Preferred extractor: NXOpen post-processing API.
|
|
|
|
NOTE: Result object names/components vary by template. Keep this function as the
|
|
primary integration point for project-specific API wiring.
|
|
"""
|
|
raise NotImplementedError(
|
|
"Wire NXOpen post-processing calls here (nodal stress/displacement, elemental strain, mass)."
|
|
)
|
|
|
|
|
|
def extract_results_csv_fallback(work_dir: Path) -> Dict[str, Any]:
|
|
"""
|
|
Fallback extractor: parse Simcenter-exported CSV files in work_dir.
|
|
|
|
Expected files:
|
|
- nodal_stress.csv
|
|
- nodal_disp.csv
|
|
- elemental_strain.csv
|
|
- mass.json (optional: {"mass": ...})
|
|
"""
|
|
stress_coords, stress_vals = _parse_csv_results(work_dir / "nodal_stress.csv")
|
|
disp_coords, disp_vals = _parse_csv_results(work_dir / "nodal_disp.csv")
|
|
_, strain_vals = _parse_csv_results(work_dir / "elemental_strain.csv")
|
|
|
|
# Use stress nodal coordinates as canonical nodes_xy
|
|
nodes_xy = stress_coords if stress_coords else disp_coords
|
|
|
|
mass = 0.0
|
|
mass_file = work_dir / "mass.json"
|
|
if mass_file.exists():
|
|
mass = float(json.loads(mass_file.read_text()).get("mass", 0.0))
|
|
|
|
return {
|
|
"nodes_xy": nodes_xy,
|
|
"stress_values": stress_vals,
|
|
"disp_values": disp_vals,
|
|
"strain_values": strain_vals,
|
|
"mass": mass,
|
|
}
|
|
|
|
|
|
def run_in_nx(
|
|
work_dir: Path,
|
|
result_path: Path,
|
|
sandbox_ids: List[str],
|
|
solution_name: str | None = None,
|
|
use_csv_fallback: bool = False,
|
|
) -> Dict[str, Any]:
|
|
import NXOpen # type: ignore
|
|
|
|
session = NXOpen.Session.GetSession()
|
|
sim_part = session.Parts.BaseWork
|
|
if sim_part is None:
|
|
raise RuntimeError("No active NX SIM/FEM work part.")
|
|
|
|
remesh_full_plate(sim_part)
|
|
solve_active_solution(sim_part, solution_name=solution_name)
|
|
|
|
if use_csv_fallback:
|
|
results = extract_results_csv_fallback(work_dir)
|
|
else:
|
|
results = extract_results_nxopen(sim_part, sandbox_ids)
|
|
|
|
result_path.parent.mkdir(parents=True, exist_ok=True)
|
|
result_path.write_text(json.dumps(results, indent=2))
|
|
print(f"[solve_and_extract] wrote {result_path}")
|
|
return results
|
|
|
|
|
|
def main(argv: Sequence[str] | None = None) -> int:
|
|
parser = argparse.ArgumentParser(description="Remesh, solve, and export results.json")
|
|
parser.add_argument("--work-dir", default=".", help="Working directory for CSV fallback artifacts")
|
|
parser.add_argument("--results", default="results.json", help="Output JSON path")
|
|
parser.add_argument("--sandbox-id", action="append", default=[], help="Sandbox id filter (repeatable)")
|
|
parser.add_argument("--solution", default=None, help="NX solution name (defaults to Solution 1)")
|
|
parser.add_argument("--csv-fallback", action="store_true", help="Parse CSV files instead of NXOpen post API")
|
|
args = parser.parse_args(argv)
|
|
|
|
run_in_nx(
|
|
work_dir=Path(args.work_dir),
|
|
result_path=Path(args.results),
|
|
sandbox_ids=args.sandbox_id,
|
|
solution_name=args.solution,
|
|
use_csv_fallback=args.csv_fallback,
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|