# 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