# NX Expression Import System

> **Feature**: Robust NX part expression update via .exp file import
>
> **Status**: ✅ Production Ready (2025-11-17)
>
> **Impact**: Enables updating ALL NX expressions including those not stored in text format in binary .prt files

---

## Overview

The NX Expression Import System provides a robust method for updating NX part expressions by leveraging NX's native .exp file import functionality through journal scripts.

### Problem Solved

Some NX expressions (like `hole_count` in parametric features) are stored in binary .prt file formats that cannot be reliably parsed or updated through text-based regex operations. Traditional binary .prt editing fails for expressions that:
- Are used inside feature parameters
- Are stored in non-text binary sections
- Are linked to parametric pattern features

### Solution

Instead of binary .prt editing, use NX's native expression import/export:
1. Export all expressions to .exp file format (text-based)
2. Create .exp file containing only study design variables with new values
3. Import .exp file using NX journal script
4. NX updates all expressions natively, including binary-stored ones

---

## Architecture

### Components

1. **NXParameterUpdater** ([optimization_engine/nx_updater.py](../optimization_engine/nx_updater.py))
   - Main class handling expression updates
   - Provides both legacy (binary edit) and new (NX import) methods
   - Automatic method selection based on expression type

2. **import_expressions.py** ([optimization_engine/import_expressions.py](../optimization_engine/import_expressions.py))
   - NX journal script for importing .exp files
   - Handles part loading, expression import, model update, and save
   - Robust error handling and status reporting

3. **.exp File Format**
   - Plain text format for NX expressions
   - Format: `[Units]name=value` or `name=value` (unitless)
   - Human-readable and LLM-friendly

### Workflow

```
┌─────────────────────────────────────────────────────────┐
│ 1. Export ALL expressions to .exp format               │
│    (NX journal: export_expressions.py)                  │
│    Purpose: Determine units for each expression        │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│ 2. Create .exp file with ONLY study variables          │
│    [MilliMeter]beam_face_thickness=22.0                │
│    [MilliMeter]beam_half_core_thickness=25.0           │
│    [MilliMeter]holes_diameter=280.0                    │
│    hole_count=12                                        │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│ 3. Run NX journal to import expressions                │
│    (NX journal: import_expressions.py)                  │
│    - Opens .prt file                                    │
│    - Imports .exp using Replace mode                    │
│    - Updates model geometry                             │
│    - Saves .prt file                                    │
└─────────────────────────────────────────────────────────┘
                          ↓
┌─────────────────────────────────────────────────────────┐
│ 4. Verify updates                                       │
│    - Re-export expressions                              │
│    - Confirm all values updated                         │
└─────────────────────────────────────────────────────────┘
```

---

## Usage

### Basic Usage

```python
from pathlib import Path
from optimization_engine.nx_updater import NXParameterUpdater

# Create updater
prt_file = Path("studies/simple_beam_optimization/model/Beam.prt")
updater = NXParameterUpdater(prt_file)

# Define design variables to update
design_vars = {
    "beam_half_core_thickness": 25.0,    # mm
    "beam_face_thickness": 22.0,         # mm
    "holes_diameter": 280.0,             # mm
    "hole_count": 12                     # unitless
}

# Update expressions using NX import (default method)
updater.update_expressions(design_vars)

# Verify updates
expressions = updater.get_all_expressions()
for name, value in design_vars.items():
    actual = expressions[name]["value"]
    print(f"{name}: expected={value}, actual={actual}, match={abs(actual - value) < 0.001}")
```

### Integration in Optimization Loop

The system is automatically used in optimization workflows:

```python
# In OptimizationRunner
for trial in range(n_trials):
    # Optuna suggests new design variable values
    design_vars = {
        "beam_half_core_thickness": trial.suggest_float("beam_half_core_thickness", 10, 40),
        "holes_diameter": trial.suggest_float("holes_diameter", 150, 450),
        "hole_count": trial.suggest_int("hole_count", 5, 15),
        # ... other variables
    }

    # Update NX model (automatically uses .exp import)
    updater.update_expressions(design_vars)

    # Run FEM simulation
    solver.solve(sim_file)

    # Extract results
    results = extractor.extract(op2_file)
```

---

## File Format: .exp

### Format Specification

```
[UnitSystem]expression_name=value
expression_name=value              # For unitless expressions
```

### Example .exp File

```
[MilliMeter]beam_face_thickness=20.0
[MilliMeter]beam_half_core_thickness=20.0
[MilliMeter]holes_diameter=400.0
hole_count=10
```

### Supported Units

NX units are specified in square brackets:
- `[MilliMeter]` - Length in mm
- `[Meter]` - Length in m
- `[Newton]` - Force in N
- `[Kilogram]` - Mass in kg
- `[Pascal]` - Pressure/stress in Pa
- `[Degree]` - Angle in degrees
- No brackets - Unitless values

---

## Implementation Details

### NXParameterUpdater.update_expressions_via_import()

**Location**: [optimization_engine/nx_updater.py](../optimization_engine/nx_updater.py)

**Purpose**: Update expressions by creating and importing .exp file

**Algorithm**:
1. Export ALL expressions from .prt to get units information
2. Create .exp file with ONLY study variables:
   - Use units from full export
   - Format: `[units]name=value` or `name=value`
3. Run NX journal script to import .exp file
4. Delete temporary .exp file
5. Return success/failure status

