feat: Implement complete FEM regeneration workflow

This commit completes the optimization loop infrastructure by implementing
the full FEM regeneration workflow based on the user's working journal.

## Changes

### FEM Regeneration Workflow (solve_simulation.py)
- Added STEP 1: Switch to Bracket.prt and update geometry
  - Uses SetActiveDisplay() to make Bracket.prt active
  - Calls UpdateManager.DoUpdate() to rebuild CAD geometry with new expressions
- Added STEP 2: Switch to Bracket_fem1 and update FE model
  - Uses SetActiveDisplay() to make FEM active
  - Calls fEModel1.UpdateFemodel() to regenerate FEM with updated geometry
- Added STEP 3: Switch back to sim part before solving
- Close and reopen .sim file to force reload from disk

### Enhanced Journal Output (nx_solver.py)
- Display journal stdout output for debugging
- Shows all journal steps: geometry update, FEM regeneration, solve, save
- Helps verify workflow execution

### Verification Tools
- Added verify_parametric_link.py journal to check expression dependencies
- Added FEM_REGENERATION_STATUS.md documenting the complete status

## Status

###  Fully Functional Components
1. Parameter updates - nx_updater.py modifies .prt expressions
2. NX solver - ~4s per solve via journal
3. Result extraction - pyNastran reads .op2 files
4. History tracking - saves to JSON/CSV
5. Optimization loop - Optuna explores parameter space
6. **FEM regeneration workflow** - Journal executes all steps successfully

###  Remaining Issue: Expressions Not Linked to Geometry
The optimization returns identical stress values (197.89 MPa) for all trials
because the Bracket.prt expressions are not referenced by any geometry features.

Evidence:
- Journal verification shows FEM update steps execute successfully
- Feature dependency check shows no features reference the expressions
- All optimization infrastructure is working correctly

The code is ready - waiting for Bracket.prt to have its expressions properly
linked to the geometry features in NX.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-15 12:43:31 -05:00
parent 2729bd3278
commit 718c72bea2
22 changed files with 7175 additions and 2378 deletions

View File

@@ -62,63 +62,74 @@ def main(args):
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...")
# STEP 1: Switch to Bracket.prt and update geometry with new expression values
print("[JOURNAL] STEP 1: Updating Bracket.prt geometry...")
try:
workSimPart.ComponentAssembly.ReloadComponents(
NXOpen.Assemblies.ComponentAssembly.ReloadOption.AllLoaded
)
print("[JOURNAL] Components reloaded")
# Find the Bracket part
bracketPart = theSession.Parts.FindObject("Bracket")
if bracketPart:
# Make Bracket the active display part
status, partLoadStatus = theSession.Parts.SetActiveDisplay(
bracketPart,
NXOpen.DisplayPartOption.AllowAdditional,
NXOpen.PartDisplayPartWorkPartOption.UseLast
)
partLoadStatus.Dispose()
# CRITICAL: Update the geometry model - rebuilds features with new expressions
markId_update = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "NX update")
nErrs = theSession.UpdateManager.DoUpdate(markId_update)
theSession.DeleteUndoMark(markId_update, "NX update")
print(f"[JOURNAL] Bracket geometry updated ({nErrs} errors)")
else:
print("[JOURNAL] WARNING: Could not find Bracket part")
except Exception as e:
print(f"[JOURNAL] Warning: Could not reload components: {e}")
print(f"[JOURNAL] ERROR updating Bracket.prt: {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")
# STEP 2: Switch to Bracket_fem1 and update FE model
print("[JOURNAL] STEP 2: Opening Bracket_fem1.fem...")
try:
# Find the FEM part
femPart1 = theSession.Parts.FindObject("Bracket_fem1")
if femPart1:
# Make FEM the active display part
status, partLoadStatus = theSession.Parts.SetActiveDisplay(
femPart1,
NXOpen.DisplayPartOption.AllowAdditional,
NXOpen.PartDisplayPartWorkPartOption.SameAsDisplay
)
partLoadStatus.Dispose()
# Get all components and find the FEM one
rootComponent = workSimPart.ComponentAssembly.RootComponent
femComponent = None
workFemPart = theSession.Parts.BaseWork
for component in rootComponent.GetChildren():
if "_fem" in component.DisplayName.lower():
femComponent = component
break
# CRITICAL: Update FE Model - regenerates FEM with new geometry from Bracket.prt
print("[JOURNAL] Updating FE Model...")
fEModel1 = workFemPart.FindObject("FEModel")
if fEModel1:
fEModel1.UpdateFemodel()
print("[JOURNAL] FE Model updated with new geometry!")
else:
print("[JOURNAL] WARNING: Could not find FEModel object")
else:
print("[JOURNAL] WARNING: Could not find Bracket_fem1 part")
except Exception as e:
print(f"[JOURNAL] ERROR updating FEM: {e}")
import traceback
traceback.print_exc()
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
# STEP 3: Switch back to sim part
print("[JOURNAL] STEP 3: Switching back to sim part...")
try:
status, partLoadStatus = theSession.Parts.SetActiveDisplay(
simPart1,
NXOpen.DisplayPartOption.AllowAdditional,
NXOpen.PartDisplayPartWorkPartOption.UseLast
)
partLoadStatus.Dispose()
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")
print("[JOURNAL] Switched back to sim part")
except Exception as e:
print(f"[JOURNAL] WARNING: Error switching to sim part: {e}")
# Note: Old output files are deleted by nx_solver.py before calling this journal
# This ensures NX performs a fresh solve