Major additions: - Training data export system for AtomizerField neural network training - Bracket stiffness optimization study with 50+ training samples - Intelligent NX model discovery (auto-detect solutions, expressions, mesh) - Result extractors module for displacement, stress, frequency, mass - User-generated NX journals for advanced workflows - Archive structure for legacy scripts and test outputs - Protocol documentation and dashboard launcher 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
173 lines
5.5 KiB
Python
173 lines
5.5 KiB
Python
"""
|
|
Generic Stiffness Calculator
|
|
============================
|
|
|
|
Reusable calculator for structural stiffness from FEA results.
|
|
Works with any structure: bracket, beam, plate, etc.
|
|
|
|
Stiffness (k) = Applied Force (F) / Displacement (δ)
|
|
|
|
Usage:
|
|
calculator = StiffnessCalculator(
|
|
field_file="export_field_dz.fld",
|
|
op2_file="model.op2",
|
|
force_component="fz",
|
|
displacement_component="z"
|
|
)
|
|
results = calculator.calculate()
|
|
stiffness = results['stiffness']
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from typing import Dict, Any, Optional
|
|
from .field_data_extractor import FieldDataExtractor
|
|
from .op2_extractor import OP2Extractor
|
|
|
|
|
|
class StiffnessCalculator:
|
|
"""
|
|
Generic stiffness calculator for structural analysis.
|
|
|
|
Can be used for:
|
|
- Bracket stiffness (force/deflection)
|
|
- Beam bending stiffness
|
|
- Plate flexural stiffness
|
|
- Any structure where k = F/δ
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
field_file: str,
|
|
op2_file: str,
|
|
force_component: str = "fz", # fx, fy, fz, total
|
|
displacement_component: str = "z", # x, y, z
|
|
displacement_aggregation: str = "max_abs",
|
|
applied_force: Optional[float] = None, # If known, skip OP2 extraction
|
|
):
|
|
"""
|
|
Args:
|
|
field_file: Path to displacement field file (.fld or .csv)
|
|
op2_file: Path to OP2 file
|
|
force_component: Force component to extract (fx, fy, fz, total)
|
|
displacement_component: Displacement component (x, y, z)
|
|
displacement_aggregation: How to aggregate displacement (max_abs, max, min)
|
|
applied_force: Optional pre-known force value (skips OP2 extraction)
|
|
"""
|
|
self.field_file = Path(field_file)
|
|
self.op2_file = Path(op2_file)
|
|
self.force_component = force_component
|
|
self.displacement_component = displacement_component
|
|
self.displacement_aggregation = displacement_aggregation
|
|
self.applied_force = applied_force
|
|
|
|
def calculate(self) -> Dict[str, Any]:
|
|
"""
|
|
Calculate stiffness from FEA results.
|
|
|
|
Returns:
|
|
dict: {
|
|
'stiffness': k value (N/mm or appropriate units),
|
|
'displacement': max displacement (mm),
|
|
'force': applied force (N),
|
|
'compliance': 1/k (mm/N),
|
|
'units': unit description
|
|
}
|
|
"""
|
|
# Extract displacement
|
|
disp_extractor = FieldDataExtractor(
|
|
field_file=str(self.field_file),
|
|
result_column=f"{self.displacement_component}(mm)",
|
|
aggregation=self.displacement_aggregation
|
|
)
|
|
disp_result = disp_extractor.extract()
|
|
displacement = disp_result['value'] # mm
|
|
|
|
if displacement == 0:
|
|
raise ValueError(f"Zero displacement found. Check field file: {self.field_file}")
|
|
|
|
# Extract force
|
|
if self.applied_force is not None:
|
|
force = self.applied_force
|
|
else:
|
|
op2_extractor = OP2Extractor(op2_file=str(self.op2_file))
|
|
force_result = op2_extractor.extract_grid_point_forces(component=self.force_component)
|
|
force = force_result['force'] # N (or mN, check units)
|
|
|
|
# Note: Forces in OP2 might be in milli-Newtons (mN)
|
|
# Check f06 header for units and convert if needed
|
|
# For now, assuming Newtons
|
|
|
|
if force == 0:
|
|
raise ValueError(f"Zero force found. Check OP2 file: {self.op2_file}")
|
|
|
|
# Calculate stiffness: k = F / δ
|
|
stiffness = force / displacement # N/mm
|
|
|
|
# Calculate compliance (inverse of stiffness)
|
|
compliance = displacement / force # mm/N
|
|
|
|
return {
|
|
'stiffness': stiffness,
|
|
'displacement': displacement,
|
|
'force': force,
|
|
'compliance': compliance,
|
|
'units': {
|
|
'stiffness': 'N/mm',
|
|
'displacement': 'mm',
|
|
'force': 'N (verify from f06)',
|
|
'compliance': 'mm/N'
|
|
},
|
|
'field_file': str(self.field_file),
|
|
'op2_file': str(self.op2_file),
|
|
'displacement_stats': disp_result['stats'],
|
|
}
|
|
|
|
|
|
def calculate_stiffness(
|
|
field_file: str,
|
|
op2_file: str,
|
|
force_component: str = "fz",
|
|
displacement_component: str = "z"
|
|
) -> float:
|
|
"""
|
|
Convenience function to calculate stiffness.
|
|
|
|
Args:
|
|
field_file: Path to displacement field file
|
|
op2_file: Path to OP2 file
|
|
force_component: Force component (fx, fy, fz, total)
|
|
displacement_component: Displacement component (x, y, z)
|
|
|
|
Returns:
|
|
Stiffness value (N/mm)
|
|
"""
|
|
calculator = StiffnessCalculator(
|
|
field_file=field_file,
|
|
op2_file=op2_file,
|
|
force_component=force_component,
|
|
displacement_component=displacement_component
|
|
)
|
|
result = calculator.calculate()
|
|
return result['stiffness']
|
|
|
|
|
|
if __name__ == "__main__":
|
|
# Example usage
|
|
import sys
|
|
if len(sys.argv) > 2:
|
|
field_file = sys.argv[1]
|
|
op2_file = sys.argv[2]
|
|
|
|
calculator = StiffnessCalculator(
|
|
field_file=field_file,
|
|
op2_file=op2_file,
|
|
force_component="fz",
|
|
displacement_component="z"
|
|
)
|
|
|
|
results = calculator.calculate()
|
|
print(f"Stiffness: {results['stiffness']:.2f} N/mm")
|
|
print(f"Displacement: {results['displacement']:.6f} mm")
|
|
print(f"Force: {results['force']:.2f} N")
|
|
print(f"Compliance: {results['compliance']:.6e} mm/N")
|