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 <noreply@anthropic.com>
183 lines
7.2 KiB
Python
183 lines
7.2 KiB
Python
"""
|
|
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 <sim_file_path>
|
|
|
|
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 <sim_file_path>")
|
|
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 <name>_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)
|