**Key Code**:
```python
def update_expressions_via_import(self, updates: Dict[str, float]):
    # Get all expressions to determine units
    all_expressions = self.get_all_expressions(use_exp_export=True)

    # Create .exp file with ONLY study variables
    exp_file = self.prt_path.parent / f"{self.prt_path.stem}_study_variables.exp"

    with open(exp_file, 'w', encoding='utf-8') as f:
        for name, value in updates.items():
            units = all_expressions[name].get('units', '')
            if units:
                f.write(f"[{units}]{name}={value}\n")
            else:
                f.write(f"{name}={value}\n")

    # Run NX journal to import
    journal_script = Path(__file__).parent / "import_expressions.py"
    cmd_str = f'"{self.nx_run_journal_path}" "{journal_script}" -args "{self.prt_path}" "{exp_file}"'
    result = subprocess.run(cmd_str, capture_output=True, text=True, shell=True)

    # Clean up
    exp_file.unlink()

    return result.returncode == 0
```

### import_expressions.py Journal

**Location**: [optimization_engine/import_expressions.py](../optimization_engine/import_expressions.py)

**Purpose**: NX journal script to import .exp file into .prt file

**NXOpen API Usage**:
```python
# Open part file
workPart, partLoadStatus1 = theSession.Parts.OpenActiveDisplay(
    prt_file,
    NXOpen.DisplayPartOption.AllowAdditional
)

# Import expressions (Replace mode overwrites existing values)
expModified, errorMessages = workPart.Expressions.ImportFromFile(
    exp_file,
    NXOpen.ExpressionCollection.ImportMode.Replace
)

# Update geometry with new expression values
markId = theSession.SetUndoMark(NXOpen.Session.MarkVisibility.Invisible, "NX update")
nErrs = theSession.UpdateManager.DoUpdate(markId)

# Save part
partSaveStatus = workPart.Save(
    NXOpen.BasePart.SaveComponents.TrueValue,
    NXOpen.BasePart.CloseAfterSave.FalseValue
)
```

---

## Validation Results

### Test Case: 4D Beam Optimization

**Study**: `studies/simple_beam_optimization/`

**Design Variables**:
- `beam_half_core_thickness`: 10-40 mm
- `beam_face_thickness`: 10-40 mm
- `holes_diameter`: 150-450 mm
- `hole_count`: 5-15 (integer, unitless)

**Problem**: `hole_count` was not updating with binary .prt editing

**Solution**: Implemented .exp import system

**Results**:
```
✅ Trial 0: hole_count=6  (successfully updated from baseline=10)
✅ Trial 1: hole_count=15 (successfully updated)
✅ Trial 2: hole_count=11 (successfully updated)

Mesh adaptation confirmed:
- Trial 0: 5373 CQUAD4 elements (6 holes)
- Trial 1: 5158 CQUAD4 + 1 CTRIA3 (15 holes)
- Trial 2: 5318 CQUAD4 (11 holes)

All 3 trials: ALL 4 variables updated successfully
```

---

## Advantages

### Robustness
- Works for ALL expression types, not just text-parseable ones
- Native NX functionality - no binary file hacks
- Handles units automatically
- No regex pattern failures

### Simplicity
- .exp format is human-readable
- Easy to debug (just open .exp file)
- LLM-friendly format

### Reliability
- NX validates expressions during import
- Automatic model update after import
- Error messages from NX if import fails

### Performance
- Fast: .exp file creation + journal execution < 1 second
- No need to parse large .prt files
- Minimal I/O operations

---

## Comparison: Binary Edit vs .exp Import

| Aspect | Binary .prt Edit | .exp Import (New) |
|--------|------------------|-------------------|
| **Expression Coverage** | ~60-80% (text-parseable only) | ✅ 100% (all expressions) |
| **Reliability** | Fragile (regex failures) | ✅ Robust (native NX) |
| **Units Handling** | Manual regex parsing | ✅ Automatic via .exp format |
| **Model Update** | Requires separate step | ✅ Integrated in journal |
| **Debugging** | Hard (binary file) | ✅ Easy (.exp is text) |
| **Performance** | Fast (direct edit) | Fast (journal execution) |
| **Error Handling** | Limited | ✅ Full NX validation |
| **Feature Parameters** | ❌ Fails for linked expressions | ✅ Works for all |

**Recommendation**: Use .exp import by default. Binary edit only for legacy/special cases.

---

## Future Enhancements

### Batch Updates
Currently creates one .exp file per update operation. Could optimize:
- Cache .exp file across multiple trials
- Only recreate if design variables change

### Validation
Add pre-import validation:
- Check expression names exist
- Validate value ranges
- Warn about unit mismatches

### Rollback
Implement undo capability:
- Save original .exp before updates
- Restore from backup if import fails

### Performance Profiling
Measure and optimize:
- .exp export time
- Journal execution time
- Model update time

---

## References

### NXOpen Documentation
- `NXOpen.ExpressionCollection.ImportFromFile()` - Import expressions from .exp file
- `NXOpen.ExpressionCollection.ExportMode.Replace` - Overwrite existing expression values
- `NXOpen.Session.UpdateManager.DoUpdate()` - Update model after expression changes

### Files
- [nx_updater.py](../optimization_engine/nx_updater.py) - Main implementation
- [import_expressions.py](../optimization_engine/import_expressions.py) - NX journal script
- [NXOPEN_INTELLISENSE_SETUP.md](NXOPEN_INTELLISENSE_SETUP.md) - NXOpen development setup

### Related Features
- [OPTIMIZATION_WORKFLOW.md](OPTIMIZATION_WORKFLOW.md) - Overall optimization pipeline
- [DEVELOPMENT_GUIDANCE.md](../DEVELOPMENT_GUIDANCE.md) - Development standards
- [NX_SOLVER_INTEGRATION.md](archive/NX_SOLVER_INTEGRATION.md) - NX Simcenter integration

---

**Author**: Antoine Letarte
**Date**: 2025-11-17
**Status**: ✅ Production Ready
**Version**: 1.0
