feat(canvas): Add 'Run Baseline' FEA simulation feature to IntrospectionPanel
Backend:
- Add POST /api/optimization/studies/{study_id}/nx/run-baseline endpoint
- Creates trial_baseline folder in 2_iterations/
- Copies all model files and runs NXSolver
- Returns paths to result files (.op2, .f06, .bdf) for extractor testing
Frontend:
- Add 'Run Baseline Simulation' button to IntrospectionPanel
- Show progress spinner during simulation
- Display result files when complete (OP2, F06, BDF)
- Show error messages if simulation fails
This enables:
- Testing custom extractors against real FEA results
- Validating the simulation pipeline before optimization
- Inspecting boundary conditions and loads
This commit is contained in:
@@ -4577,6 +4577,153 @@ async def introspect_nx_model(study_id: str, force: bool = False):
|
||||
raise HTTPException(status_code=500, detail=f"Failed to introspect: {str(e)}")
|
||||
|
||||
|
||||
@router.post("/studies/{study_id}/nx/run-baseline")
|
||||
async def run_baseline_simulation(study_id: str):
|
||||
"""
|
||||
Run a baseline FEA simulation with current/default parameter values.
|
||||
|
||||
This creates a 'baseline' trial folder in 2_iterations/ with:
|
||||
- All NX model files copied
|
||||
- Simulation run with baseline parameters
|
||||
- Result files (.op2, .f06, .bdf) for extractor testing
|
||||
|
||||
Use this to:
|
||||
1. Verify the FEA pipeline works before optimization
|
||||
2. Get result files for testing custom extractors
|
||||
3. Validate boundary conditions and loads are correct
|
||||
|
||||
Args:
|
||||
study_id: Study identifier
|
||||
|
||||
Returns:
|
||||
JSON with baseline run status and paths to result files
|
||||
"""
|
||||
try:
|
||||
study_dir = resolve_study_path(study_id)
|
||||
print(f"[run-baseline] study_id={study_id}, study_dir={study_dir}")
|
||||
|
||||
if not study_dir.exists():
|
||||
raise HTTPException(status_code=404, detail=f"Study {study_id} not found")
|
||||
|
||||
# Find model directory
|
||||
model_dir = None
|
||||
for possible_dir in [
|
||||
study_dir / "1_setup" / "model",
|
||||
study_dir / "1_model",
|
||||
study_dir / "0_model",
|
||||
study_dir / "model",
|
||||
study_dir / "1_setup",
|
||||
]:
|
||||
if possible_dir.exists() and list(possible_dir.glob("*.sim")):
|
||||
model_dir = possible_dir
|
||||
break
|
||||
|
||||
if model_dir is None:
|
||||
raise HTTPException(
|
||||
status_code=404, detail=f"No model directory with .sim file found for {study_id}"
|
||||
)
|
||||
|
||||
# Find .sim file
|
||||
sim_files = list(model_dir.glob("*.sim"))
|
||||
if not sim_files:
|
||||
raise HTTPException(status_code=404, detail=f"No .sim file found in {model_dir}")
|
||||
|
||||
sim_file = sim_files[0]
|
||||
print(f"[run-baseline] sim_file={sim_file}")
|
||||
|
||||
# Create baseline trial folder
|
||||
iterations_dir = study_dir / "2_iterations"
|
||||
iterations_dir.mkdir(exist_ok=True)
|
||||
baseline_dir = iterations_dir / "trial_baseline"
|
||||
|
||||
# Clean up existing baseline if present
|
||||
if baseline_dir.exists():
|
||||
import shutil
|
||||
|
||||
shutil.rmtree(baseline_dir)
|
||||
baseline_dir.mkdir()
|
||||
|
||||
# Copy all model files to baseline folder
|
||||
import shutil
|
||||
|
||||
model_extensions = [".prt", ".fem", ".afm", ".sim", ".exp"]
|
||||
copied_files = []
|
||||
for ext in model_extensions:
|
||||
for src_file in model_dir.glob(f"*{ext}"):
|
||||
dst_file = baseline_dir / src_file.name
|
||||
shutil.copy2(src_file, dst_file)
|
||||
copied_files.append(src_file.name)
|
||||
|
||||
print(f"[run-baseline] Copied {len(copied_files)} files to baseline folder")
|
||||
|
||||
# Find the copied sim file in baseline dir
|
||||
baseline_sim = baseline_dir / sim_file.name
|
||||
|
||||
# Try to run the solver
|
||||
try:
|
||||
from optimization_engine.nx.solver import NXSolver
|
||||
|
||||
solver = NXSolver(
|
||||
nastran_version="2512",
|
||||
timeout=600,
|
||||
use_journal=True,
|
||||
enable_session_management=True,
|
||||
study_name=study_id,
|
||||
)
|
||||
|
||||
print(f"[run-baseline] Starting solver...")
|
||||
result = solver.run_simulation(
|
||||
sim_file=baseline_sim,
|
||||
working_dir=baseline_dir,
|
||||
cleanup=False, # Keep all files for inspection
|
||||
expression_updates=None, # Use baseline values
|
||||
)
|
||||
|
||||
# Find result files
|
||||
op2_files = list(baseline_dir.glob("*.op2"))
|
||||
f06_files = list(baseline_dir.glob("*.f06"))
|
||||
bdf_files = list(baseline_dir.glob("*.bdf")) + list(baseline_dir.glob("*.dat"))
|
||||
|
||||
return {
|
||||
"success": result.get("success", False),
|
||||
"study_id": study_id,
|
||||
"baseline_dir": str(baseline_dir),
|
||||
"sim_file": str(baseline_sim),
|
||||
"elapsed_time": result.get("elapsed_time"),
|
||||
"result_files": {
|
||||
"op2": [f.name for f in op2_files],
|
||||
"f06": [f.name for f in f06_files],
|
||||
"bdf": [f.name for f in bdf_files],
|
||||
},
|
||||
"errors": result.get("errors", []),
|
||||
"message": "Baseline simulation complete"
|
||||
if result.get("success")
|
||||
else "Simulation failed",
|
||||
}
|
||||
|
||||
except ImportError as e:
|
||||
return {
|
||||
"success": False,
|
||||
"study_id": study_id,
|
||||
"baseline_dir": str(baseline_dir),
|
||||
"error": f"NXSolver not available: {str(e)}",
|
||||
"message": "Model files copied but solver not available. Run manually in NX.",
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"study_id": study_id,
|
||||
"baseline_dir": str(baseline_dir),
|
||||
"error": str(e),
|
||||
"message": f"Solver execution failed: {str(e)}",
|
||||
}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to run baseline: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/studies/{study_id}/nx/expressions")
|
||||
async def get_nx_expressions(study_id: str):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user