Revise spec to reserved-region FEM and add Phase 2 NX sandbox scripts
This commit is contained in:
166
tools/adaptive-isogrid/src/nx/solve_and_extract.py
Normal file
166
tools/adaptive-isogrid/src/nx/solve_and_extract.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
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())
|
||||
Reference in New Issue
Block a user