feat: Add SIM file introspection journal and enhanced file-type specific UI
- Create introspect_sim.py NX journal to extract solutions, BCs from SIM files - Update introspect_sim_file() to optionally call NX journal for full introspection - Add FEM mesh section (nodes, elements, materials, properties) to IntrospectionPanel - Add SIM solutions and boundary conditions sections to IntrospectionPanel - Show introspection method and NX errors in panel
This commit is contained in:
380
nx_journals/introspect_sim.py
Normal file
380
nx_journals/introspect_sim.py
Normal file
@@ -0,0 +1,380 @@
|
||||
"""
|
||||
NX Journal: SIM File Introspection Tool
|
||||
=========================================
|
||||
|
||||
This journal performs deep introspection of an NX .sim file and extracts:
|
||||
- Solutions (name, type, solver)
|
||||
- Boundary conditions (SPCs, loads, etc.)
|
||||
- Subcases
|
||||
- Linked FEM files
|
||||
- Solution properties
|
||||
|
||||
Usage:
|
||||
run_journal.exe introspect_sim.py <sim_file_path> [output_dir]
|
||||
|
||||
Output:
|
||||
_introspection_sim.json - JSON with all extracted data
|
||||
|
||||
Author: Atomizer
|
||||
Created: 2026-01-20
|
||||
Version: 1.0
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import NXOpen
|
||||
import NXOpen.CAE
|
||||
|
||||
|
||||
def get_solutions(simSimulation):
|
||||
"""Extract all solutions from the simulation."""
|
||||
solutions = []
|
||||
|
||||
try:
|
||||
# Iterate through all solutions in the simulation
|
||||
# Solutions are accessed via FindObject with pattern "Solution[name]"
|
||||
# But we can also iterate if the simulation has a solutions collection
|
||||
|
||||
# Try to get solution info by iterating through known solution names
|
||||
# Common patterns: "Solution 1", "Solution 2", etc.
|
||||
for i in range(1, 20): # Check up to 20 solutions
|
||||
sol_name = f"Solution {i}"
|
||||
try:
|
||||
sol = simSimulation.FindObject(f"Solution[{sol_name}]")
|
||||
if sol:
|
||||
sol_info = {"name": sol_name, "type": str(type(sol).__name__), "properties": {}}
|
||||
|
||||
# Try to get common properties
|
||||
try:
|
||||
sol_info["properties"]["solver_type"] = (
|
||||
str(sol.SolverType) if hasattr(sol, "SolverType") else None
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
try:
|
||||
sol_info["properties"]["analysis_type"] = (
|
||||
str(sol.AnalysisType) if hasattr(sol, "AnalysisType") else None
|
||||
)
|
||||
except:
|
||||
pass
|
||||
|
||||
solutions.append(sol_info)
|
||||
except:
|
||||
# Solution not found, stop looking
|
||||
if i > 5: # Give a few tries in case there are gaps
|
||||
break
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
solutions.append({"error": str(e)})
|
||||
|
||||
return solutions
|
||||
|
||||
|
||||
def get_boundary_conditions(simSimulation, workPart):
|
||||
"""Extract boundary conditions from the simulation."""
|
||||
bcs = {"constraints": [], "loads": [], "total_count": 0}
|
||||
|
||||
try:
|
||||
# Try to access BC collections through the simulation object
|
||||
# BCs are typically stored in the simulation's children
|
||||
|
||||
# Look for constraint groups
|
||||
constraint_names = [
|
||||
"Constraint Group[1]",
|
||||
"Constraint Group[2]",
|
||||
"Constraint Group[3]",
|
||||
"SPC[1]",
|
||||
"SPC[2]",
|
||||
"SPC[3]",
|
||||
"Fixed Constraint[1]",
|
||||
"Fixed Constraint[2]",
|
||||
]
|
||||
|
||||
for name in constraint_names:
|
||||
try:
|
||||
obj = simSimulation.FindObject(name)
|
||||
if obj:
|
||||
bc_info = {
|
||||
"name": name,
|
||||
"type": str(type(obj).__name__),
|
||||
}
|
||||
bcs["constraints"].append(bc_info)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Look for load groups
|
||||
load_names = [
|
||||
"Load Group[1]",
|
||||
"Load Group[2]",
|
||||
"Load Group[3]",
|
||||
"Force[1]",
|
||||
"Force[2]",
|
||||
"Pressure[1]",
|
||||
"Pressure[2]",
|
||||
"Enforced Displacement[1]",
|
||||
"Enforced Displacement[2]",
|
||||
]
|
||||
|
||||
for name in load_names:
|
||||
try:
|
||||
obj = simSimulation.FindObject(name)
|
||||
if obj:
|
||||
load_info = {
|
||||
"name": name,
|
||||
"type": str(type(obj).__name__),
|
||||
}
|
||||
bcs["loads"].append(load_info)
|
||||
except:
|
||||
pass
|
||||
|
||||
bcs["total_count"] = len(bcs["constraints"]) + len(bcs["loads"])
|
||||
|
||||
except Exception as e:
|
||||
bcs["error"] = str(e)
|
||||
|
||||
return bcs
|
||||
|
||||
|
||||
def get_sim_part_info(workPart):
|
||||
"""Extract SIM part-level information."""
|
||||
info = {"name": None, "full_path": None, "type": None, "fem_parts": [], "component_count": 0}
|
||||
|
||||
try:
|
||||
info["name"] = workPart.Name
|
||||
info["full_path"] = workPart.FullPath if hasattr(workPart, "FullPath") else None
|
||||
info["type"] = str(type(workPart).__name__)
|
||||
|
||||
# Check for component assembly (assembly FEM)
|
||||
try:
|
||||
root = workPart.ComponentAssembly.RootComponent
|
||||
if root:
|
||||
info["is_assembly"] = True
|
||||
# Count components
|
||||
try:
|
||||
children = root.GetChildren()
|
||||
info["component_count"] = len(children) if children else 0
|
||||
|
||||
# Get component names
|
||||
components = []
|
||||
for child in children[:10]: # Limit to first 10
|
||||
try:
|
||||
comp_info = {
|
||||
"name": child.Name if hasattr(child, "Name") else str(child),
|
||||
"type": str(type(child).__name__),
|
||||
}
|
||||
components.append(comp_info)
|
||||
except:
|
||||
pass
|
||||
info["components"] = components
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
info["is_assembly"] = False
|
||||
|
||||
except Exception as e:
|
||||
info["error"] = str(e)
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def get_cae_session_info(theSession):
|
||||
"""Get CAE session information."""
|
||||
cae_info = {"active_sim_part": None, "active_fem_part": None, "solver_types": []}
|
||||
|
||||
try:
|
||||
# Get CAE session
|
||||
caeSession = theSession.GetExportedObject("NXOpen.CAE.CaeSession")
|
||||
if caeSession:
|
||||
cae_info["cae_session_exists"] = True
|
||||
except:
|
||||
cae_info["cae_session_exists"] = False
|
||||
|
||||
return cae_info
|
||||
|
||||
|
||||
def explore_simulation_tree(simSimulation, workPart):
|
||||
"""Explore the simulation tree structure."""
|
||||
tree_info = {"simulation_objects": [], "found_types": set()}
|
||||
|
||||
# Try to enumerate objects in the simulation
|
||||
# This is exploratory - we don't know the exact API
|
||||
|
||||
try:
|
||||
# Try common child object patterns
|
||||
patterns = [
|
||||
# Solutions
|
||||
"Solution[Solution 1]",
|
||||
"Solution[Solution 2]",
|
||||
"Solution[SOLUTION 1]",
|
||||
# Subcases
|
||||
"Subcase[Subcase 1]",
|
||||
"Subcase[Subcase - Static 1]",
|
||||
# Loads/BCs
|
||||
"LoadSet[LoadSet 1]",
|
||||
"ConstraintSet[ConstraintSet 1]",
|
||||
"BoundaryCondition[1]",
|
||||
# FEM reference
|
||||
"FemPart",
|
||||
"AssyFemPart",
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
try:
|
||||
obj = simSimulation.FindObject(pattern)
|
||||
if obj:
|
||||
obj_info = {"pattern": pattern, "type": str(type(obj).__name__), "found": True}
|
||||
tree_info["simulation_objects"].append(obj_info)
|
||||
tree_info["found_types"].add(str(type(obj).__name__))
|
||||
except:
|
||||
pass
|
||||
|
||||
tree_info["found_types"] = list(tree_info["found_types"])
|
||||
|
||||
except Exception as e:
|
||||
tree_info["error"] = str(e)
|
||||
|
||||
return tree_info
|
||||
|
||||
|
||||
def main(args):
|
||||
"""Main entry point for NX journal."""
|
||||
|
||||
if len(args) < 1:
|
||||
print("ERROR: No .sim file path provided")
|
||||
print("Usage: run_journal.exe introspect_sim.py <sim_file_path> [output_dir]")
|
||||
return False
|
||||
|
||||
sim_file_path = args[0]
|
||||
output_dir = args[1] if len(args) > 1 else os.path.dirname(sim_file_path)
|
||||
sim_filename = os.path.basename(sim_file_path)
|
||||
|
||||
print(f"[INTROSPECT-SIM] " + "=" * 60)
|
||||
print(f"[INTROSPECT-SIM] NX SIMULATION INTROSPECTION")
|
||||
print(f"[INTROSPECT-SIM] " + "=" * 60)
|
||||
print(f"[INTROSPECT-SIM] SIM File: {sim_filename}")
|
||||
print(f"[INTROSPECT-SIM] Output: {output_dir}")
|
||||
|
||||
results = {
|
||||
"sim_file": sim_filename,
|
||||
"sim_path": sim_file_path,
|
||||
"success": False,
|
||||
"error": None,
|
||||
"part_info": {},
|
||||
"solutions": [],
|
||||
"boundary_conditions": {},
|
||||
"tree_structure": {},
|
||||
"cae_info": {},
|
||||
}
|
||||
|
||||
try:
|
||||
theSession = NXOpen.Session.GetSession()
|
||||
|
||||
# Set load options
|
||||
working_dir = os.path.dirname(sim_file_path)
|
||||
theSession.Parts.LoadOptions.ComponentLoadMethod = (
|
||||
NXOpen.LoadOptions.LoadMethod.FromDirectory
|
||||
)
|
||||
theSession.Parts.LoadOptions.SetSearchDirectories([working_dir], [True])
|
||||
theSession.Parts.LoadOptions.ComponentsToLoad = NXOpen.LoadOptions.LoadComponents.All
|
||||
theSession.Parts.LoadOptions.PartLoadOption = NXOpen.LoadOptions.LoadOption.FullyLoad
|
||||
|
||||
# Open the SIM file
|
||||
print(f"[INTROSPECT-SIM] Opening SIM file...")
|
||||
basePart, partLoadStatus = theSession.Parts.OpenActiveDisplay(
|
||||
sim_file_path, NXOpen.DisplayPartOption.AllowAdditional
|
||||
)
|
||||
partLoadStatus.Dispose()
|
||||
|
||||
workPart = theSession.Parts.Work
|
||||
print(f"[INTROSPECT-SIM] Loaded: {workPart.Name}")
|
||||
|
||||
# Switch to SFEM application
|
||||
try:
|
||||
theSession.ApplicationSwitchImmediate("UG_APP_SFEM")
|
||||
print(f"[INTROSPECT-SIM] Switched to SFEM application")
|
||||
except Exception as e:
|
||||
print(f"[INTROSPECT-SIM] Note: Could not switch to SFEM: {e}")
|
||||
|
||||
# Get part info
|
||||
print(f"[INTROSPECT-SIM] Extracting part info...")
|
||||
results["part_info"] = get_sim_part_info(workPart)
|
||||
print(f"[INTROSPECT-SIM] Part: {results['part_info'].get('name')}")
|
||||
print(f"[INTROSPECT-SIM] Is Assembly: {results['part_info'].get('is_assembly', False)}")
|
||||
|
||||
# Get simulation object
|
||||
print(f"[INTROSPECT-SIM] Finding Simulation object...")
|
||||
try:
|
||||
simSimulation = workPart.FindObject("Simulation")
|
||||
print(f"[INTROSPECT-SIM] Found Simulation object: {type(simSimulation).__name__}")
|
||||
|
||||
# Get solutions
|
||||
print(f"[INTROSPECT-SIM] Extracting solutions...")
|
||||
results["solutions"] = get_solutions(simSimulation)
|
||||
print(f"[INTROSPECT-SIM] Found {len(results['solutions'])} solutions")
|
||||
|
||||
# Get boundary conditions
|
||||
print(f"[INTROSPECT-SIM] Extracting boundary conditions...")
|
||||
results["boundary_conditions"] = get_boundary_conditions(simSimulation, workPart)
|
||||
print(
|
||||
f"[INTROSPECT-SIM] Found {results['boundary_conditions'].get('total_count', 0)} BCs"
|
||||
)
|
||||
|
||||
# Explore tree structure
|
||||
print(f"[INTROSPECT-SIM] Exploring simulation tree...")
|
||||
results["tree_structure"] = explore_simulation_tree(simSimulation, workPart)
|
||||
print(
|
||||
f"[INTROSPECT-SIM] Found types: {results['tree_structure'].get('found_types', [])}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[INTROSPECT-SIM] WARNING: Could not find Simulation object: {e}")
|
||||
results["simulation_object_error"] = str(e)
|
||||
|
||||
# Get CAE session info
|
||||
print(f"[INTROSPECT-SIM] Getting CAE session info...")
|
||||
results["cae_info"] = get_cae_session_info(theSession)
|
||||
|
||||
# List all loaded parts
|
||||
print(f"[INTROSPECT-SIM] Listing loaded parts...")
|
||||
loaded_parts = []
|
||||
for part in theSession.Parts:
|
||||
try:
|
||||
loaded_parts.append(
|
||||
{
|
||||
"name": part.Name,
|
||||
"type": str(type(part).__name__),
|
||||
"leaf": part.Leaf if hasattr(part, "Leaf") else None,
|
||||
}
|
||||
)
|
||||
except:
|
||||
pass
|
||||
results["loaded_parts"] = loaded_parts
|
||||
print(f"[INTROSPECT-SIM] {len(loaded_parts)} parts loaded")
|
||||
|
||||
results["success"] = True
|
||||
print(f"[INTROSPECT-SIM] ")
|
||||
print(f"[INTROSPECT-SIM] INTROSPECTION COMPLETE!")
|
||||
print(f"[INTROSPECT-SIM] " + "=" * 60)
|
||||
|
||||
except Exception as e:
|
||||
results["error"] = str(e)
|
||||
results["success"] = False
|
||||
print(f"[INTROSPECT-SIM] FATAL ERROR: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
# Write results
|
||||
output_file = os.path.join(output_dir, "_introspection_sim.json")
|
||||
with open(output_file, "w") as f:
|
||||
json.dump(results, f, indent=2)
|
||||
print(f"[INTROSPECT-SIM] Results written to: {output_file}")
|
||||
|
||||
return results["success"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
||||
Reference in New Issue
Block a user