""" NX Journal: Intelligent Model Discovery ======================================== This journal scans a .sim file and reports: - All solutions (names, types, solver types) - All expressions in the model (potential design variables) - FEM properties (mesh info, materials) - Output file locations Usage: run_journal.exe discover_model.py -- sim_file.sim Output: JSON to stdout with all discovered information """ import sys import json import NXOpen import NXOpen.CAE def discover_model(sim_path): """Discover all information about an NX simulation model.""" result = { 'sim_file': sim_path, 'success': False, 'solutions': [], 'expressions': [], 'materials': [], 'mesh_info': {}, 'linked_parts': [], 'errors': [] } try: theSession = NXOpen.Session.GetSession() # Set load options to load from the directory containing the sim file import os sim_dir = os.path.dirname(os.path.abspath(sim_path)) sim_name = os.path.basename(sim_path) loadOptions = theSession.Parts.LoadOptions loadOptions.ComponentLoadMethod = NXOpen.LoadOptions.LoadComponents.AllComponents loadOptions.SetSearchDirectories([sim_dir]) print(f"[DISCOVER] Opening simulation: {sim_path}", file=sys.stderr) # Open the simulation file basePart, partLoadStatus = theSession.Parts.OpenActiveDisplay(sim_path, NXOpen.DisplayPartOption.ReplaceExisting) partLoadStatus.Dispose() if basePart is None: result['errors'].append("Failed to open simulation file") print(json.dumps(result)) return # Get the simulation part simPart = theSession.Parts.BaseWork # ============================================================ # DISCOVER SOLUTIONS # ============================================================ print(f"[DISCOVER] Scanning for solutions...", file=sys.stderr) try: simSimulation = simPart.Simulation # Try to find solutions by iterating through common naming patterns solution_patterns = [ "Solution 1", "Solution 2", "Solution 3", "Solution 4", "Solution 5", "solution_1", "solution_2", "solution_3", "Static", "Modal", "Buckling", "Frequency Response", "Thermal", "Heat Transfer", "Transient", ] found_solutions = [] for pattern in solution_patterns: try: sol_obj_name = f"Solution[{pattern}]" sol = simSimulation.FindObject(sol_obj_name) if sol: sol_info = { 'name': pattern, 'object_name': sol_obj_name, 'type': str(type(sol).__name__), } # Try to get solver type try: sol_info['solver'] = str(sol.SolutionType) except: pass # Try to get analysis type try: sol_info['analysis_type'] = str(sol.AnalysisType) if hasattr(sol, 'AnalysisType') else 'Unknown' except: pass found_solutions.append(sol_info) print(f"[DISCOVER] Found solution: {pattern}", file=sys.stderr) except: pass # Also try iterating by number (Solution 1, Solution 2, etc.) for i in range(1, 20): pattern = f"Solution {i}" if any(s['name'] == pattern for s in found_solutions): continue try: sol_obj_name = f"Solution[{pattern}]" sol = simSimulation.FindObject(sol_obj_name) if sol: sol_info = { 'name': pattern, 'object_name': sol_obj_name, 'type': str(type(sol).__name__), } found_solutions.append(sol_info) print(f"[DISCOVER] Found solution: {pattern}", file=sys.stderr) except: break # Stop when we hit missing numbers result['solutions'] = found_solutions print(f"[DISCOVER] Found {len(found_solutions)} solution(s)", file=sys.stderr) except Exception as e: result['errors'].append(f"Error scanning solutions: {str(e)}") # ============================================================ # DISCOVER EXPRESSIONS (Potential Design Variables) # ============================================================ print(f"[DISCOVER] Scanning for expressions...", file=sys.stderr) try: # Find all linked parts (geometry parts) linked_parts = [] # Check for common part naming patterns based on sim file name sim_base = os.path.splitext(sim_name)[0] # e.g., "Bracket_sim1" # Extract the base model name (remove _sim1, _fem1, etc.) import re base_match = re.match(r'(.+?)(?:_sim\d*|_fem\d*)?$', sim_base, re.IGNORECASE) if base_match: model_base = base_match.group(1) # e.g., "Bracket" else: model_base = sim_base print(f"[DISCOVER] Model base name: {model_base}", file=sys.stderr) # Look for the geometry part for part in theSession.Parts: part_name = part.Name if hasattr(part, 'Name') else str(part) # Check if this is a geometry part (not sim or fem) if model_base.lower() in part_name.lower(): is_sim = '_sim' in part_name.lower() or part_name.lower().endswith('.sim') is_fem = '_fem' in part_name.lower() or part_name.lower().endswith('.fem') if not is_sim and not is_fem: linked_parts.append(part_name) print(f"[DISCOVER] Found geometry part: {part_name}", file=sys.stderr) # Get expressions from this part try: for expr in part.Expressions: expr_info = { 'name': expr.Name, 'value': expr.Value if hasattr(expr, 'Value') else None, 'formula': expr.RightHandSide if hasattr(expr, 'RightHandSide') else None, 'type': str(expr.Type) if hasattr(expr, 'Type') else 'Unknown', 'units': str(expr.Units) if hasattr(expr, 'Units') else None, 'part': part_name } # Filter out system expressions if not expr.Name.startswith('p') or not expr.Name[1:].isdigit(): result['expressions'].append(expr_info) except Exception as e: result['errors'].append(f"Error reading expressions from {part_name}: {str(e)}") result['linked_parts'] = linked_parts print(f"[DISCOVER] Found {len(result['expressions'])} expression(s)", file=sys.stderr) except Exception as e: result['errors'].append(f"Error scanning expressions: {str(e)}") # ============================================================ # DISCOVER MESH INFO # ============================================================ print(f"[DISCOVER] Scanning for mesh info...", file=sys.stderr) try: # Look for FEM parts for part in theSession.Parts: part_name = part.Name if hasattr(part, 'Name') else str(part) if '_fem' in part_name.lower() or part_name.lower().endswith('.fem'): print(f"[DISCOVER] Found FEM part: {part_name}", file=sys.stderr) mesh_info = { 'fem_part': part_name, 'element_count': 0, 'node_count': 0 } # Try to get mesh statistics try: if hasattr(part, 'FEModel'): feModel = part.FEModel if feModel: # Get element count try: mesh_info['element_count'] = feModel.FeelementLabelMap.Size except: pass # Get node count try: mesh_info['node_count'] = feModel.FenodeLabelMap.Size except: pass except: pass result['mesh_info'] = mesh_info break except Exception as e: result['errors'].append(f"Error scanning mesh: {str(e)}") result['success'] = True print(f"[DISCOVER] Discovery complete!", file=sys.stderr) except Exception as e: result['errors'].append(f"Fatal error: {str(e)}") import traceback traceback.print_exc(file=sys.stderr) # Output JSON result to stdout print(json.dumps(result, indent=2)) return result if __name__ == '__main__': if len(sys.argv) < 2: print(json.dumps({'success': False, 'error': 'No sim file specified'})) sys.exit(1) sim_path = sys.argv[1] discover_model(sim_path)