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:
88
examples/debug_op2_stress.py
Normal file
88
examples/debug_op2_stress.py
Normal file
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
Deep diagnostic to find where stress data is hiding in the OP2 file.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from pyNastran.op2.op2 import OP2
|
||||
|
||||
op2_path = Path("examples/bracket/bracket_sim1-solution_1.op2")
|
||||
|
||||
print("="*60)
|
||||
print("DEEP OP2 STRESS DIAGNOSTIC")
|
||||
print("="*60)
|
||||
print(f"File: {op2_path}")
|
||||
print()
|
||||
|
||||
op2 = OP2()
|
||||
op2.read_op2(str(op2_path))
|
||||
|
||||
# List ALL attributes that might contain stress
|
||||
print("--- SEARCHING FOR STRESS DATA ---")
|
||||
print()
|
||||
|
||||
# Check all attributes
|
||||
all_attrs = dir(op2)
|
||||
stress_related = [attr for attr in all_attrs if 'stress' in attr.lower() or 'oes' in attr.lower()]
|
||||
|
||||
print("Attributes with 'stress' or 'oes' in name:")
|
||||
for attr in stress_related:
|
||||
obj = getattr(op2, attr, None)
|
||||
if obj and not callable(obj):
|
||||
print(f" {attr}: {type(obj)}")
|
||||
if hasattr(obj, 'keys'):
|
||||
print(f" Keys: {list(obj.keys())}")
|
||||
if obj:
|
||||
first_key = list(obj.keys())[0]
|
||||
first_obj = obj[first_key]
|
||||
print(f" First item type: {type(first_obj)}")
|
||||
if hasattr(first_obj, 'data'):
|
||||
print(f" Data shape: {first_obj.data.shape}")
|
||||
print(f" Data type: {first_obj.data.dtype}")
|
||||
if hasattr(first_obj, '__dict__'):
|
||||
attrs = [a for a in dir(first_obj) if not a.startswith('_')]
|
||||
print(f" Available methods/attrs: {attrs[:10]}...")
|
||||
|
||||
print()
|
||||
print("--- CHECKING STANDARD STRESS TABLES ---")
|
||||
|
||||
standard_tables = [
|
||||
'cquad4_stress',
|
||||
'ctria3_stress',
|
||||
'ctetra_stress',
|
||||
'chexa_stress',
|
||||
'cpenta_stress',
|
||||
'cbar_stress',
|
||||
'cbeam_stress',
|
||||
]
|
||||
|
||||
for table_name in standard_tables:
|
||||
if hasattr(op2, table_name):
|
||||
table = getattr(op2, table_name)
|
||||
print(f"\n{table_name}:")
|
||||
print(f" Exists: {table is not None}")
|
||||
print(f" Type: {type(table)}")
|
||||
print(f" Bool: {bool(table)}")
|
||||
|
||||
if table:
|
||||
print(f" Keys: {list(table.keys())}")
|
||||
if table.keys():
|
||||
first_key = list(table.keys())[0]
|
||||
data = table[first_key]
|
||||
print(f" Data type: {type(data)}")
|
||||
print(f" Data shape: {data.data.shape if hasattr(data, 'data') else 'No data attr'}")
|
||||
|
||||
# Try to inspect the data object
|
||||
if hasattr(data, 'data'):
|
||||
print(f" Data min: {data.data.min():.6f}")
|
||||
print(f" Data max: {data.data.max():.6f}")
|
||||
|
||||
# Show column-wise max
|
||||
if len(data.data.shape) == 3:
|
||||
print(f" Column-wise max values:")
|
||||
for col in range(data.data.shape[2]):
|
||||
col_max = data.data[0, :, col].max()
|
||||
col_min = data.data[0, :, col].min()
|
||||
print(f" Column {col}: min={col_min:.6f}, max={col_max:.6f}")
|
||||
|
||||
print()
|
||||
print("="*60)
|
||||
56
examples/test_stress_direct.py
Normal file
56
examples/test_stress_direct.py
Normal file
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
Direct test of stress extraction without using cached imports.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
# Force reload
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
# Import directly from the file
|
||||
import importlib.util
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
"op2_extractor",
|
||||
project_root / "optimization_engine/result_extractors/op2_extractor_example.py"
|
||||
)
|
||||
op2_extractor = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(op2_extractor)
|
||||
|
||||
if __name__ == "__main__":
|
||||
op2_path = project_root / "examples/bracket/bracket_sim1-solution_1.op2"
|
||||
|
||||
print("="*60)
|
||||
print("DIRECT STRESS EXTRACTION TEST")
|
||||
print("="*60)
|
||||
print(f"OP2 file: {op2_path}")
|
||||
print()
|
||||
|
||||
# Test stress extraction
|
||||
print("--- Testing extract_max_stress() ---")
|
||||
try:
|
||||
result = op2_extractor.extract_max_stress(op2_path, stress_type='von_mises')
|
||||
print()
|
||||
print("RESULT:")
|
||||
for key, value in result.items():
|
||||
print(f" {key}: {value}")
|
||||
|
||||
if result['max_stress'] > 100.0:
|
||||
print()
|
||||
print("SUCCESS! Stress extraction working!")
|
||||
print(f"Got: {result['max_stress']:.2f} MPa")
|
||||
elif result['max_stress'] == 0.0:
|
||||
print()
|
||||
print("FAIL: Still returning 0.0")
|
||||
else:
|
||||
print()
|
||||
print(f"Got unexpected value: {result['max_stress']:.2f} MPa")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print()
|
||||
print("="*60)
|
||||
65
examples/test_stress_fix.py
Normal file
65
examples/test_stress_fix.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""
|
||||
Quick test to verify stress extraction fix for CHEXA elements.
|
||||
|
||||
Run this in test_env:
|
||||
conda activate test_env
|
||||
python examples/test_stress_fix.py
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from optimization_engine.result_extractors.extractors import stress_extractor, displacement_extractor
|
||||
|
||||
if __name__ == "__main__":
|
||||
op2_path = project_root / "examples/bracket/bracket_sim1-solution_1.op2"
|
||||
|
||||
print("="*60)
|
||||
print("STRESS EXTRACTION FIX VERIFICATION")
|
||||
print("="*60)
|
||||
print(f"OP2 file: {op2_path}")
|
||||
print()
|
||||
|
||||
# Test displacement (we know this works - 0.315 mm)
|
||||
print("--- Displacement (baseline test) ---")
|
||||
try:
|
||||
disp_result = displacement_extractor(op2_path)
|
||||
print(f"Max displacement: {disp_result['max_displacement']:.6f} mm")
|
||||
print(f"Node ID: {disp_result['max_node_id']}")
|
||||
print("OK Displacement extractor working")
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
|
||||
print()
|
||||
|
||||
# Test stress (should now return 122.91 MPa, not 0.0)
|
||||
print("--- Stress (FIXED - should show ~122.91 MPa) ---")
|
||||
try:
|
||||
stress_result = stress_extractor(op2_path)
|
||||
print(f"Max von Mises: {stress_result['max_von_mises']:.2f} MPa")
|
||||
print(f"Element ID: {stress_result['element_id']}")
|
||||
print(f"Element type: {stress_result['element_type']}")
|
||||
|
||||
# Verify fix worked
|
||||
if stress_result['max_von_mises'] > 100.0:
|
||||
print()
|
||||
print("SUCCESS! Stress extraction fixed!")
|
||||
print(f"Expected: ~122.91 MPa")
|
||||
print(f"Got: {stress_result['max_von_mises']:.2f} MPa")
|
||||
elif stress_result['max_von_mises'] == 0.0:
|
||||
print()
|
||||
print("FAIL: Still returning 0.0 - fix not working")
|
||||
else:
|
||||
print()
|
||||
print(f"WARNING: Got {stress_result['max_von_mises']:.2f} MPa - verify if correct")
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print()
|
||||
print("="*60)
|
||||
Reference in New Issue
Block a user