fix: Complete stress extraction fix for NX Nastran OP2 files
THREE critical fixes applied: 1. API Access Pattern - Support dotted attribute names (e.g., 'stress.chexa_stress') - Compatible with newer pyNastran versions (NX 2412.5) - Fallback to older API formats for compatibility 2. Correct Von Mises Index - Solid elements (CHEXA, CTETRA, CPENTA): index 9 - Shell elements (CQUAD4, CTRIA3): last column - Data structure: [oxx, oyy, ozz, txy, tyz, txz, o1, o2, o3, von_mises] 3. Units Conversion (CRITICAL) - NX Nastran outputs stress in kPa, not MPa - Apply conversion: kPa / 1000 = MPa - Example: 113094.73 kPa -> 113.09 MPa Test Results: - Before: 0.00 MPa (FAIL) - After: 113.09 MPa at element 83 (SUCCESS) Files modified: - optimization_engine/result_extractors/op2_extractor_example.py Test files added: - examples/test_stress_direct.py - examples/test_stress_fix.py - examples/debug_op2_stress.py - STRESS_EXTRACTION_FIXED.md - TESTING_STRESS_FIX.md
This commit is contained in:
@@ -71,7 +71,7 @@ def extract_max_stress(op2_path: Path, stress_type: str = 'von_mises') -> Dict[s
|
||||
"""
|
||||
from pyNastran.op2.op2 import OP2
|
||||
|
||||
op2 = OP2()
|
||||
op2 = OP2(debug=False)
|
||||
op2.read_op2(str(op2_path))
|
||||
|
||||
# Stress can be in different tables depending on element type
|
||||
@@ -81,6 +81,7 @@ def extract_max_stress(op2_path: Path, stress_type: str = 'von_mises') -> Dict[s
|
||||
'ctria3_stress',
|
||||
'ctetra_stress',
|
||||
'chexa_stress',
|
||||
'cpenta_stress',
|
||||
'cbar_stress',
|
||||
'cbeam_stress'
|
||||
]
|
||||
@@ -89,37 +90,64 @@ def extract_max_stress(op2_path: Path, stress_type: str = 'von_mises') -> Dict[s
|
||||
max_element_id = None
|
||||
max_element_type = None
|
||||
|
||||
# Try to get stress from different pyNastran API formats
|
||||
for table_name in stress_tables:
|
||||
if hasattr(op2, table_name):
|
||||
stress_table = None
|
||||
|
||||
# Try format 1: Attribute name with dot (e.g., 'stress.chexa_stress')
|
||||
# This is used in newer pyNastran versions
|
||||
dotted_name = f'stress.{table_name}'
|
||||
if hasattr(op2, dotted_name):
|
||||
stress_table = getattr(op2, dotted_name)
|
||||
# Try format 2: Nested attribute op2.stress.chexa_stress
|
||||
elif hasattr(op2, 'stress') and hasattr(op2.stress, table_name):
|
||||
stress_table = getattr(op2.stress, table_name)
|
||||
# Try format 3: Direct attribute op2.chexa_stress (older pyNastran)
|
||||
elif hasattr(op2, table_name):
|
||||
stress_table = getattr(op2, table_name)
|
||||
if stress_table:
|
||||
subcase_id = list(stress_table.keys())[0]
|
||||
stress_data = stress_table[subcase_id]
|
||||
|
||||
# Extract von Mises stress
|
||||
# Note: Structure varies by element type
|
||||
element_ids = stress_data.element_node[:, 0].astype(int)
|
||||
if stress_table:
|
||||
subcase_id = list(stress_table.keys())[0]
|
||||
stress_data = stress_table[subcase_id]
|
||||
|
||||
if stress_type == 'von_mises':
|
||||
# von Mises is usually last column
|
||||
stresses = stress_data.data[0, :, -1] # timestep 0, all elements, last column
|
||||
# Extract von Mises stress
|
||||
# Note: Structure varies by element type
|
||||
element_ids = stress_data.element_node[:, 0].astype(int)
|
||||
|
||||
if stress_type == 'von_mises':
|
||||
# For solid elements (CHEXA, CTETRA, CPENTA): von Mises is at index 9
|
||||
# For shell elements (CQUAD4, CTRIA3): von Mises is last column (-1)
|
||||
if table_name in ['chexa_stress', 'ctetra_stress', 'cpenta_stress']:
|
||||
# Solid elements: data shape is [itime, nnodes, 10]
|
||||
# Index 9 is von_mises [oxx, oyy, ozz, txy, tyz, txz, o1, o2, o3, von_mises]
|
||||
stresses = stress_data.data[0, :, 9]
|
||||
else:
|
||||
# Shell elements: von Mises is last column
|
||||
stresses = stress_data.data[0, :, -1]
|
||||
else:
|
||||
# Max principal stress
|
||||
if table_name in ['chexa_stress', 'ctetra_stress', 'cpenta_stress']:
|
||||
stresses = stress_data.data[0, :, 6] # o1 (max principal)
|
||||
else:
|
||||
# Max principal stress (second-to-last column typically)
|
||||
stresses = stress_data.data[0, :, -2]
|
||||
|
||||
max_stress_in_table = np.max(stresses)
|
||||
if max_stress_in_table > max_stress_overall:
|
||||
max_stress_overall = max_stress_in_table
|
||||
max_idx = np.argmax(stresses)
|
||||
max_element_id = element_ids[max_idx]
|
||||
max_element_type = table_name.replace('_stress', '')
|
||||
max_stress_in_table = np.max(stresses)
|
||||
if max_stress_in_table > max_stress_overall:
|
||||
max_stress_overall = max_stress_in_table
|
||||
max_idx = np.argmax(stresses)
|
||||
max_element_id = element_ids[max_idx]
|
||||
max_element_type = table_name.replace('_stress', '')
|
||||
|
||||
# CRITICAL: NX Nastran outputs stress in kPa (mN/mm²), convert to MPa
|
||||
# 1 kPa = 0.001 MPa
|
||||
max_stress_overall_mpa = max_stress_overall / 1000.0
|
||||
|
||||
return {
|
||||
'max_stress': float(max_stress_overall),
|
||||
'max_stress': float(max_stress_overall_mpa),
|
||||
'stress_type': stress_type,
|
||||
'element_id': int(max_element_id) if max_element_id else None,
|
||||
'element_type': max_element_type,
|
||||
'units': 'MPa', # NX typically uses MPa
|
||||
'units': 'MPa',
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user