""" NX Expression Manager Hook =========================== Provides Python functions to get and set NX expressions (parameters). API Reference (verified via Siemens MCP docs): - Part.Expressions() -> ExpressionCollection - ExpressionCollection.Edit(expression, value) - Expression.Name, Expression.Value, Expression.RightHandSide - Expression.Units.Name Usage: from optimization_engine.hooks.nx_cad import expression_manager # Get all expressions result = expression_manager.get_expressions("C:/path/to/part.prt") # Get specific expression result = expression_manager.get_expression("C:/path/to/part.prt", "thickness") # Set expression value result = expression_manager.set_expression("C:/path/to/part.prt", "thickness", 5.0) # Set multiple expressions result = expression_manager.set_expressions("C:/path/to/part.prt", { "thickness": 5.0, "width": 10.0 }) """ import os import json import subprocess import tempfile from pathlib import Path from typing import Optional, Dict, Any, List, Tuple, Union # Import NX path from centralized config try: from config import NX_BIN_DIR NX_BIN_PATH = str(NX_BIN_DIR) except ImportError: NX_BIN_PATH = os.environ.get( "NX_BIN_PATH", r"C:\Program Files\Siemens\DesigncenterNX2512\NXBIN" ) # Journal template for expression operations EXPRESSION_OPERATIONS_JOURNAL = ''' # NX Open Python Journal - Expression Operations # Auto-generated by Atomizer hooks import NXOpen import NXOpen.UF import json import sys import os def main(): """Execute expression operation based on command arguments.""" # Get the NX session session = NXOpen.Session.GetSession() # Parse arguments: operation, part_path, output_json, [extra_args...] args = sys.argv[1:] if len(sys.argv) > 1 else [] if len(args) < 3: raise ValueError("Usage: script.py [args...]") operation = args[0] part_path = args[1] output_json = args[2] extra_args = args[3:] if len(args) > 3 else [] result = {"success": False, "error": None, "data": {}} try: # Ensure part is open part = ensure_part_open(session, part_path) if part is None: result["error"] = f"Failed to open part: {part_path}" elif operation == "get_all": result = get_all_expressions(part) elif operation == "get": expr_name = extra_args[0] if extra_args else None result = get_expression(part, expr_name) elif operation == "set": expr_name = extra_args[0] if len(extra_args) > 0 else None expr_value = extra_args[1] if len(extra_args) > 1 else None result = set_expression(session, part, expr_name, expr_value) elif operation == "set_multiple": # Extra args is a JSON string with name:value pairs expr_dict = json.loads(extra_args[0]) if extra_args else {} result = set_multiple_expressions(session, part, expr_dict) else: result["error"] = f"Unknown operation: {operation}" except Exception as e: import traceback result["error"] = str(e) result["traceback"] = traceback.format_exc() # Write result to output JSON with open(output_json, 'w') as f: json.dump(result, f, indent=2) return result def ensure_part_open(session, part_path): """Ensure the part is open and return it.""" # Check if already open part_path_normalized = os.path.normpath(part_path).lower() for part in session.Parts: if os.path.normpath(part.FullPath).lower() == part_path_normalized: return part # Need to open it if not os.path.exists(part_path): return None try: # Set load options for the working directory working_dir = os.path.dirname(part_path) session.Parts.LoadOptions.ComponentLoadMethod = NXOpen.LoadOptions.LoadMethod.FromDirectory session.Parts.LoadOptions.SetSearchDirectories([working_dir], [True]) # Use OpenActiveDisplay instead of OpenBase for better compatibility part, load_status = session.Parts.OpenActiveDisplay( part_path, NXOpen.DisplayPartOption.AllowAdditional ) load_status.Dispose() return part except: return None def get_all_expressions(part): """Get all expressions from a part. NX Open API: Part.Expressions() -> ExpressionCollection """ result = {"success": False, "error": None, "data": {}} try: expressions = {} for expr in part.Expressions: try: expr_data = { "name": expr.Name, "value": expr.Value, "rhs": expr.RightHandSide, "units": expr.Units.Name if expr.Units else None, "type": expr.Type.ToString() if hasattr(expr.Type, 'ToString') else str(expr.Type), } expressions[expr.Name] = expr_data except: # Skip expressions that can't be read pass result["success"] = True result["data"] = { "count": len(expressions), "expressions": expressions } except Exception as e: result["error"] = str(e) return result def get_expression(part, expr_name): """Get a specific expression by name. NX Open API: ExpressionCollection iteration, Expression properties """ result = {"success": False, "error": None, "data": {}} if not expr_name: result["error"] = "Expression name is required" return result try: # Find the expression by name found_expr = None for expr in part.Expressions: if expr.Name == expr_name: found_expr = expr break if found_expr is None: result["error"] = f"Expression not found: {expr_name}" return result result["success"] = True result["data"] = { "name": found_expr.Name, "value": found_expr.Value, "rhs": found_expr.RightHandSide, "units": found_expr.Units.Name if found_expr.Units else None, "type": found_expr.Type.ToString() if hasattr(found_expr.Type, 'ToString') else str(found_expr.Type), } except Exception as e: result["error"] = str(e) return result def set_expression(session, part, expr_name, expr_value): """Set an expression value. NX Open API: ExpressionCollection.Edit(expression, new_rhs) """ result = {"success": False, "error": None, "data": {}} if not expr_name: result["error"] = "Expression name is required" return result if expr_value is None: result["error"] = "Expression value is required" return result try: # Find the expression found_expr = None for expr in part.Expressions: if expr.Name == expr_name: found_expr = expr break if found_expr is None: result["error"] = f"Expression not found: {expr_name}" return result # Set undo mark mark_id = session.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Edit Expression") # Edit the expression # The value should be a string for the RHS new_rhs = str(expr_value) part.Expressions.Edit(found_expr, new_rhs) # Update the model session.UpdateManager.DoUpdate(mark_id) result["success"] = True result["data"] = { "name": expr_name, "old_value": found_expr.Value, # Note: this might be the new value after edit "new_rhs": new_rhs, } except Exception as e: result["error"] = str(e) return result def set_multiple_expressions(session, part, expr_dict): """Set multiple expressions at once. Args: session: NX session part: NX part expr_dict: Dict of expression name -> value """ result = {"success": False, "error": None, "data": {}} if not expr_dict: result["error"] = "No expressions provided" return result try: # Set undo mark for all changes mark_id = session.SetUndoMark(NXOpen.Session.MarkVisibility.Visible, "Edit Multiple Expressions") updated = [] errors = [] for expr_name, expr_value in expr_dict.items(): # Find the expression found_expr = None for expr in part.Expressions: if expr.Name == expr_name: found_expr = expr break if found_expr is None: errors.append(f"Expression not found: {expr_name}") continue try: # Edit the expression new_rhs = str(expr_value) part.Expressions.Edit(found_expr, new_rhs) updated.append({"name": expr_name, "value": expr_value}) except Exception as e: errors.append(f"Failed to set {expr_name}: {str(e)}") # Update the model session.UpdateManager.DoUpdate(mark_id) result["success"] = len(errors) == 0 result["data"] = { "updated": updated, "errors": errors, "update_count": len(updated), "error_count": len(errors), } except Exception as e: result["error"] = str(e) return result if __name__ == "__main__": main() ''' def _get_run_journal_exe() -> str: """Get the path to run_journal.exe.""" return os.path.join(NX_BIN_PATH, "run_journal.exe") def _run_journal(journal_path: str, args: list) -> Tuple[bool, str]: """Run an NX journal with arguments. Returns: Tuple of (success, output_or_error) """ run_journal = _get_run_journal_exe() if not os.path.exists(run_journal): return False, f"run_journal.exe not found at {run_journal}" cmd = [run_journal, journal_path, "-args"] + args try: result = subprocess.run( cmd, capture_output=True, text=True, timeout=120 # 2 minute timeout ) if result.returncode != 0: return False, f"Journal execution failed: {result.stderr}" return True, result.stdout except subprocess.TimeoutExpired: return False, "Journal execution timed out" except Exception as e: return False, str(e) def _execute_expression_operation( operation: str, part_path: str, extra_args: list = None ) -> Dict[str, Any]: """Execute an expression operation via NX journal. Args: operation: The operation to perform (get_all, get, set, set_multiple) part_path: Path to the part file extra_args: Additional arguments for the operation Returns: Dict with operation result """ # Create temporary journal file with tempfile.NamedTemporaryFile( mode='w', suffix='.py', delete=False ) as journal_file: journal_file.write(EXPRESSION_OPERATIONS_JOURNAL) journal_path = journal_file.name # Create temporary output file output_file = tempfile.NamedTemporaryFile( mode='w', suffix='.json', delete=False ).name try: # Build arguments args = [operation, part_path, output_file] if extra_args: args.extend(extra_args) # Run the journal success, output = _run_journal(journal_path, args) if not success: return {"success": False, "error": output, "data": {}} # Read the result if os.path.exists(output_file): with open(output_file, 'r') as f: return json.load(f) else: return {"success": False, "error": "Output file not created", "data": {}} finally: # Cleanup temporary files if os.path.exists(journal_path): os.unlink(journal_path) if os.path.exists(output_file): os.unlink(output_file) # ============================================================================= # Public API # ============================================================================= def get_expressions(part_path: str) -> Dict[str, Any]: """Get all expressions from an NX part. Args: part_path: Full path to the .prt file Returns: Dict with keys: - success: bool - error: Optional error message - data: Dict with count and expressions dict Each expression has: name, value, rhs, units, type Example: >>> result = get_expressions("C:/models/bracket.prt") >>> if result["success"]: ... for name, expr in result["data"]["expressions"].items(): ... print(f"{name} = {expr['value']} {expr['units']}") """ part_path = os.path.abspath(part_path) if not os.path.exists(part_path): return { "success": False, "error": f"Part file not found: {part_path}", "data": {} } return _execute_expression_operation("get_all", part_path) def get_expression(part_path: str, expression_name: str) -> Dict[str, Any]: """Get a specific expression from an NX part. Args: part_path: Full path to the .prt file expression_name: Name of the expression Returns: Dict with keys: - success: bool - error: Optional error message - data: Dict with name, value, rhs, units, type Example: >>> result = get_expression("C:/models/bracket.prt", "thickness") >>> if result["success"]: ... print(f"thickness = {result['data']['value']}") """ part_path = os.path.abspath(part_path) if not os.path.exists(part_path): return { "success": False, "error": f"Part file not found: {part_path}", "data": {} } return _execute_expression_operation("get", part_path, [expression_name]) def set_expression( part_path: str, expression_name: str, value: Union[float, int, str] ) -> Dict[str, Any]: """Set an expression value in an NX part. Args: part_path: Full path to the .prt file expression_name: Name of the expression value: New value (will be converted to string for RHS) Returns: Dict with keys: - success: bool - error: Optional error message - data: Dict with name, old_value, new_rhs Example: >>> result = set_expression("C:/models/bracket.prt", "thickness", 5.0) >>> if result["success"]: ... print("Expression updated!") """ part_path = os.path.abspath(part_path) if not os.path.exists(part_path): return { "success": False, "error": f"Part file not found: {part_path}", "data": {} } return _execute_expression_operation( "set", part_path, [expression_name, str(value)] ) def set_expressions( part_path: str, expressions: Dict[str, Union[float, int, str]] ) -> Dict[str, Any]: """Set multiple expressions in an NX part. Args: part_path: Full path to the .prt file expressions: Dict mapping expression names to values Returns: Dict with keys: - success: bool - error: Optional error message - data: Dict with updated list, errors list, counts Example: >>> result = set_expressions("C:/models/bracket.prt", { ... "thickness": 5.0, ... "width": 10.0, ... "height": 15.0 ... }) >>> if result["success"]: ... print(f"Updated {result['data']['update_count']} expressions") """ part_path = os.path.abspath(part_path) if not os.path.exists(part_path): return { "success": False, "error": f"Part file not found: {part_path}", "data": {} } # Convert expressions dict to JSON string expr_json = json.dumps(expressions) return _execute_expression_operation( "set_multiple", part_path, [expr_json] )