Complete the NXOpenSolver class in nx_interface.py with production-ready
evaluate() and close() methods, following proven patterns from
M1_Mirror/SAT3_Trajectory_V7.
Pipeline per trial:
1. NXSolver.create_iteration_folder() — HEEDS-style isolation with fresh
model copies + params.exp generation
2. NXSolver.run_simulation() — journal-based solve via run_journal.exe
(handles expression import, geometry rebuild, FEM update, SOL 101)
3. extract_displacement() — max displacement from OP2
4. extract_solid_stress() — max von Mises with auto-detect element type
(tries all solid types first, falls back to CQUAD4 shell)
5. extract_mass_from_expression() — reads _temp_mass.txt from journal,
with _temp_part_properties.json fallback
Key decisions:
- Auto-detect element type for stress (element_type=None) instead of
hardcoding CQUAD4 — the beam model may use solid or shell elements
- Lazy solver init on first evaluate() call for clean error handling
- OP2 fallback path: tries solver result first, then expected naming
convention (beam_sim1-solution_1.op2)
- Mass fallback: _temp_mass.txt -> _temp_part_properties.json
- LAC-compliant close(): only uses session_manager.cleanup_stale_locks(),
never kills NX processes directly
Expression mapping (confirmed from binary introspection):
- beam_half_core_thickness, beam_face_thickness, holes_diameter, hole_count
- Mass output: p173 (body_property147.mass, kg)
Refs: OP_09, OPTIMIZATION_STRATEGY.md §8.2