feat: Add NX study models and optimization histories
Includes all study folders with NX models for development: - bracket_stiffness_optimization (V1, V2, V3) - drone_gimbal_arm_optimization - simple_beam_optimization - uav_arm_optimization (V1, V2) - training_data_export_test - uav_arm_atomizerfield_test Contains .prt, .fem, .sim files and optimization databases. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,242 @@
|
||||
"""
|
||||
Bracket Stiffness Optimization - Results Extractor
|
||||
==================================================
|
||||
|
||||
This extractor uses the generic tools from optimization_engine/extractors/ to:
|
||||
1. Execute NX journal to export displacement field
|
||||
2. Extract z-displacement from field data
|
||||
3. Extract applied force from OP2 file
|
||||
4. Calculate stiffness (k = F/δ)
|
||||
5. Extract mass from OP2 file
|
||||
|
||||
This is a thin wrapper around generic extractors - all the heavy lifting
|
||||
is done by reusable components.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Tuple
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from optimization_engine.extractors.stiffness_calculator import StiffnessCalculator
|
||||
from optimization_engine.extractors.bdf_mass_extractor import BDFMassExtractor
|
||||
from optimization_engine.nx_solver import NXSolver
|
||||
|
||||
# Import central configuration
|
||||
import config as atomizer_config
|
||||
|
||||
|
||||
class BracketStiffnessExtractor:
|
||||
"""
|
||||
Bracket-specific extractor that orchestrates generic tools.
|
||||
|
||||
Extracts:
|
||||
- Stiffness (N/mm) from force and z-displacement
|
||||
- Mass (kg) from OP2 grid point weight
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
model_dir: Path,
|
||||
sim_file: str = "Bracket_sim1.sim",
|
||||
export_journal: str = "export_displacement_field.py",
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
model_dir: Directory containing model files
|
||||
sim_file: Name of .sim file
|
||||
export_journal: Name of journal that exports displacement field
|
||||
"""
|
||||
self.model_dir = Path(model_dir)
|
||||
self.sim_file = self.model_dir / sim_file
|
||||
self.export_journal = self.model_dir / export_journal
|
||||
self.sim_base = Path(sim_file).stem # e.g., "Bracket_sim1" -> "Bracket_sim1"
|
||||
|
||||
# Expected output files from NX
|
||||
self.field_file = self.model_dir / "export_field_dz.fld"
|
||||
# NX creates OP2 with lowercase base name and solution suffix
|
||||
self.op2_file = self.model_dir / f"{self.sim_base.lower()}-solution_1.op2"
|
||||
# BDF/DAT file for mass extraction
|
||||
self.dat_file = self.model_dir / f"{self.sim_base.lower()}-solution_1.dat"
|
||||
|
||||
def extract_results(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Extract stiffness and mass from FEA results.
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
'stiffness': stiffness value (N/mm),
|
||||
'mass': mass in kg,
|
||||
'mass_g': mass in grams,
|
||||
'displacement': max z-displacement (mm),
|
||||
'force': applied force (N),
|
||||
'compliance': inverse stiffness (mm/N),
|
||||
'objectives': {
|
||||
'stiffness': value for maximization,
|
||||
'mass': value for constraint checking
|
||||
}
|
||||
}
|
||||
"""
|
||||
# Step 1: Execute NX journal to export displacement field
|
||||
print(f"Executing journal to export displacement field...")
|
||||
self._export_displacement_field()
|
||||
|
||||
# Verify field file was created
|
||||
if not self.field_file.exists():
|
||||
raise FileNotFoundError(f"Field file not created: {self.field_file}")
|
||||
|
||||
# Verify OP2 file exists
|
||||
if not self.op2_file.exists():
|
||||
raise FileNotFoundError(f"OP2 file not found: {self.op2_file}")
|
||||
|
||||
# Step 2: Calculate stiffness using generic calculator
|
||||
print(f"Calculating stiffness...")
|
||||
stiffness_calc = StiffnessCalculator(
|
||||
field_file=str(self.field_file),
|
||||
op2_file=str(self.op2_file),
|
||||
force_component="fz", # Z-direction force
|
||||
displacement_component="z", # Z-displacement
|
||||
displacement_aggregation="max_abs", # Maximum absolute displacement
|
||||
applied_force=1000.0 # Applied load is 1000N (constant for this model)
|
||||
)
|
||||
|
||||
stiffness_results = stiffness_calc.calculate()
|
||||
|
||||
# Step 3: Extract mass from BDF/DAT file
|
||||
print(f"Extracting mass from BDF...")
|
||||
if not self.dat_file.exists():
|
||||
raise FileNotFoundError(f"DAT file not found: {self.dat_file}")
|
||||
|
||||
bdf_extractor = BDFMassExtractor(bdf_file=str(self.dat_file))
|
||||
mass_results = bdf_extractor.extract_mass()
|
||||
|
||||
# Step 4: Combine results
|
||||
results = {
|
||||
'stiffness': stiffness_results['stiffness'],
|
||||
'mass': mass_results['mass_kg'],
|
||||
'mass_g': mass_results['mass_g'],
|
||||
'displacement': stiffness_results['displacement'],
|
||||
'force': stiffness_results['force'],
|
||||
'compliance': stiffness_results['compliance'],
|
||||
'objectives': {
|
||||
'stiffness': stiffness_results['stiffness'], # Maximize
|
||||
'mass': mass_results['mass_kg'] # Constrain ≤ 0.2 kg
|
||||
},
|
||||
'details': {
|
||||
'stiffness_stats': stiffness_results['displacement_stats'],
|
||||
'mass_cg': mass_results.get('cg'),
|
||||
'units': stiffness_results['units']
|
||||
}
|
||||
}
|
||||
|
||||
print(f"\n[OK] Stiffness: {results['stiffness']:.2f} N/mm")
|
||||
print(f"[OK] Mass: {results['mass']:.6f} kg ({results['mass_g']:.2f} g)")
|
||||
print(f"[OK] Displacement: {results['displacement']:.6f} mm")
|
||||
print(f"[OK] Force: {results['force']:.2f} N")
|
||||
|
||||
return results
|
||||
|
||||
def _export_displacement_field(self):
|
||||
"""
|
||||
Execute NX journal to export displacement field.
|
||||
|
||||
The journal should:
|
||||
1. Open the simulation
|
||||
2. Export ResultProbe to field file (.fld)
|
||||
3. Save and close
|
||||
"""
|
||||
if not self.export_journal.exists():
|
||||
raise FileNotFoundError(f"Export journal not found: {self.export_journal}")
|
||||
|
||||
# Use NXSolver to execute journal
|
||||
# Note: This assumes NXSolver can run journals in non-solve mode
|
||||
# If not, we'll need to create a separate journal runner
|
||||
try:
|
||||
from optimization_engine.nx_solver import run_journal
|
||||
run_journal(str(self.export_journal))
|
||||
except ImportError:
|
||||
# Fallback: Execute journal directly via NX command line
|
||||
import subprocess
|
||||
nx_exe = atomizer_config.NX_RUN_JOURNAL
|
||||
if Path(nx_exe).exists():
|
||||
# Note: NX's run_journal.exe may return non-zero even on success due to sys.exit() handling
|
||||
# We check for field file existence instead of return code
|
||||
result = subprocess.run([nx_exe, str(self.export_journal)], capture_output=True, text=True)
|
||||
# If field file doesn't exist after running journal, something went wrong
|
||||
if not self.field_file.exists():
|
||||
raise RuntimeError(
|
||||
f"Journal execution completed but field file not created: {self.field_file}\n"
|
||||
f"Journal output:\n{result.stdout}\n{result.stderr}"
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"Cannot execute journal. NX executable not found: {nx_exe}\n"
|
||||
f"Please execute journal manually: {self.export_journal}"
|
||||
)
|
||||
|
||||
|
||||
def extract_bracket_stiffness(
|
||||
model_dir: str,
|
||||
sim_file: str = "Bracket_sim1.sim"
|
||||
) -> Tuple[float, float]:
|
||||
"""
|
||||
Convenience function to extract stiffness and mass.
|
||||
|
||||
Args:
|
||||
model_dir: Directory containing bracket model files
|
||||
sim_file: Name of simulation file
|
||||
|
||||
Returns:
|
||||
(stiffness, mass): Stiffness in N/mm, mass in kg
|
||||
"""
|
||||
extractor = BracketStiffnessExtractor(
|
||||
model_dir=Path(model_dir),
|
||||
sim_file=sim_file
|
||||
)
|
||||
results = extractor.extract_results()
|
||||
return results['stiffness'], results['mass']
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Example usage / testing
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
model_dir = sys.argv[1]
|
||||
else:
|
||||
# Default to study model directory
|
||||
model_dir = Path(__file__).parent / "1_setup" / "model"
|
||||
|
||||
print(f"Testing bracket stiffness extractor...")
|
||||
print(f"Model directory: {model_dir}\n")
|
||||
|
||||
extractor = BracketStiffnessExtractor(model_dir=model_dir)
|
||||
|
||||
try:
|
||||
results = extractor.extract_results()
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("EXTRACTION RESULTS")
|
||||
print("="*60)
|
||||
print(f"Stiffness: {results['stiffness']:.2f} N/mm")
|
||||
print(f"Mass: {results['mass']:.6f} kg ({results['mass_g']:.2f} g)")
|
||||
print(f"Displacement: {results['displacement']:.6f} mm")
|
||||
print(f"Force: {results['force']:.2f} N")
|
||||
print(f"Compliance: {results['compliance']:.6e} mm/N")
|
||||
print("="*60)
|
||||
|
||||
# Check constraint
|
||||
max_mass_kg = 0.2
|
||||
if results['mass'] <= max_mass_kg:
|
||||
print(f"[OK] Mass constraint satisfied: {results['mass']:.6f} kg <= {max_mass_kg} kg")
|
||||
else:
|
||||
print(f"[X] Mass constraint violated: {results['mass']:.6f} kg > {max_mass_kg} kg")
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n[X] Extraction failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
Reference in New Issue
Block a user