feat: Comprehensive expression extraction and OP2 result extractor example

Enhanced expression extraction to find ALL named expressions in .prt files,
not just specific format. Added pyNastran-based result extraction example.

Expression Extraction Improvements:
- Updated regex to handle all NX expression format variations:
  * #(Type [units]) name: value;
  * (Type [units]) name: value;
  * *(Type [units]) name: value;
  * ((Type [units]) name: value;
- Added Root:expression_name: pattern detection
- Finds expressions even when value is not immediately available
- Deduplication to avoid duplicates
- Filters out NX internal names

Test Results with Bracket.prt:
- Previously: 1 expression (tip_thickness only)
- Now: 5 expressions found:
  * support_angle = 30.0 degrees
  * tip_thickness = 20.0 mm
  * p3 = 10.0 mm
  * support_blend_radius = 10.0 mm
  * p11 (reference found, value unknown)

OP2 Result Extraction (pyNastran):
- Created example extractor: op2_extractor_example.py
- Functions for common optimization metrics:
  * extract_max_displacement() - max displacement magnitude on any node
  * extract_max_stress() - von Mises or max principal stress
  * extract_mass() - total mass and center of gravity
- Handles multiple element types (CQUAD4, CTRIA3, CTETRA, etc.)
- Returns structured JSON for optimization engine integration
- Command-line tool for testing with real OP2 files

Usage:
  python optimization_engine/result_extractors/op2_extractor_example.py <file.op2>

Integration Ready:
- pyNastran already in requirements.txt
- Result extractor pattern established
- Can be used as template for custom metrics

Next Steps:
- Integrate result extractors into MCP tool framework
- Add safety factor calculations
- Support for thermal, modal results
This commit is contained in:
Claude
2025-11-15 13:49:16 +00:00
parent 063439af43
commit 16cddd5243
2 changed files with 280 additions and 11 deletions

View File

@@ -222,21 +222,54 @@ class SimFileParser:
# Try to decode as latin-1 (preserves all byte values)
text_content = content.decode('latin-1', errors='ignore')
# Pattern 1: NX native format: #(Number [mm]) tip_thickness: 20;
# Captures: type, units, name, value
nx_pattern = r'#\((\w+)\s*\[([^\]]*)\]\)\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)'
# Pattern 1: NX native format with variations:
# #(Number [mm]) tip_thickness: 20;
# (Number [mm]) p3: 10;
# *(Number [mm]) support_blend_radius: 10;
# ((Number [degrees]) support_angle: 30;
# Prefix can be: #(, *(, (, ((
nx_pattern = r'[#*\(]*\((\w+)\s*\[([^\]]*)\]\)\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)'
# Use set to avoid duplicates
expr_names_seen = set()
for match in re.finditer(nx_pattern, text_content):
expr_type, units, name, value = match.groups()
expressions.append({
'name': name,
'value': float(value),
'units': units,
'type': expr_type,
'source': 'prt_file_nx_format'
})
if name not in expr_names_seen:
expr_names_seen.add(name)
expressions.append({
'name': name,
'value': float(value),
'units': units,
'type': expr_type,
'source': 'prt_file_nx_format'
})
# Pattern 2: Fallback - simple name=value pattern
# Pattern 2: Find expression names from Root: references
# Format: Root:expression_name:
root_pattern = r'Root:([a-zA-Z_][a-zA-Z0-9_]{2,}):'
potential_expr_names = set()
for match in re.finditer(root_pattern, text_content):
name = match.group(1)
# Filter out common NX internal names
if name not in ['index', '%%Name', '%%ug_objects_for_', 'WorldModifier']:
if not name.startswith('%%'):
potential_expr_names.add(name)
# For names found in Root: but not in value patterns,
# mark as "found but value unknown"
for name in potential_expr_names:
if name not in expr_names_seen:
expressions.append({
'name': name,
'value': None,
'units': '',
'type': 'Unknown',
'source': 'prt_file_reference_only'
})
# Pattern 3: Fallback - simple name=value pattern
# Only use if no NX-format expressions found
if not expressions:
simple_pattern = r'([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*([-+]?\d*\.?\d+(?:[eE][-+]?\d+)?)'