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:
2025-11-15 14:00:00 -05:00
parent c1fad3bd37
commit 9ddc065d31
5 changed files with 1216 additions and 3 deletions

View File

@@ -0,0 +1,158 @@
"""
NX Journal Script: Extract Expressions from .sim File
This script:
1. Opens a .sim file
2. Extracts all expressions from the .sim and loaded .prt files
3. Saves expression data to JSON for the dashboard
Usage:
run_journal.exe extract_expressions.py <sim_file_path> <output_json_path>
"""
import sys
import json
import NXOpen
def extract_all_expressions(sim_file_path, output_file_path):
"""
Extract all expressions from .sim file and loaded parts.
Args:
sim_file_path: Path to .sim file
output_file_path: Path to save JSON output
"""
try:
# Get NX session
session = NXOpen.Session.GetSession()
# Open the .sim file
print(f"Opening .sim file: {sim_file_path}")
part_load_status = None
base_part, part_load_status = session.Parts.OpenBaseDisplay(sim_file_path)
if part_load_status:
part_load_status.Dispose()
# Collect all expressions from all loaded parts
all_expressions = {}
# Get work parts and components
parts_to_scan = [base_part]
# Also scan all loaded components
for component in base_part.ComponentAssembly.RootComponent.GetChildren():
try:
component_part = component.Prototype.OwningPart
if component_part and component_part not in parts_to_scan:
parts_to_scan.append(component_part)
except:
pass
# Extract expressions from each part
for part in parts_to_scan:
part_name = part.Name
print(f"Scanning expressions from: {part_name}")
expressions_list = []
# Get all expressions
for expr in part.Expressions:
try:
expr_data = {
'name': expr.Name,
'value': expr.Value,
'formula': expr.Equation,
'units': expr.Units,
'type': 'number', # Most expressions are numeric
'source_part': part_name
}
# Try to determine if it's a design variable candidate
# (not a formula, can be changed)
if '=' not in expr.Equation or expr.Equation.strip() == str(expr.Value):
expr_data['is_variable_candidate'] = True
else:
expr_data['is_variable_candidate'] = False
expressions_list.append(expr_data)
except Exception as e:
print(f"Warning: Could not read expression {expr.Name}: {e}")
continue
all_expressions[part_name] = expressions_list
# Collect simulation metadata
metadata = {
'sim_file': sim_file_path,
'base_part': base_part.Name,
'num_components': len(parts_to_scan),
'total_expressions': sum(len(exprs) for exprs in all_expressions.values()),
'variable_candidates': sum(
1 for exprs in all_expressions.values()
for expr in exprs
if expr.get('is_variable_candidate', False)
)
}
# Prepare output
output_data = {
'metadata': metadata,
'expressions_by_part': all_expressions
}
# Save to JSON
print(f"Saving expressions to: {output_file_path}")
with open(output_file_path, 'w') as f:
json.dump(output_data, f, indent=2)
print(f"Successfully extracted {metadata['total_expressions']} expressions")
print(f"Found {metadata['variable_candidates']} potential design variables")
# Close part
base_part.Close(NXOpen.BasePart.CloseWholeTree.True,
NXOpen.BasePart.CloseModified.CloseModified, None)
return True
except Exception as e:
error_data = {
'error': str(e),
'sim_file': sim_file_path
}
print(f"ERROR: {e}")
with open(output_file_path, 'w') as f:
json.dump(error_data, f, indent=2)
return False
def main():
"""Main entry point for journal script."""
if len(sys.argv) < 3:
print("Usage: extract_expressions.py <sim_file_path> <output_json_path>")
sys.exit(1)
sim_file_path = sys.argv[1]
output_file_path = sys.argv[2]
print("="*60)
print("NX Expression Extractor")
print("="*60)
success = extract_all_expressions(sim_file_path, output_file_path)
if success:
print("\nExpression extraction completed successfully!")
sys.exit(0)
else:
print("\nExpression extraction failed!")
sys.exit(1)
if __name__ == '__main__':
main()