feat: Add comprehensive study management system to dashboard
Added full study configuration UI: - Create studies with isolated folder structure (sim/, results/, config.json) - File management: users drop .sim/.prt files into study's sim folder - NX expression extraction: journal script to explore .sim file - Configuration UI for design variables, objectives, and constraints - Save/load study configurations through API - Step-by-step workflow: create → add files → explore → configure → run Backend API (app.py): - POST /api/study/create - Create new study with folder structure - GET /api/study/<name>/sim/files - List files in sim folder - POST /api/study/<name>/explore - Extract expressions from .sim file - GET/POST /api/study/<name>/config - Load/save study configuration Frontend: - New study configuration view with 5-step wizard - Modal for creating new studies - Expression explorer with clickable selection - Dynamic forms for variables/objectives/constraints - Professional styling with config cards NX Integration: - extract_expressions.py journal script - Scans .sim and all loaded .prt files - Identifies potential design variable candidates - Exports expressions with values, formulas, units Each study is self-contained with its own geometry files and config.
This commit is contained in:
@@ -453,6 +453,276 @@ def get_visualization_data(study_name: str):
|
||||
}), 500
|
||||
|
||||
|
||||
# ====================
|
||||
# Study Management API
|
||||
# ====================
|
||||
|
||||
@app.route('/api/study/create', methods=['POST'])
|
||||
def create_study():
|
||||
"""
|
||||
Create a new study with folder structure.
|
||||
|
||||
Request body:
|
||||
{
|
||||
"study_name": "my_new_study",
|
||||
"description": "Optional description"
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
study_name = data.get('study_name')
|
||||
description = data.get('description', '')
|
||||
|
||||
if not study_name:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'study_name is required'
|
||||
}), 400
|
||||
|
||||
# Create study folder structure
|
||||
study_dir = project_root / 'optimization_results' / study_name
|
||||
|
||||
if study_dir.exists():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Study {study_name} already exists'
|
||||
}), 400
|
||||
|
||||
# Create directories
|
||||
study_dir.mkdir(parents=True, exist_ok=True)
|
||||
(study_dir / 'sim').mkdir(exist_ok=True)
|
||||
(study_dir / 'results').mkdir(exist_ok=True)
|
||||
|
||||
# Create initial metadata
|
||||
metadata = {
|
||||
'study_name': study_name,
|
||||
'description': description,
|
||||
'created_at': datetime.now().isoformat(),
|
||||
'status': 'created',
|
||||
'has_sim_files': False,
|
||||
'is_configured': False
|
||||
}
|
||||
|
||||
metadata_path = study_dir / 'metadata.json'
|
||||
with open(metadata_path, 'w') as f:
|
||||
json.dump(metadata, f, indent=2)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Study {study_name} created successfully',
|
||||
'study_path': str(study_dir),
|
||||
'sim_folder': str(study_dir / 'sim')
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/api/study/<study_name>/sim/files', methods=['GET'])
|
||||
def list_sim_files(study_name: str):
|
||||
"""
|
||||
List all files in the study's sim/ folder.
|
||||
|
||||
Args:
|
||||
study_name: Name of the study
|
||||
"""
|
||||
try:
|
||||
study_dir = project_root / 'optimization_results' / study_name
|
||||
sim_dir = study_dir / 'sim'
|
||||
|
||||
if not sim_dir.exists():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Study {study_name} does not exist'
|
||||
}), 404
|
||||
|
||||
# List all files
|
||||
files = []
|
||||
for file_path in sim_dir.iterdir():
|
||||
if file_path.is_file():
|
||||
files.append({
|
||||
'name': file_path.name,
|
||||
'size': file_path.stat().st_size,
|
||||
'extension': file_path.suffix,
|
||||
'modified': datetime.fromtimestamp(file_path.stat().st_mtime).isoformat()
|
||||
})
|
||||
|
||||
# Check for .sim file
|
||||
has_sim = any(f['extension'] == '.sim' for f in files)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'files': files,
|
||||
'has_sim_file': has_sim,
|
||||
'sim_folder': str(sim_dir)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/api/study/<study_name>/explore', methods=['POST'])
|
||||
def explore_sim_file(study_name: str):
|
||||
"""
|
||||
Explore the .sim file in the study folder to extract expressions.
|
||||
|
||||
Args:
|
||||
study_name: Name of the study
|
||||
"""
|
||||
try:
|
||||
study_dir = project_root / 'optimization_results' / study_name
|
||||
sim_dir = study_dir / 'sim'
|
||||
|
||||
# Find .sim file
|
||||
sim_files = list(sim_dir.glob('*.sim'))
|
||||
if not sim_files:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'No .sim file found in sim/ folder'
|
||||
}), 404
|
||||
|
||||
sim_file = sim_files[0]
|
||||
|
||||
# Run NX journal to extract expressions
|
||||
import subprocess
|
||||
journal_script = project_root / 'dashboard' / 'scripts' / 'extract_expressions.py'
|
||||
output_file = study_dir / 'expressions.json'
|
||||
|
||||
# Execute journal
|
||||
nx_executable = r"C:\Program Files\Siemens\Simcenter3D_2412\NXBIN\run_journal.exe"
|
||||
|
||||
result = subprocess.run(
|
||||
[nx_executable, str(journal_script), str(sim_file), str(output_file)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'NX journal failed: {result.stderr}'
|
||||
}), 500
|
||||
|
||||
# Load extracted expressions
|
||||
if not output_file.exists():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Expression extraction failed - no output file'
|
||||
}), 500
|
||||
|
||||
with open(output_file, 'r') as f:
|
||||
expressions = json.load(f)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'sim_file': str(sim_file),
|
||||
'expressions': expressions
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/api/study/<study_name>/config', methods=['GET'])
|
||||
def get_study_config(study_name: str):
|
||||
"""
|
||||
Get the configuration for a study.
|
||||
|
||||
Args:
|
||||
study_name: Name of the study
|
||||
"""
|
||||
try:
|
||||
study_dir = project_root / 'optimization_results' / study_name
|
||||
config_path = study_dir / 'config.json'
|
||||
|
||||
if not config_path.exists():
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'config': None,
|
||||
'message': 'No configuration found for this study'
|
||||
})
|
||||
|
||||
with open(config_path, 'r') as f:
|
||||
config = json.load(f)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'config': config
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
@app.route('/api/study/<study_name>/config', methods=['POST'])
|
||||
def save_study_config(study_name: str):
|
||||
"""
|
||||
Save configuration for a study.
|
||||
|
||||
Args:
|
||||
study_name: Name of the study
|
||||
|
||||
Request body:
|
||||
{
|
||||
"design_variables": [...],
|
||||
"objectives": [...],
|
||||
"constraints": [...],
|
||||
"optimization_settings": {...}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
study_dir = project_root / 'optimization_results' / study_name
|
||||
|
||||
if not study_dir.exists():
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Study {study_name} does not exist'
|
||||
}), 404
|
||||
|
||||
config = request.get_json()
|
||||
config_path = study_dir / 'config.json'
|
||||
|
||||
# Save configuration
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(config, f, indent=2)
|
||||
|
||||
# Update metadata
|
||||
metadata_path = study_dir / 'metadata.json'
|
||||
if metadata_path.exists():
|
||||
with open(metadata_path, 'r') as f:
|
||||
metadata = json.load(f)
|
||||
|
||||
metadata['is_configured'] = True
|
||||
metadata['last_modified'] = datetime.now().isoformat()
|
||||
|
||||
with open(metadata_path, 'w') as f:
|
||||
json.dump(metadata, f, indent=2)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Configuration saved for study {study_name}'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}), 500
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("="*60)
|
||||
print("ATOMIZER DASHBOARD API")
|
||||
|
||||
Reference in New Issue
Block a user