# 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
