feat: Add MLP surrogate with Turbo Mode for 100x faster optimization
Neural Acceleration (MLP Surrogate): - Add run_nn_optimization.py with hybrid FEA/NN workflow - MLP architecture: 4-layer (64->128->128->64) with BatchNorm/Dropout - Three workflow modes: - --all: Sequential export->train->optimize->validate - --hybrid-loop: Iterative Train->NN->Validate->Retrain cycle - --turbo: Aggressive single-best validation (RECOMMENDED) - Turbo mode: 5000 NN trials + 50 FEA validations in ~12 minutes - Separate nn_study.db to avoid overloading dashboard Performance Results (bracket_pareto_3obj study): - NN prediction errors: mass 1-5%, stress 1-4%, stiffness 5-15% - Found minimum mass designs at boundary (angle~30deg, thick~30mm) - 100x speedup vs pure FEA exploration Protocol Operating System: - Add .claude/skills/ with Bootstrap, Cheatsheet, Context Loader - Add docs/protocols/ with operations (OP_01-06) and system (SYS_10-14) - Update SYS_14_NEURAL_ACCELERATION.md with MLP Turbo Mode docs NX Automation: - Add optimization_engine/hooks/ for NX CAD/CAE automation - Add study_wizard.py for guided study creation - Fix FEM mesh update: load idealized part before UpdateFemodel() New Study: - bracket_pareto_3obj: 3-objective Pareto (mass, stress, stiffness) - 167 FEA trials + 5000 NN trials completed - Demonstrates full hybrid workflow 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
344
optimization_engine/hooks/README.md
Normal file
344
optimization_engine/hooks/README.md
Normal file
@@ -0,0 +1,344 @@
|
||||
# Atomizer NX Open Hooks
|
||||
|
||||
Direct Python hooks for NX CAD/CAE operations via NX Open API.
|
||||
|
||||
## Overview
|
||||
|
||||
This module provides a clean Python API for manipulating NX parts programmatically. Each hook executes NX journals via `run_journal.exe` and returns structured JSON results.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
hooks/
|
||||
├── __init__.py # Main entry point
|
||||
├── README.md # This file
|
||||
├── test_hooks.py # Test script
|
||||
└── nx_cad/ # CAD manipulation hooks
|
||||
├── __init__.py
|
||||
├── part_manager.py # Open/Close/Save parts
|
||||
├── expression_manager.py # Get/Set expressions
|
||||
├── geometry_query.py # Mass properties, bodies
|
||||
└── feature_manager.py # Suppress/Unsuppress features
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- **NX Installation**: Siemens NX 2506 or compatible version
|
||||
- **Environment Variable**: `NX_BIN_PATH` (defaults to `C:\Program Files\Siemens\NX2506\NXBIN`)
|
||||
- **Python**: 3.8+ with `atomizer` conda environment
|
||||
|
||||
## Quick Start
|
||||
|
||||
```python
|
||||
from optimization_engine.hooks.nx_cad import (
|
||||
part_manager,
|
||||
expression_manager,
|
||||
geometry_query,
|
||||
feature_manager,
|
||||
)
|
||||
|
||||
# Path to your NX part
|
||||
part_path = "C:/path/to/model.prt"
|
||||
|
||||
# Get all expressions
|
||||
result = expression_manager.get_expressions(part_path)
|
||||
if result["success"]:
|
||||
for name, expr in result["data"]["expressions"].items():
|
||||
print(f"{name} = {expr['value']} {expr['units']}")
|
||||
|
||||
# Get mass properties
|
||||
result = geometry_query.get_mass_properties(part_path)
|
||||
if result["success"]:
|
||||
print(f"Mass: {result['data']['mass']:.4f} kg")
|
||||
print(f"Material: {result['data']['material']}")
|
||||
```
|
||||
|
||||
## Module Reference
|
||||
|
||||
### part_manager
|
||||
|
||||
Manage NX part files (open, close, save).
|
||||
|
||||
| Function | Description | Returns |
|
||||
|----------|-------------|---------|
|
||||
| `open_part(path)` | Open an NX part file | Part info dict |
|
||||
| `close_part(path)` | Close an open part | Success status |
|
||||
| `save_part(path)` | Save a part | Success status |
|
||||
| `save_part_as(path, new_path)` | Save with new name | Success status |
|
||||
| `get_part_info(path)` | Get part metadata | Part info dict |
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
from optimization_engine.hooks.nx_cad import part_manager
|
||||
|
||||
# Open a part
|
||||
result = part_manager.open_part("C:/models/bracket.prt")
|
||||
if result["success"]:
|
||||
print(f"Opened: {result['data']['part_name']}")
|
||||
print(f"Modified: {result['data']['is_modified']}")
|
||||
|
||||
# Save the part
|
||||
result = part_manager.save_part("C:/models/bracket.prt")
|
||||
|
||||
# Save as new file
|
||||
result = part_manager.save_part_as(
|
||||
"C:/models/bracket.prt",
|
||||
"C:/models/bracket_v2.prt"
|
||||
)
|
||||
```
|
||||
|
||||
### expression_manager
|
||||
|
||||
Get and set NX expressions (design parameters).
|
||||
|
||||
| Function | Description | Returns |
|
||||
|----------|-------------|---------|
|
||||
| `get_expressions(path)` | Get all expressions | Dict of expressions |
|
||||
| `get_expression(path, name)` | Get single expression | Expression dict |
|
||||
| `set_expression(path, name, value)` | Set single expression | Success status |
|
||||
| `set_expressions(path, dict)` | Set multiple expressions | Success status |
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
from optimization_engine.hooks.nx_cad import expression_manager
|
||||
|
||||
part = "C:/models/bracket.prt"
|
||||
|
||||
# Get all expressions
|
||||
result = expression_manager.get_expressions(part)
|
||||
if result["success"]:
|
||||
for name, expr in result["data"]["expressions"].items():
|
||||
print(f"{name} = {expr['value']} {expr['units']}")
|
||||
# Example output:
|
||||
# thickness = 5.0 MilliMeter
|
||||
# width = 50.0 MilliMeter
|
||||
|
||||
# Get specific expression
|
||||
result = expression_manager.get_expression(part, "thickness")
|
||||
if result["success"]:
|
||||
print(f"Thickness: {result['data']['value']} {result['data']['units']}")
|
||||
|
||||
# Set single expression
|
||||
result = expression_manager.set_expression(part, "thickness", 7.5)
|
||||
|
||||
# Set multiple expressions (batch update)
|
||||
result = expression_manager.set_expressions(part, {
|
||||
"thickness": 7.5,
|
||||
"width": 60.0,
|
||||
"height": 100.0
|
||||
})
|
||||
if result["success"]:
|
||||
print(f"Updated {result['data']['update_count']} expressions")
|
||||
```
|
||||
|
||||
### geometry_query
|
||||
|
||||
Query geometric properties (mass, volume, bodies).
|
||||
|
||||
| Function | Description | Returns |
|
||||
|----------|-------------|---------|
|
||||
| `get_mass_properties(path)` | Get mass, volume, area, centroid | Properties dict |
|
||||
| `get_bodies(path)` | Get body count and types | Bodies dict |
|
||||
| `get_volume(path)` | Get total volume | Volume float |
|
||||
| `get_surface_area(path)` | Get total surface area | Area float |
|
||||
| `get_material(path)` | Get material name | Material string |
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
from optimization_engine.hooks.nx_cad import geometry_query
|
||||
|
||||
part = "C:/models/bracket.prt"
|
||||
|
||||
# Get mass properties
|
||||
result = geometry_query.get_mass_properties(part)
|
||||
if result["success"]:
|
||||
data = result["data"]
|
||||
print(f"Mass: {data['mass']:.6f} {data['mass_unit']}")
|
||||
print(f"Volume: {data['volume']:.2f} {data['volume_unit']}")
|
||||
print(f"Surface Area: {data['surface_area']:.2f} {data['area_unit']}")
|
||||
print(f"Centroid: ({data['centroid']['x']:.2f}, "
|
||||
f"{data['centroid']['y']:.2f}, {data['centroid']['z']:.2f}) mm")
|
||||
print(f"Material: {data['material']}")
|
||||
# Example output:
|
||||
# Mass: 0.109838 kg
|
||||
# Volume: 39311.99 mm^3
|
||||
# Surface Area: 10876.71 mm^2
|
||||
# Centroid: (0.00, 42.30, 39.58) mm
|
||||
# Material: Aluminum_2014
|
||||
|
||||
# Get body information
|
||||
result = geometry_query.get_bodies(part)
|
||||
if result["success"]:
|
||||
print(f"Total bodies: {result['data']['count']}")
|
||||
print(f"Solid bodies: {result['data']['solid_count']}")
|
||||
```
|
||||
|
||||
### feature_manager
|
||||
|
||||
Suppress and unsuppress features for design exploration.
|
||||
|
||||
| Function | Description | Returns |
|
||||
|----------|-------------|---------|
|
||||
| `get_features(path)` | List all features | Features list |
|
||||
| `get_feature_status(path, name)` | Check if suppressed | Boolean |
|
||||
| `suppress_feature(path, name)` | Suppress a feature | Success status |
|
||||
| `unsuppress_feature(path, name)` | Unsuppress a feature | Success status |
|
||||
| `suppress_features(path, names)` | Suppress multiple | Success status |
|
||||
| `unsuppress_features(path, names)` | Unsuppress multiple | Success status |
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
from optimization_engine.hooks.nx_cad import feature_manager
|
||||
|
||||
part = "C:/models/bracket.prt"
|
||||
|
||||
# List all features
|
||||
result = feature_manager.get_features(part)
|
||||
if result["success"]:
|
||||
print(f"Found {result['data']['count']} features")
|
||||
for feat in result["data"]["features"]:
|
||||
status = "suppressed" if feat["is_suppressed"] else "active"
|
||||
print(f" {feat['name']} ({feat['type']}): {status}")
|
||||
|
||||
# Suppress a feature
|
||||
result = feature_manager.suppress_feature(part, "FILLET(3)")
|
||||
if result["success"]:
|
||||
print("Feature suppressed!")
|
||||
|
||||
# Unsuppress multiple features
|
||||
result = feature_manager.unsuppress_features(part, ["FILLET(3)", "CHAMFER(1)"])
|
||||
```
|
||||
|
||||
## Return Format
|
||||
|
||||
All hook functions return a consistent dictionary structure:
|
||||
|
||||
```python
|
||||
{
|
||||
"success": bool, # True if operation succeeded
|
||||
"error": str | None, # Error message if failed
|
||||
"data": dict # Operation-specific results
|
||||
}
|
||||
```
|
||||
|
||||
**Error Handling:**
|
||||
```python
|
||||
result = expression_manager.get_expressions(part_path)
|
||||
|
||||
if not result["success"]:
|
||||
print(f"Error: {result['error']}")
|
||||
# Handle error...
|
||||
else:
|
||||
# Process result["data"]...
|
||||
```
|
||||
|
||||
## NX Open API Reference
|
||||
|
||||
These hooks use the following NX Open APIs (verified via Siemens MCP documentation):
|
||||
|
||||
| Hook | NX Open API |
|
||||
|------|-------------|
|
||||
| Open part | `Session.Parts.OpenActiveDisplay()` |
|
||||
| Close part | `Part.Close()` |
|
||||
| Save part | `Part.Save()`, `Part.SaveAs()` |
|
||||
| Get expressions | `Part.Expressions` collection |
|
||||
| Set expression | `ExpressionCollection.Edit()` |
|
||||
| Update model | `Session.UpdateManager.DoUpdate()` |
|
||||
| Mass properties | `MeasureManager.NewMassProperties()` |
|
||||
| Get bodies | `Part.Bodies` collection |
|
||||
| Suppress feature | `Feature.Suppress()` |
|
||||
| Unsuppress feature | `Feature.Unsuppress()` |
|
||||
|
||||
## Configuration
|
||||
|
||||
### NX Path
|
||||
|
||||
Set the NX installation path via environment variable:
|
||||
|
||||
```bash
|
||||
# Windows
|
||||
set NX_BIN_PATH=C:\Program Files\Siemens\NX2506\NXBIN
|
||||
|
||||
# Or in Python before importing
|
||||
import os
|
||||
os.environ["NX_BIN_PATH"] = r"C:\Program Files\Siemens\NX2506\NXBIN"
|
||||
```
|
||||
|
||||
### Timeout
|
||||
|
||||
Journal execution has a default 2-minute timeout. For large parts, you may need to increase this in the hook source code.
|
||||
|
||||
## Integration with Atomizer
|
||||
|
||||
These hooks are designed to integrate with Atomizer's optimization workflow:
|
||||
|
||||
```python
|
||||
# In run_optimization.py or custom extractor
|
||||
|
||||
from optimization_engine.hooks.nx_cad import expression_manager, geometry_query
|
||||
|
||||
def evaluate_design(part_path: str, params: dict) -> dict:
|
||||
"""Evaluate a design point by updating NX model and extracting metrics."""
|
||||
|
||||
# 1. Update design parameters
|
||||
result = expression_manager.set_expressions(part_path, params)
|
||||
if not result["success"]:
|
||||
raise RuntimeError(f"Failed to set expressions: {result['error']}")
|
||||
|
||||
# 2. Extract mass (objective)
|
||||
result = geometry_query.get_mass_properties(part_path)
|
||||
if not result["success"]:
|
||||
raise RuntimeError(f"Failed to get mass: {result['error']}")
|
||||
|
||||
return {
|
||||
"mass_kg": result["data"]["mass"],
|
||||
"volume_mm3": result["data"]["volume"],
|
||||
"material": result["data"]["material"]
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run the test script to verify hooks work with your NX installation:
|
||||
|
||||
```bash
|
||||
# Activate atomizer environment
|
||||
conda activate atomizer
|
||||
|
||||
# Run tests with default bracket part
|
||||
python -m optimization_engine.hooks.test_hooks
|
||||
|
||||
# Or specify a custom part
|
||||
python -m optimization_engine.hooks.test_hooks "C:/path/to/your/part.prt"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Part file not found"
|
||||
- Verify the path exists and is accessible
|
||||
- Use forward slashes or raw strings: `r"C:\path\to\file.prt"`
|
||||
|
||||
### "Failed to open part"
|
||||
- Ensure NX license is available
|
||||
- Check `NX_BIN_PATH` environment variable
|
||||
- Verify NX version compatibility
|
||||
|
||||
### "Expression not found"
|
||||
- Expression names are case-sensitive
|
||||
- Use `get_expressions()` to list available names
|
||||
|
||||
### Journal execution timeout
|
||||
- Large parts may need longer timeout
|
||||
- Check NX is not displaying modal dialogs
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Changes |
|
||||
|---------|------|---------|
|
||||
| 1.0.0 | 2025-12-06 | Initial release with CAD hooks |
|
||||
|
||||
## See Also
|
||||
|
||||
- [NX_OPEN_AUTOMATION_ROADMAP.md](../../docs/plans/NX_OPEN_AUTOMATION_ROADMAP.md) - Development roadmap
|
||||
- [SYS_12_EXTRACTOR_LIBRARY.md](../../docs/protocols/system/SYS_12_EXTRACTOR_LIBRARY.md) - Extractor catalog
|
||||
- [NXJournaling.com](https://nxjournaling.com/) - NX Open examples
|
||||
Reference in New Issue
Block a user