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