From 2729bd327897c0141a72121800a8343a157b39bd Mon Sep 17 00:00:00 2001 From: Anto01 Date: Sat, 15 Nov 2025 12:23:57 -0500 Subject: [PATCH] feat: Add journal-based NX solver integration for optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements NX solver integration that connects to running Simcenter3D GUI to solve simulations using the journal API. This approach handles licensing properly and ensures fresh output files are generated for each iteration. **New Components:** - optimization_engine/nx_solver.py: Main solver wrapper with auto-detection - optimization_engine/solve_simulation.py: NX journal script for batch solving - examples/test_journal_optimization.py: Complete optimization workflow test - examples/test_nx_solver.py: Solver integration tests - tests/journal_*.py: Reference journal files for NX automation **Key Features:** - Auto-detects NX installation and version - Connects to running NX GUI session (uses existing license) - Closes/reopens .sim files to force reload of updated .prt files - Deletes old output files to force fresh solves - Waits for background solve completion - Saves simulation to ensure all outputs are written - ~4 second solve time per iteration **Workflow:** 1. Update parameters in .prt file (nx_updater.py) 2. Close any open parts in NX session 3. Open .sim file fresh from disk (loads updated .prt) 4. Reload components and switch to FEM component 5. Solve in background mode 6. Save .sim file 7. Wait for .op2/.f06 to appear 8. Extract results from fresh .op2 **Tested:** - Multiple iteration loop (3+ iterations) - Files regenerated fresh each time (verified by timestamps) - Complete parameter update -> solve -> extract workflow šŸ¤– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- NX_SOLVER_INTEGRATION.md | 294 ++++++++++++++ examples/check_nx_installation.py | 48 +++ examples/check_nx_license.py | 89 +++++ examples/test_journal_optimization.py | 133 ++++++ examples/test_nx_solver.py | 130 ++++++ examples/test_optimization_with_solver.py | 130 ++++++ optimization_engine/nx_solver.py | 466 ++++++++++++++++++++++ optimization_engine/solve_simulation.py | 182 +++++++++ tests/journal_open_run_sim.py | 75 ++++ tests/journal_open_run_sim_and_save.py | 82 ++++ tests/journal_reload_master.py | 23 ++ 11 files changed, 1652 insertions(+) create mode 100644 NX_SOLVER_INTEGRATION.md create mode 100644 examples/check_nx_installation.py create mode 100644 examples/check_nx_license.py create mode 100644 examples/test_journal_optimization.py create mode 100644 examples/test_nx_solver.py create mode 100644 examples/test_optimization_with_solver.py create mode 100644 optimization_engine/nx_solver.py create mode 100644 optimization_engine/solve_simulation.py create mode 100644 tests/journal_open_run_sim.py create mode 100644 tests/journal_open_run_sim_and_save.py create mode 100644 tests/journal_reload_master.py diff --git a/NX_SOLVER_INTEGRATION.md b/NX_SOLVER_INTEGRATION.md new file mode 100644 index 00000000..5710bf17 --- /dev/null +++ b/NX_SOLVER_INTEGRATION.md @@ -0,0 +1,294 @@ +# NX Solver Integration Guide + +## Overview + +The NX solver integration allows Atomizer to automatically run Siemens NX Nastran simulations in batch mode during optimization loops. + +## Architecture + +``` +Optimization Loop: +1. Update parameters in .prt file → nx_updater.py +2. Run NX solver in batch mode → nx_solver.py ← NEW! +3. Extract results from OP2 → op2_extractor_example.py +4. Evaluate objectives/constraints → runner.py +5. Optuna suggests next parameters → repeat +``` + +## Quick Start + +### Test 1: Verify Solver Integration + +```bash +conda activate test_env +python examples/test_nx_solver.py +``` + +This tests: +- NX installation detection +- Batch solver execution +- OP2 file generation +- Error handling + +**Expected**: Solver runs and produces .op2 file in ~1-2 minutes + +### Test 2: Run Optimization with Real Solver + +```bash +conda activate test_env +python examples/test_optimization_with_solver.py +``` + +This runs 3 optimization trials with REAL simulations! + +**Expected time**: ~5-10 minutes (depends on model complexity) + +## Usage in Your Code + +### Simple Usage (Convenience Function) + +```python +from optimization_engine.nx_solver import run_nx_simulation +from pathlib import Path + +sim_file = Path("path/to/model.sim") +op2_file = run_nx_simulation( + sim_file=sim_file, + nastran_version="2412", + timeout=600, # 10 minutes + cleanup=True # Remove temp files +) + +# op2_file now contains path to results +``` + +### Advanced Usage (Full Control) + +```python +from optimization_engine.nx_solver import NXSolver + +solver = NXSolver( + nastran_version="2412", + timeout=600 +) + +result = solver.run_simulation( + sim_file=sim_file, + working_dir=None, # Defaults to sim file directory + cleanup=True +) + +if result['success']: + print(f"OP2: {result['op2_file']}") + print(f"Time: {result['elapsed_time']:.1f}s") +else: + print(f"Errors: {result['errors']}") +``` + +### Integration with Optimization Runner + +```python +from optimization_engine.nx_solver import run_nx_simulation + +def my_simulation_runner() -> Path: + """Simulation runner for optimization.""" + sim_file = Path("my_model.sim") + + # Run solver + op2_file = run_nx_simulation( + sim_file=sim_file, + nastran_version="2412", + timeout=600, + cleanup=True + ) + + return op2_file + +# Use in OptimizationRunner +runner = OptimizationRunner( + config_path=config_path, + model_updater=my_model_updater, + simulation_runner=my_simulation_runner, # Uses real solver! + result_extractors=extractors +) +``` + +## Configuration + +### Auto-Detection + +By default, NXSolver auto-detects NX installation: + +```python +solver = NXSolver(nastran_version="2412") +# Searches: +# - C:/Program Files/Siemens/NX2412 +# - C:/Program Files/Siemens/Simcenter3D_2412 +# - C:/Program Files (x86)/Siemens/NX2412 +``` + +### Manual Configuration + +```python +from pathlib import Path + +solver = NXSolver( + nx_install_dir=Path("C:/Program Files/Siemens/NX2412"), + nastran_version="2412", + timeout=1200 # 20 minutes +) +``` + +## Solver Output Files + +### Files Created +- `model.op2` - Binary results (kept) +- `model.f06` - Text output (kept) +- `model.log` - Solver log (kept) +- `model.f04` - Intermediate (cleaned up) +- `model.dat` - Intermediate (cleaned up) +- `model.diag` - Diagnostic (cleaned up) + +### Cleanup Behavior + +With `cleanup=True` (recommended): +- Keeps: .op2, .f06, .log +- Removes: .f04, .dat, .diag, .master, .dball, plots + +With `cleanup=False`: +- Keeps all files for debugging + +## Error Handling + +### Common Issues + +**Issue**: `FileNotFoundError: NX Nastran solver not found` + +**Solution**: +- Check NX is installed at standard location +- Specify `nx_install_dir` manually +- Verify nastran.exe exists in NXNASTRAN/bin/ + +**Issue**: `RuntimeError: NX simulation failed` + +**Solution**: +- Check .f06 file for error messages +- Verify .sim file is valid +- Check NX license is available +- Ensure model can solve in NX GUI first + +**Issue**: `TimeoutExpired` + +**Solution**: +- Increase `timeout` parameter +- Simplify model (fewer elements, linear analysis) +- Check solver isn't stuck (memory issues) + +### Checking Solver Success + +The solver checks for completion by: +1. Looking for "NORMAL TERMINATION" in .f06 +2. Checking for "FATAL MESSAGE" errors +3. Verifying .op2 file was created recently + +## Performance Tips + +### Speed Up Optimization + +1. **Reduce Model Complexity** + - Use coarser mesh for initial exploration + - Simplify geometry in non-critical areas + - Use linear analysis if possible + +2. **Parallel Trials (Future)** + - Run multiple trials simultaneously + - Requires separate working directories + - Use Optuna's parallelization features + +3. **Smart Sampling** + - Use TPE sampler (default) for efficiency + - Increase `n_startup_trials` for better initial sampling + - Use constraints to avoid infeasible regions + +4. **Cleanup Strategy** + - Use `cleanup=True` to save disk space + - Only keep .op2 and .log files + - Archive results after optimization + +### Typical Solve Times + +| Model Size | Analysis Type | Time per Trial | +|------------|---------------|----------------| +| Small (<10k nodes) | Linear Static | 30-60s | +| Medium (10-50k) | Linear Static | 1-3 min | +| Large (>50k) | Linear Static | 3-10 min | +| Any | Nonlinear | 5-30 min | + +## Batch Processing + +For running many optimizations: + +```python +# Save solver instance to reuse +solver = NXSolver(nastran_version="2412", timeout=600) + +for trial_params in parameter_sets: + # Update model + update_nx_model(prt_file, trial_params) + + # Solve + result = solver.run_simulation(sim_file, cleanup=True) + + if result['success']: + # Extract and analyze + results = extract_all_results(result['op2_file']) +``` + +## Troubleshooting + +### Enable Debug Output + +```python +solver = NXSolver(nastran_version="2412") + +result = solver.run_simulation( + sim_file=sim_file, + cleanup=False # Keep all files +) + +# Check detailed output +print(result['errors']) + +# Manually inspect files +# - Check .f06 for solver messages +# - Check .log for execution details +# - Check .f04 for input deck +``` + +### Verify NX Installation + +```python +from optimization_engine.nx_solver import NXSolver + +solver = NXSolver(nastran_version="2412") +print(f"NX Dir: {solver.nx_install_dir}") +print(f"Solver: {solver.solver_exe}") +print(f"Exists: {solver.solver_exe.exists()}") +``` + +## Next Steps + +1. **Test solver integration**: Run `test_nx_solver.py` +2. **Test optimization loop**: Run `test_optimization_with_solver.py` +3. **Customize for your model**: Modify simulation_runner function +4. **Run real optimization**: Increase n_trials to 50-150 +5. **Analyze results**: Use history.csv to understand parameter sensitivity + +## Support + +For issues: +1. Check this guide +2. Verify NX installation +3. Test .sim file in NX GUI first +4. Check solver logs (.f06, .log files) +5. Review error messages in result['errors'] diff --git a/examples/check_nx_installation.py b/examples/check_nx_installation.py new file mode 100644 index 00000000..509cb5e5 --- /dev/null +++ b/examples/check_nx_installation.py @@ -0,0 +1,48 @@ +""" +Quick check: Verify NX installation can be found +""" + +from pathlib import Path +import sys + +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from optimization_engine.nx_solver import NXSolver + +print("="*60) +print("NX INSTALLATION CHECK") +print("="*60) + +try: + solver = NXSolver(nastran_version="2412") + + print("\nāœ“ NX Solver found!") + print(f"\nInstallation:") + print(f" Directory: {solver.nx_install_dir}") + print(f" Solver: {solver.solver_exe}") + print(f"\nSolver executable exists: {solver.solver_exe.exists()}") + + if solver.solver_exe.exists(): + print(f"Solver size: {solver.solver_exe.stat().st_size / (1024*1024):.1f} MB") + + print("\n" + "="*60) + print("READY TO USE!") + print("="*60) + print("\nNext step: Run test_nx_solver.py to verify solver execution") + +except FileNotFoundError as e: + print(f"\nāœ— Error: {e}") + print("\nPlease check:") + print(" - NX 2412 is installed") + print(" - Installation is at standard location") + print("\nTry specifying path manually:") + print(" solver = NXSolver(") + print(" nx_install_dir=Path('C:/your/path/to/NX2412'),") + print(" nastran_version='2412'") + print(" )") + +except Exception as e: + print(f"\nāœ— Unexpected error: {e}") + import traceback + traceback.print_exc() diff --git a/examples/check_nx_license.py b/examples/check_nx_license.py new file mode 100644 index 00000000..c9a99b5d --- /dev/null +++ b/examples/check_nx_license.py @@ -0,0 +1,89 @@ +""" +Check NX License Configuration +""" + +import os +from pathlib import Path + +print("="*60) +print("NX LICENSE CONFIGURATION CHECK") +print("="*60) + +# Check environment variables +print("\n--- Environment Variables ---") + +license_vars = [ + 'SPLM_LICENSE_SERVER', + 'UGII_LICENSE_BUNDLE', + 'LM_LICENSE_FILE', + 'NX_LICENSE_FILE', +] + +for var in license_vars: + value = os.environ.get(var) + if value: + print(f" āœ“ {var} = {value}") + else: + print(f" āœ— {var} = (not set)") + +# Check license server files +print("\n--- License Server Files ---") + +possible_license_files = [ + Path("C:/Program Files/Siemens/License Server/ugslmd.opt"), + Path("C:/Program Files/Siemens/License Server/server.lic"), + Path("C:/Program Files (x86)/Siemens/License Server/ugslmd.opt"), +] + +for lic_file in possible_license_files: + if lic_file.exists(): + print(f" āœ“ Found: {lic_file}") + else: + print(f" āœ— Not found: {lic_file}") + +# Check NX installation licensing +print("\n--- NX Installation License Info ---") + +nx_dirs = [ + Path("C:/Program Files/Siemens/NX2412"), + Path("C:/Program Files/Siemens/Simcenter3D_2412"), +] + +for nx_dir in nx_dirs: + if nx_dir.exists(): + print(f"\n{nx_dir.name}:") + license_file = nx_dir / "ugslmd.lic" + if license_file.exists(): + print(f" āœ“ License file: {license_file}") + else: + print(f" āœ— No ugslmd.lic found") + +print("\n" + "="*60) +print("RECOMMENDATIONS:") +print("="*60) + +print(""" +1. If you see SPLM_LICENSE_SERVER: + - License server is configured āœ“ + +2. If no environment variables are set: + - You may need to set SPLM_LICENSE_SERVER + - Format: port@hostname (e.g., 28000@localhost) + - Or: path to license file + +3. Common fixes: + - Set environment variable in Windows: + setx SPLM_LICENSE_SERVER "28000@your-license-server" + + - Or use license file: + setx SPLM_LICENSE_FILE "C:\\path\\to\\license.dat" + +4. For local/node-locked license: + - Check License Server is running + - Services → Siemens License Server should be running + +5. For network license: + - Verify license server hostname/IP + - Check port (usually 28000) + - Verify firewall allows connection +""") diff --git a/examples/test_journal_optimization.py b/examples/test_journal_optimization.py new file mode 100644 index 00000000..9ce25471 --- /dev/null +++ b/examples/test_journal_optimization.py @@ -0,0 +1,133 @@ +""" +Test: Complete Optimization with Journal-Based NX Solver + +This tests the complete workflow: +1. Update model parameters in .prt +2. Solve via journal (using running NX GUI) +3. Extract results from OP2 +4. Run optimization loop + +REQUIREMENTS: +- Simcenter3D must be open (but no files need to be loaded) +- test_env conda environment activated +""" + +from pathlib import Path +import sys + +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from optimization_engine.runner import OptimizationRunner +from optimization_engine.nx_updater import update_nx_model +from optimization_engine.nx_solver import run_nx_simulation +from optimization_engine.result_extractors.extractors import ( + stress_extractor, + displacement_extractor +) + + +def bracket_model_updater(design_vars: dict): + """Update bracket model parameters.""" + prt_file = project_root / "examples/bracket/Bracket.prt" + print(f"\n[MODEL UPDATE] {prt_file.name}") + for name, value in design_vars.items(): + print(f" {name} = {value:.4f}") + update_nx_model(prt_file, design_vars, backup=False) + + +def bracket_simulation_runner() -> Path: + """ + Run NX solver via journal on running NX GUI session. + + This connects to the running Simcenter3D GUI and: + 1. Opens the .sim file + 2. Updates the FEM component + 3. Solves the simulation + 4. Returns path to .op2 file + """ + sim_file = project_root / "examples/bracket/Bracket_sim1.sim" + + print("\n[SIMULATION] Running via journal on NX GUI...") + print(f" SIM file: {sim_file.name}") + + try: + # Run solver via journal (connects to running NX GUI) + op2_file = run_nx_simulation( + sim_file=sim_file, + nastran_version="2412", + timeout=300, # 5 minute timeout + cleanup=True, # Clean up temp files + use_journal=True # Use journal mode (requires NX GUI open) + ) + + print(f"[SIMULATION] Complete! Results: {op2_file.name}") + return op2_file + + except Exception as e: + print(f"[SIMULATION] FAILED: {e}") + raise + + +if __name__ == "__main__": + print("="*60) + print("JOURNAL-BASED OPTIMIZATION TEST") + print("="*60) + print("\nāš ļø REQUIREMENTS āš ļø") + print("- Simcenter3D must be OPEN (no files need to be loaded)") + print("- Will run 3 optimization trials") + print("- Each trial: update params -> solve via journal -> extract results") + print("="*60) + + response = input("\nIs Simcenter3D open? (yes/no): ") + if response.lower() not in ['yes', 'y']: + print("Please open Simcenter3D and try again.") + sys.exit(0) + + config_path = project_root / "examples/bracket/optimization_config_stress_displacement.json" + + runner = OptimizationRunner( + config_path=config_path, + model_updater=bracket_model_updater, + simulation_runner=bracket_simulation_runner, # Journal-based solver! + result_extractors={ + 'stress_extractor': stress_extractor, + 'displacement_extractor': displacement_extractor + } + ) + + # Run just 3 trials for testing + runner.config['optimization_settings']['n_trials'] = 3 + + print("\n" + "="*60) + print("Starting optimization with 3 trials") + print("Objective: Minimize max von Mises stress") + print("Constraint: Max displacement <= 1.0 mm") + print("Solver: Journal-based (using running NX GUI)") + print("="*60) + + try: + study = runner.run(study_name="journal_solver_test") + + print("\n" + "="*60) + print("OPTIMIZATION COMPLETE!") + print("="*60) + print(f"\nBest stress: {study.best_value:.2f} MPa") + print(f"\nBest parameters:") + for param, value in study.best_params.items(): + print(f" {param}: {value:.4f}") + + print(f"\nResults saved to: {runner.output_dir}") + print("\nCheck history.csv to see optimization progress!") + + except Exception as e: + print(f"\n{'='*60}") + print("ERROR DURING OPTIMIZATION") + print("="*60) + print(f"{e}") + import traceback + traceback.print_exc() + print("\nMake sure:") + print(" - Simcenter3D is open and running") + print(" - .sim file is valid and solvable") + print(" - No other processes are locking the files") diff --git a/examples/test_nx_solver.py b/examples/test_nx_solver.py new file mode 100644 index 00000000..d0f44a12 --- /dev/null +++ b/examples/test_nx_solver.py @@ -0,0 +1,130 @@ +""" +Test NX Solver Integration + +Tests running NX Nastran in batch mode. +""" + +from pathlib import Path +import sys + +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from optimization_engine.nx_solver import NXSolver, run_nx_simulation + + +def test_solver_basic(): + """Test basic solver execution.""" + print("="*60) + print("TEST 1: Basic Solver Execution") + print("="*60) + + sim_file = project_root / "examples/bracket/Bracket_sim1.sim" + + if not sim_file.exists(): + print(f"ERROR: Simulation file not found: {sim_file}") + return False + + try: + # Initialize solver + solver = NXSolver(nastran_version="2412", timeout=300) + print(f"\nSolver initialized:") + print(f" NX Directory: {solver.nx_install_dir}") + print(f" Solver Exe: {solver.solver_exe}") + + # Run simulation + result = solver.run_simulation( + sim_file=sim_file, + cleanup=False # Keep all files for inspection + ) + + print(f"\n{'='*60}") + print("SOLVER RESULT:") + print(f"{'='*60}") + print(f" Success: {result['success']}") + print(f" Time: {result['elapsed_time']:.1f}s") + print(f" OP2 file: {result['op2_file']}") + print(f" Return code: {result['return_code']}") + + if result['errors']: + print(f"\n Errors:") + for error in result['errors']: + print(f" {error}") + + return result['success'] + + except Exception as e: + print(f"\nERROR: {e}") + import traceback + traceback.print_exc() + return False + + +def test_convenience_function(): + """Test convenience function.""" + print("\n" + "="*60) + print("TEST 2: Convenience Function") + print("="*60) + + sim_file = project_root / "examples/bracket/Bracket_sim1.sim" + + try: + op2_file = run_nx_simulation( + sim_file=sim_file, + nastran_version="2412", + timeout=300, + cleanup=True + ) + + print(f"\nSUCCESS!") + print(f" OP2 file: {op2_file}") + print(f" File exists: {op2_file.exists()}") + print(f" File size: {op2_file.stat().st_size / 1024:.1f} KB") + + return True + + except Exception as e: + print(f"\nFAILED: {e}") + import traceback + traceback.print_exc() + return False + + +if __name__ == "__main__": + print("="*60) + print("NX SOLVER INTEGRATION TEST") + print("="*60) + print("\nThis will run NX Nastran solver in batch mode.") + print("Make sure:") + print(" 1. NX 2412 is installed") + print(" 2. No NX GUI sessions are using the .sim file") + print(" 3. You have write permissions in the bracket folder") + print("\n" + "="*60) + + input("\nPress ENTER to continue or Ctrl+C to cancel...") + + # Test 1: Basic execution + test1_result = test_solver_basic() + + if test1_result: + # Test 2: Convenience function + test2_result = test_convenience_function() + + if test2_result: + print("\n" + "="*60) + print("ALL TESTS PASSED āœ“") + print("="*60) + print("\nNX solver integration is working!") + print("You can now use it in optimization loops.") + else: + print("\n" + "="*60) + print("TEST 2 FAILED") + print("="*60) + else: + print("\n" + "="*60) + print("TEST 1 FAILED - Skipping Test 2") + print("="*60) + print("\nCheck:") + print(" - NX installation path") + print(" - .sim file is valid") + print(" - NX license is available") diff --git a/examples/test_optimization_with_solver.py b/examples/test_optimization_with_solver.py new file mode 100644 index 00000000..84e1a605 --- /dev/null +++ b/examples/test_optimization_with_solver.py @@ -0,0 +1,130 @@ +""" +Test: Complete Optimization with Real NX Solver + +This runs the complete optimization loop: +1. Update model parameters +2. Run NX solver (REAL simulation) +3. Extract results from OP2 +4. Optimize with Optuna + +WARNING: This will run NX solver for each trial! +For 5 trials, expect ~5-10 minutes depending on solver speed. +""" + +from pathlib import Path +import sys + +project_root = Path(__file__).parent.parent +sys.path.insert(0, str(project_root)) + +from optimization_engine.runner import OptimizationRunner +from optimization_engine.nx_updater import update_nx_model +from optimization_engine.nx_solver import run_nx_simulation +from optimization_engine.result_extractors.extractors import ( + stress_extractor, + displacement_extractor +) + + +def bracket_model_updater(design_vars: dict): + """Update bracket model parameters.""" + prt_file = project_root / "examples/bracket/Bracket.prt" + print(f"\n[MODEL UPDATE] {prt_file.name}") + for name, value in design_vars.items(): + print(f" {name} = {value:.4f}") + update_nx_model(prt_file, design_vars, backup=False) + + +def bracket_simulation_runner() -> Path: + """ + Run NX Nastran solver and return path to OP2 file. + + This is the key difference from the test version - + it actually runs the solver for each trial! + """ + sim_file = project_root / "examples/bracket/Bracket_sim1.sim" + + print("\n[SIMULATION] Running NX Nastran solver...") + print(f" SIM file: {sim_file.name}") + + try: + # Run solver (this will take ~1-2 minutes per trial) + op2_file = run_nx_simulation( + sim_file=sim_file, + nastran_version="2412", + timeout=600, # 10 minute timeout + cleanup=True # Clean up temp files + ) + + print(f"[SIMULATION] Complete! Results: {op2_file.name}") + return op2_file + + except Exception as e: + print(f"[SIMULATION] FAILED: {e}") + raise + + +if __name__ == "__main__": + print("="*60) + print("REAL OPTIMIZATION WITH NX SOLVER") + print("="*60) + print("\nāš ļø WARNING āš ļø") + print("This will run NX Nastran solver for each trial!") + print("For 3 trials, expect ~5-10 minutes total.") + print("\nMake sure:") + print(" - NX 2412 is installed and licensed") + print(" - No NX GUI sessions are open") + print(" - Bracket.prt and Bracket_sim1.sim are accessible") + print("="*60) + + response = input("\nContinue? (yes/no): ") + if response.lower() not in ['yes', 'y']: + print("Cancelled.") + sys.exit(0) + + config_path = project_root / "examples/bracket/optimization_config_stress_displacement.json" + + runner = OptimizationRunner( + config_path=config_path, + model_updater=bracket_model_updater, + simulation_runner=bracket_simulation_runner, # REAL SOLVER! + result_extractors={ + 'stress_extractor': stress_extractor, + 'displacement_extractor': displacement_extractor + } + ) + + # Run just 3 trials for testing (change to 20-50 for real optimization) + runner.config['optimization_settings']['n_trials'] = 3 + + print("\n" + "="*60) + print("Starting optimization with 3 trials") + print("Objective: Minimize max von Mises stress") + print("Constraint: Max displacement <= 1.0 mm") + print("="*60) + + try: + study = runner.run(study_name="real_solver_test") + + print("\n" + "="*60) + print("OPTIMIZATION COMPLETE!") + print("="*60) + print(f"\nBest stress: {study.best_value:.2f} MPa") + print(f"\nBest parameters:") + for param, value in study.best_params.items(): + print(f" {param}: {value:.4f}") + + print(f"\nResults saved to: {runner.output_dir}") + print("\nCheck history.csv to see how stress changed with parameters!") + + except Exception as e: + print(f"\n{'='*60}") + print("ERROR DURING OPTIMIZATION") + print("="*60) + print(f"{e}") + import traceback + traceback.print_exc() + print("\nMake sure:") + print(" - NX Nastran is properly installed") + print(" - License is available") + print(" - .sim file is valid and solvable") diff --git a/optimization_engine/nx_solver.py b/optimization_engine/nx_solver.py new file mode 100644 index 00000000..71d24512 --- /dev/null +++ b/optimization_engine/nx_solver.py @@ -0,0 +1,466 @@ +""" +NX Nastran Solver Integration + +Executes NX Nastran solver in batch mode for optimization loops. +""" + +from pathlib import Path +from typing import Optional, Dict, Any +import subprocess +import time +import shutil +import os + + +class NXSolver: + """ + Wrapper for NX Nastran batch solver execution. + + Supports: + - Running .sim files through NX Nastran + - Monitoring solver progress + - Detecting completion and errors + - Cleaning up temporary files + """ + + def __init__( + self, + nx_install_dir: Optional[Path] = None, + nastran_version: str = "2412", + timeout: int = 600, + use_journal: bool = True + ): + """ + Initialize NX Solver. + + Args: + nx_install_dir: Path to NX installation (auto-detected if None) + nastran_version: NX version (e.g., "2412", "2506") + timeout: Maximum solver time in seconds (default: 10 minutes) + use_journal: Use NX journal for solving (recommended for licensing) + """ + self.nastran_version = nastran_version + self.timeout = timeout + self.use_journal = use_journal + + # Auto-detect NX installation + if nx_install_dir is None: + nx_install_dir = self._find_nx_installation() + + self.nx_install_dir = Path(nx_install_dir) + + # Set up solver executable + if use_journal: + self.solver_exe = self._find_journal_runner() + else: + self.solver_exe = self._find_solver_executable() + + if not self.solver_exe.exists(): + raise FileNotFoundError( + f"NX solver/runner not found at: {self.solver_exe}\n" + f"Please check NX installation at: {self.nx_install_dir}" + ) + + def _find_nx_installation(self) -> Path: + """Auto-detect NX installation directory.""" + # Common installation paths + possible_paths = [ + Path(f"C:/Program Files/Siemens/NX{self.nastran_version}"), + Path(f"C:/Program Files/Siemens/Simcenter3D_{self.nastran_version}"), + Path(f"C:/Program Files (x86)/Siemens/NX{self.nastran_version}"), + ] + + for path in possible_paths: + if path.exists(): + return path + + raise FileNotFoundError( + f"Could not auto-detect NX {self.nastran_version} installation.\n" + f"Checked: {[str(p) for p in possible_paths]}\n" + f"Please specify nx_install_dir manually." + ) + + def _find_journal_runner(self) -> Path: + """Find the NX journal runner executable.""" + # Simcenter3D has run_journal.exe for batch execution + possible_exes = [ + Path(f"C:/Program Files/Siemens/Simcenter3D_{self.nastran_version}/NXBIN/run_journal.exe"), + Path(f"C:/Program Files/Siemens/NX{self.nastran_version}/NXBIN/run_journal.exe"), + ] + + for exe in possible_exes: + if exe.exists(): + return exe + + # Return first guess (will error in __init__ if doesn't exist) + return possible_exes[0] + + def _find_solver_executable(self) -> Path: + """Find the Nastran solver executable.""" + # Use NX Nastran (not Simcenter) - has different licensing + # Priority: Use NX installation, not Simcenter + possible_exes = [ + self.nx_install_dir / "NXNASTRAN" / "bin" / "nastran.exe", + self.nx_install_dir / "NXNASTRAN" / "nastran.exe", + self.nx_install_dir / "bin" / "nastran.exe", + ] + + for exe in possible_exes: + if exe.exists(): + return exe + + # If not found in NX, try Simcenter as fallback + simcenter_paths = [ + Path(f"C:/Program Files/Siemens/Simcenter3D_{self.nastran_version}"), + ] + + for simcenter_dir in simcenter_paths: + if simcenter_dir.exists(): + solve_exe = simcenter_dir / "NXNASTRAN" / "bin" / "nastran.exe" + if solve_exe.exists(): + return solve_exe + + # Return first guess (will error in __init__ if doesn't exist) + return possible_exes[0] + + def run_simulation( + self, + sim_file: Path, + working_dir: Optional[Path] = None, + cleanup: bool = True + ) -> Dict[str, Any]: + """ + Run NX Nastran simulation. + + Args: + sim_file: Path to .sim file + working_dir: Working directory for solver (defaults to sim file dir) + cleanup: Remove intermediate files after solving + + Returns: + Dictionary with: + - success: bool + - op2_file: Path to output .op2 file + - log_file: Path to .log file + - elapsed_time: Solve time in seconds + - errors: List of error messages (if any) + """ + sim_file = Path(sim_file) + if not sim_file.exists(): + raise FileNotFoundError(f"Simulation file not found: {sim_file}") + + if working_dir is None: + working_dir = sim_file.parent + else: + working_dir = Path(working_dir) + + # Check if we need to find/use .dat file (only in direct mode, not journal mode) + # .sim files require NX GUI, but .dat files can be run directly with Nastran + dat_file = None + if not self.use_journal and sim_file.suffix == '.sim': + # Look for corresponding .dat file (created by NX when solving) + # Pattern: Bracket_sim1.sim -> bracket_sim1-solution_1.dat + base = sim_file.stem.lower() + possible_dats = list(working_dir.glob(f"{base}-solution_*.dat")) + if possible_dats: + # Use the most recent .dat file + dat_file = max(possible_dats, key=lambda p: p.stat().st_mtime) + print(f"\n[NX SOLVER] Found .dat file: {dat_file.name}") + print(f" Using .dat instead of .sim for better compatibility") + sim_file = dat_file + + # Prepare output file names + # When using journal mode with .sim files, output is named: -solution_1.op2 + # When using direct mode with .dat files, output is named: .op2 + base_name = sim_file.stem + + if self.use_journal and sim_file.suffix == '.sim': + # Journal mode: look for -solution_1 pattern + output_base = f"{base_name.lower()}-solution_1" + else: + # Direct mode or .dat file + output_base = base_name + + op2_file = working_dir / f"{output_base}.op2" + log_file = working_dir / f"{output_base}.log" + f06_file = working_dir / f"{output_base}.f06" + + print(f"\n[NX SOLVER] Starting simulation...") + print(f" Input file: {sim_file.name}") + print(f" Working dir: {working_dir}") + print(f" Mode: {'Journal' if self.use_journal else 'Direct'}") + + # Delete old result files (.op2, .log, .f06) to force fresh solve + # (.dat file is needed by NX, don't delete it!) + # (Otherwise NX may reuse cached results) + files_to_delete = [op2_file, log_file, f06_file] + + deleted_count = 0 + for old_file in files_to_delete: + if old_file.exists(): + try: + old_file.unlink() + deleted_count += 1 + except Exception as e: + print(f" Warning: Could not delete {old_file.name}: {e}") + + if deleted_count > 0: + print(f" Deleted {deleted_count} old result file(s) to force fresh solve") + + # Build command based on mode + if self.use_journal and sim_file.suffix == '.sim': + # Use NX journal for .sim files (handles licensing properly) + # Generate a temporary journal file with the correct sim file path + journal_template = Path(__file__).parent / "solve_simulation.py" + temp_journal = working_dir / "_temp_solve_journal.py" + + # Read template and replace placeholder with actual path + with open(journal_template, 'r') as f: + journal_content = f.read() + + # Create a custom journal that passes the sim file path + custom_journal = f'''# Auto-generated journal for solving {sim_file.name} +import sys +sys.argv = ['', r'{sim_file.absolute()}'] # Set argv for the main function +{journal_content} +''' + with open(temp_journal, 'w') as f: + f.write(custom_journal) + + cmd = [ + str(self.solver_exe), # run_journal.exe + str(temp_journal.absolute()) # Use absolute path to avoid path issues + ] + else: + # Direct Nastran batch command for .dat files or direct mode + # IMPORTANT: prog=bundle enables bundle licensing (required for desktop licenses) + cmd = [ + str(self.solver_exe), + str(sim_file), + "prog=bundle", + "old=no", + "scratch=yes" + ] + + # Set up environment for Simcenter/NX + env = os.environ.copy() + + # Set license server (use 29000 for Simcenter) + # Override any incorrect license server settings + env['SPLM_LICENSE_SERVER'] = '29000@AntoineThinkpad' + + # Force desktop licensing instead of enterprise + # User has nx_nas_bn_basic_dsk (desktop) not nx_nas_basic_ent (enterprise) + env['NXNA_LICENSE_FILE'] = '29000@AntoineThinkpad' + env['NXNASTRAN_LICENSE_FILE'] = '29000@AntoineThinkpad' + + # Add NX/Simcenter paths to environment + nx_bin = self.nx_install_dir / "NXBIN" + if nx_bin.exists(): + env['PATH'] = f"{nx_bin};{env.get('PATH', '')}" + + nastran_bin = self.solver_exe.parent + if nastran_bin.exists(): + env['PATH'] = f"{nastran_bin};{env.get('PATH', '')}" + + # Run solver + start_time = time.time() + + try: + result = subprocess.run( + cmd, + cwd=str(working_dir), + capture_output=True, + text=True, + timeout=self.timeout, + env=env # Use modified environment + ) + + elapsed_time = time.time() - start_time + + # Check for journal errors + if self.use_journal and result.stderr and "error" in result.stderr.lower(): + print("[JOURNAL ERRORS]") + for line in result.stderr.strip().split('\n')[:5]: + print(f" {line}") + + # Wait for output files to appear (journal mode runs solve in background) + if self.use_journal: + max_wait = 30 # seconds - background solves can take time + wait_start = time.time() + print("[NX SOLVER] Waiting for solve to complete...") + while not (f06_file.exists() and op2_file.exists()) and (time.time() - wait_start) < max_wait: + time.sleep(0.5) + if (time.time() - wait_start) % 2 < 0.5: # Print every 2 seconds + elapsed = time.time() - wait_start + print(f" Waiting... ({elapsed:.0f}s)") + + if f06_file.exists() and op2_file.exists(): + print(f"[NX SOLVER] Output files detected after {time.time() - wait_start:.1f}s") + + # Check for completion + success = self._check_solution_success(f06_file, log_file) + + errors = [] + if not success: + errors = self._extract_errors(f06_file, log_file) + + # Clean up intermediate files if requested + if cleanup and success: + self._cleanup_temp_files(working_dir, base_name) + + # Clean up temporary journal file if it was created + temp_journal_path = working_dir / "_temp_solve_journal.py" + if temp_journal_path.exists(): + try: + temp_journal_path.unlink() + except Exception: + pass + + print(f"[NX SOLVER] Complete in {elapsed_time:.1f}s") + if success: + print(f"[NX SOLVER] Results: {op2_file.name}") + else: + print(f"[NX SOLVER] FAILED - check {f06_file.name}") + for error in errors: + print(f" ERROR: {error}") + + return { + 'success': success, + 'op2_file': op2_file if op2_file.exists() else None, + 'log_file': log_file if log_file.exists() else None, + 'f06_file': f06_file if f06_file.exists() else None, + 'elapsed_time': elapsed_time, + 'errors': errors, + 'return_code': result.returncode + } + + except subprocess.TimeoutExpired: + elapsed_time = time.time() - start_time + print(f"[NX SOLVER] TIMEOUT after {elapsed_time:.1f}s") + return { + 'success': False, + 'op2_file': None, + 'log_file': log_file if log_file.exists() else None, + 'elapsed_time': elapsed_time, + 'errors': [f'Solver timeout after {self.timeout}s'], + 'return_code': -1 + } + + except Exception as e: + elapsed_time = time.time() - start_time + print(f"[NX SOLVER] ERROR: {e}") + return { + 'success': False, + 'op2_file': None, + 'log_file': None, + 'elapsed_time': elapsed_time, + 'errors': [str(e)], + 'return_code': -1 + } + + def _check_solution_success(self, f06_file: Path, log_file: Path) -> bool: + """ + Check if solution completed successfully. + + Looks for completion markers in .f06 and .log files. + """ + # Check .f06 file for completion + if f06_file.exists(): + try: + with open(f06_file, 'r', encoding='latin-1', errors='ignore') as f: + content = f.read() + # Look for successful completion markers + if 'NORMAL TERMINATION' in content or 'USER INFORMATION MESSAGE' in content: + return True + # Check for fatal errors + if 'FATAL MESSAGE' in content or 'EXECUTION TERMINATED' in content: + return False + except Exception: + pass + + # Fallback: check if OP2 was created recently + op2_file = f06_file.with_suffix('.op2') + if op2_file.exists(): + # If OP2 was modified within last minute, assume success + if (time.time() - op2_file.stat().st_mtime) < 60: + return True + + return False + + def _extract_errors(self, f06_file: Path, log_file: Path) -> list: + """Extract error messages from output files.""" + errors = [] + + if f06_file.exists(): + try: + with open(f06_file, 'r', encoding='latin-1', errors='ignore') as f: + for line in f: + if 'FATAL' in line or 'ERROR' in line: + errors.append(line.strip()) + except Exception: + pass + + return errors[:10] # Limit to first 10 errors + + def _cleanup_temp_files(self, working_dir: Path, base_name: str): + """Remove temporary solver files.""" + # Files to keep + keep_extensions = {'.op2', '.f06', '.log'} + + # Files to remove + remove_patterns = [ + f"{base_name}.f04", + f"{base_name}.dat", + f"{base_name}.diag", + f"{base_name}.master", + f"{base_name}.dball", + f"{base_name}.MASTER", + f"{base_name}.DBALL", + f"{base_name}_*.png", + f"{base_name}_*.html", + ] + + for pattern in remove_patterns: + for file in working_dir.glob(pattern): + try: + file.unlink() + except Exception: + pass + + +# Convenience function for optimization loops +def run_nx_simulation( + sim_file: Path, + nastran_version: str = "2412", + timeout: int = 600, + cleanup: bool = True, + use_journal: bool = True +) -> Path: + """ + Convenience function to run NX simulation and return OP2 file path. + + Args: + sim_file: Path to .sim file + nastran_version: NX version + timeout: Solver timeout in seconds + cleanup: Remove temp files + use_journal: Use NX journal for solving (recommended for licensing) + + Returns: + Path to output .op2 file + + Raises: + RuntimeError: If simulation fails + """ + solver = NXSolver(nastran_version=nastran_version, timeout=timeout, use_journal=use_journal) + result = solver.run_simulation(sim_file, cleanup=cleanup) + + if not result['success']: + error_msg = '\n'.join(result['errors']) if result['errors'] else 'Unknown error' + raise RuntimeError(f"NX simulation failed:\n{error_msg}") + + if not result['op2_file'] or not result['op2_file'].exists(): + raise RuntimeError("Simulation completed but OP2 file not found") + + return result['op2_file'] diff --git a/optimization_engine/solve_simulation.py b/optimization_engine/solve_simulation.py new file mode 100644 index 00000000..934b7d85 --- /dev/null +++ b/optimization_engine/solve_simulation.py @@ -0,0 +1,182 @@ +""" +NX Journal Script to Solve Simulation in Batch Mode + +This script opens a .sim file, updates the FEM, and solves it through the NX API. +Usage: run_journal.exe solve_simulation.py + +Based on recorded NX journal pattern for solving simulations. +""" + +import sys +import NXOpen +import NXOpen.Assemblies +import NXOpen.CAE + + +def main(args): + """ + Open and solve a simulation file. + + Args: + args: Command line arguments, args[0] should be the .sim file path + """ + if len(args) < 1: + print("ERROR: No .sim file path provided") + print("Usage: run_journal.exe solve_simulation.py ") + return False + + sim_file_path = args[0] + print(f"[JOURNAL] Opening simulation: {sim_file_path}") + + try: + theSession = NXOpen.Session.GetSession() + + # Close any currently open sim file to force reload from disk + print("[JOURNAL] Checking for open parts...") + try: + current_work = theSession.Parts.BaseWork + if current_work and hasattr(current_work, 'FullPath'): + current_path = current_work.FullPath + print(f"[JOURNAL] Closing currently open part: {current_path}") + # Close without saving (we want to reload from disk) + partCloseResponses1 = [NXOpen.BasePart.CloseWholeTree] + theSession.Parts.CloseAll(partCloseResponses1) + print("[JOURNAL] Parts closed") + except Exception as e: + print(f"[JOURNAL] No parts to close or error closing: {e}") + + # Open the .sim file (now will load fresh from disk with updated .prt files) + print(f"[JOURNAL] Opening simulation fresh from disk...") + basePart1, partLoadStatus1 = theSession.Parts.OpenActiveDisplay( + sim_file_path, + NXOpen.DisplayPartOption.AllowAdditional + ) + + workSimPart = theSession.Parts.BaseWork + displaySimPart = theSession.Parts.BaseDisplay + partLoadStatus1.Dispose() + + # Switch to simulation application + theSession.ApplicationSwitchImmediate("UG_APP_SFEM") + + simPart1 = workSimPart + theSession.Post.UpdateUserGroupsFromSimPart(simPart1) + + # Reload all components to pick up parameter changes from .prt files + print("[JOURNAL] Reloading components to pick up .prt parameter changes...") + try: + workSimPart.ComponentAssembly.ReloadComponents( + NXOpen.Assemblies.ComponentAssembly.ReloadOption.AllLoaded + ) + print("[JOURNAL] Components reloaded") + except Exception as e: + print(f"[JOURNAL] Warning: Could not reload components: {e}") + + # Make FEM work component (to ensure it's updated) + # Find the FEM component - pattern: "COMPONENT _fem1 1" + markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + + # Get all components and find the FEM one + rootComponent = workSimPart.ComponentAssembly.RootComponent + femComponent = None + + for component in rootComponent.GetChildren(): + if "_fem" in component.DisplayName.lower(): + femComponent = component + break + + if femComponent: + print(f"[JOURNAL] Switching to FEM component: {femComponent.DisplayName}") + + # Make FEM the work component (this is what your recorded journal does) + partLoadStatus2 = theSession.Parts.SetWorkComponent( + femComponent, + NXOpen.PartCollection.RefsetOption.Entire, + NXOpen.PartCollection.WorkComponentOption.Visible + ) + partLoadStatus2.Dispose() + + # Get the FEM part and try to update it + workFemPart = theSession.Parts.BaseWork + try: + # Try to update the FEM to pick up geometry/parameter changes + print("[JOURNAL] Updating FEM to recognize parameter changes...") + if hasattr(workFemPart, 'FemPart'): + workFemPart.FemPart.UpdateFeModel() + print("[JOURNAL] FEM updated") + except Exception as e: + print(f"[JOURNAL] Note: Could not update FEM (may not be necessary): {e}") + + # Switch back to sim part (this forces NX to recognize updates) + markId2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + partLoadStatus3 = theSession.Parts.SetWorkComponent( + NXOpen.Assemblies.Component.Null, + NXOpen.PartCollection.RefsetOption.Entire, + NXOpen.PartCollection.WorkComponentOption.Visible + ) + workSimPart = theSession.Parts.BaseWork + partLoadStatus3.Dispose() + print("[JOURNAL] Switched back to sim part") + else: + print("[JOURNAL] WARNING: No FEM component found, proceeding with solve anyway") + + # Note: Old output files are deleted by nx_solver.py before calling this journal + # This ensures NX performs a fresh solve + + # Solve the simulation + print("[JOURNAL] Starting solve...") + markId3 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + theSession.SetUndoMarkName(markId3, "Solve Dialog") + + markId5 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") + + theCAESimSolveManager = NXOpen.CAE.SimSolveManager.GetSimSolveManager(theSession) + + # Get the first solution from the simulation + simSimulation1 = workSimPart.FindObject("Simulation") + simSolution1 = simSimulation1.FindObject("Solution[Solution 1]") + + psolutions1 = [simSolution1] + + # Solve in background mode + numsolutionssolved1, numsolutionsfailed1, numsolutionsskipped1 = theCAESimSolveManager.SolveChainOfSolutions( + psolutions1, + NXOpen.CAE.SimSolution.SolveOption.Solve, + NXOpen.CAE.SimSolution.SetupCheckOption.CompleteDeepCheckAndOutputErrors, + NXOpen.CAE.SimSolution.SolveMode.Background + ) + + theSession.DeleteUndoMark(markId5, None) + theSession.SetUndoMarkName(markId3, "Solve") + + print(f"[JOURNAL] Solve submitted!") + print(f"[JOURNAL] Solutions solved: {numsolutionssolved1}") + print(f"[JOURNAL] Solutions failed: {numsolutionsfailed1}") + print(f"[JOURNAL] Solutions skipped: {numsolutionsskipped1}") + + # NOTE: In Background mode, these values may not be accurate since the solve + # runs asynchronously. The solve will continue after this journal finishes. + # We rely on the Save operation and file existence checks to verify success. + + # Save the simulation to write all output files + print("[JOURNAL] Saving simulation to ensure output files are written...") + simPart2 = workSimPart + partSaveStatus1 = simPart2.Save( + NXOpen.BasePart.SaveComponents.TrueValue, + NXOpen.BasePart.CloseAfterSave.FalseValue + ) + partSaveStatus1.Dispose() + print("[JOURNAL] Save complete!") + + return True + + except Exception as e: + print(f"[JOURNAL] ERROR: {e}") + import traceback + traceback.print_exc() + return False + + +if __name__ == '__main__': + success = main(sys.argv[1:]) + sys.exit(0 if success else 1) diff --git a/tests/journal_open_run_sim.py b/tests/journal_open_run_sim.py new file mode 100644 index 00000000..2752b499 --- /dev/null +++ b/tests/journal_open_run_sim.py @@ -0,0 +1,75 @@ +# NX 2412 +# Journal created by antoi on Sat Nov 15 11:49:43 2025 Eastern Standard Time +# +import math +import NXOpen +import NXOpen.Assemblies +import NXOpen.CAE +import NXOpen.MenuBar +import NXOpen.UserDefinedObjects +def main(args) : + + theSession = NXOpen.Session.GetSession() #type: NXOpen.Session + # ---------------------------------------------- + # Menu: File->Open... + # ---------------------------------------------- + basePart1, partLoadStatus1 = theSession.Parts.OpenActiveDisplay("C:\\Users\\antoi\\Documents\\Atomaste\\Atomizer\\tests\\Bracket_sim1.sim", NXOpen.DisplayPartOption.AllowAdditional) + + workSimPart = theSession.Parts.BaseWork + displaySimPart = theSession.Parts.BaseDisplay + partLoadStatus1.Dispose() + theSession.ApplicationSwitchImmediate("UG_APP_SFEM") + + # User Function call - UF_translate_variable + + # User Function call - UF_TEXT_ask_text_mode + + baseTemplateManager1 = theSession.XYPlotManager.TemplateManager + + simPart1 = workSimPart + theSession.Post.UpdateUserGroupsFromSimPart(simPart1) + + markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + + component1 = workSimPart.ComponentAssembly.RootComponent.FindObject("COMPONENT Bracket_fem1 1") + partLoadStatus2 = theSession.Parts.SetWorkComponent(component1, NXOpen.PartCollection.RefsetOption.Entire, NXOpen.PartCollection.WorkComponentOption.Visible) + + workFemPart = theSession.Parts.BaseWork + partLoadStatus2.Dispose() + markId2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + + partLoadStatus3 = theSession.Parts.SetWorkComponent(NXOpen.Assemblies.Component.Null, NXOpen.PartCollection.RefsetOption.Entire, NXOpen.PartCollection.WorkComponentOption.Visible) + + workSimPart = theSession.Parts.BaseWork # Bracket_sim1 + partLoadStatus3.Dispose() + # ---------------------------------------------- + # Menu: Analysis->Solve... + # ---------------------------------------------- + markId3 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + + theSession.SetUndoMarkName(markId3, "Solve Dialog") + + markId4 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") + + theSession.DeleteUndoMark(markId4, None) + + markId5 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") + + theCAESimSolveManager = NXOpen.CAE.SimSolveManager.GetSimSolveManager(theSession) + + psolutions1 = [NXOpen.CAE.SimSolution.Null] * 1 + simSimulation1 = workSimPart.FindObject("Simulation") + simSolution1 = simSimulation1.FindObject("Solution[Solution 1]") + psolutions1[0] = simSolution1 + numsolutionssolved1, numsolutionsfailed1, numsolutionsskipped1 = theCAESimSolveManager.SolveChainOfSolutions(psolutions1, NXOpen.CAE.SimSolution.SolveOption.Solve, NXOpen.CAE.SimSolution.SetupCheckOption.CompleteDeepCheckAndOutputErrors, NXOpen.CAE.SimSolution.SolveMode.Background) + + theSession.DeleteUndoMark(markId5, None) + + theSession.SetUndoMarkName(markId3, "Solve") + + # ---------------------------------------------- + # Menu: Tools->Automation->Journal->Stop Recording + # ---------------------------------------------- + +if __name__ == '__main__': + main(sys.argv[1:]) \ No newline at end of file diff --git a/tests/journal_open_run_sim_and_save.py b/tests/journal_open_run_sim_and_save.py new file mode 100644 index 00000000..b7ac8485 --- /dev/null +++ b/tests/journal_open_run_sim_and_save.py @@ -0,0 +1,82 @@ +# NX 2412 +# Journal created by antoi on Sat Nov 15 12:01:55 2025 Eastern Standard Time +# +import math +import NXOpen +import NXOpen.Assemblies +import NXOpen.CAE +import NXOpen.MenuBar +import NXOpen.UserDefinedObjects +def main(args) : + + theSession = NXOpen.Session.GetSession() #type: NXOpen.Session + # ---------------------------------------------- + # Menu: File->Open... + # ---------------------------------------------- + basePart1, partLoadStatus1 = theSession.Parts.OpenActiveDisplay("C:\\Users\\antoi\\Documents\\Atomaste\\Atomizer\\tests\\Bracket_sim1.sim", NXOpen.DisplayPartOption.AllowAdditional) + + workSimPart = theSession.Parts.BaseWork + displaySimPart = theSession.Parts.BaseDisplay + partLoadStatus1.Dispose() + theSession.ApplicationSwitchImmediate("UG_APP_SFEM") + + # User Function call - UF_translate_variable + + # User Function call - UF_TEXT_ask_text_mode + + baseTemplateManager1 = theSession.XYPlotManager.TemplateManager + + simPart1 = workSimPart + theSession.Post.UpdateUserGroupsFromSimPart(simPart1) + + markId1 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + + component1 = workSimPart.ComponentAssembly.RootComponent.FindObject("COMPONENT Bracket_fem1 1") + partLoadStatus2 = theSession.Parts.SetWorkComponent(component1, NXOpen.PartCollection.RefsetOption.Entire, NXOpen.PartCollection.WorkComponentOption.Visible) + + workFemPart = theSession.Parts.BaseWork + partLoadStatus2.Dispose() + markId2 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Make Work Part") + + partLoadStatus3 = theSession.Parts.SetWorkComponent(NXOpen.Assemblies.Component.Null, NXOpen.PartCollection.RefsetOption.Entire, NXOpen.PartCollection.WorkComponentOption.Visible) + + workSimPart = theSession.Parts.BaseWork # Bracket_sim1 + partLoadStatus3.Dispose() + # ---------------------------------------------- + # Menu: Analysis->Solve... + # ---------------------------------------------- + markId3 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Start") + + theSession.SetUndoMarkName(markId3, "Solve Dialog") + + markId4 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") + + theSession.DeleteUndoMark(markId4, None) + + markId5 = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "Solve") + + theCAESimSolveManager = NXOpen.CAE.SimSolveManager.GetSimSolveManager(theSession) + + psolutions1 = [NXOpen.CAE.SimSolution.Null] * 1 + simSimulation1 = workSimPart.FindObject("Simulation") + simSolution1 = simSimulation1.FindObject("Solution[Solution 1]") + psolutions1[0] = simSolution1 + numsolutionssolved1, numsolutionsfailed1, numsolutionsskipped1 = theCAESimSolveManager.SolveChainOfSolutions(psolutions1, NXOpen.CAE.SimSolution.SolveOption.Solve, NXOpen.CAE.SimSolution.SetupCheckOption.CompleteDeepCheckAndOutputErrors, NXOpen.CAE.SimSolution.SolveMode.Background) + + theSession.DeleteUndoMark(markId5, None) + + theSession.SetUndoMarkName(markId3, "Solve") + + # ---------------------------------------------- + # Menu: File->Save + # ---------------------------------------------- + simPart2 = workSimPart + partSaveStatus1 = simPart2.Save(NXOpen.BasePart.SaveComponents.TrueValue, NXOpen.BasePart.CloseAfterSave.FalseValue) + + partSaveStatus1.Dispose() + # ---------------------------------------------- + # Menu: Tools->Automation->Journal->Stop Recording + # ---------------------------------------------- + +if __name__ == '__main__': + main(sys.argv[1:]) \ No newline at end of file diff --git a/tests/journal_reload_master.py b/tests/journal_reload_master.py new file mode 100644 index 00000000..c25c78f3 --- /dev/null +++ b/tests/journal_reload_master.py @@ -0,0 +1,23 @@ +# NX 2412 +# Journal created by antoi on Sat Nov 15 12:12:09 2025 Eastern Standard Time +# +import math +import NXOpen +import NXOpen.CAE +def main(args) : + + theSession = NXOpen.Session.GetSession() #type: NXOpen.Session + workSimPart = theSession.Parts.BaseWork + displaySimPart = theSession.Parts.BaseDisplay + # ---------------------------------------------- + # Menu: File->Close->Close and Reopen All Modified Parts + # ---------------------------------------------- + partReopenReport1 = theSession.Parts.ReopenAll(NXOpen.BasePart.CloseModified.CloseModified, None) + + partReopenReport1.Dispose() + # ---------------------------------------------- + # Menu: Tools->Automation->Journal->Stop Recording + # ---------------------------------------------- + +if __name__ == '__main__': + main(sys.argv[1:]) \ No newline at end of file