// Study Configuration Management Functions
// Global state for configuration
let currentConfigStudy = null;
let extractedExpressions = null;
let studyConfiguration = {
design_variables: [],
objectives: [],
constraints: [],
optimization_settings: {}
};
// ====================
// Create Study Modal
// ====================
function showCreateStudyModal() {
document.getElementById('createStudyModal').style.display = 'flex';
}
function closeCreateStudyModal() {
document.getElementById('createStudyModal').style.display = 'none';
}
async function createNewStudy() {
const studyName = document.getElementById('createStudyName').value;
const description = document.getElementById('createStudyDescription').value;
if (!studyName) {
showError('Please enter a study name');
return;
}
try {
const response = await fetch(`${API_BASE}/study/create`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
study_name: studyName,
description: description
})
});
const data = await response.json();
if (data.success) {
showSuccess(`Study "${studyName}" created successfully!`);
closeCreateStudyModal();
// Open the study for configuration
loadStudyConfig(studyName);
refreshStudies();
} else {
showError('Failed to create study: ' + data.error);
}
} catch (error) {
showError('Connection error: ' + error.message);
}
}
// ====================
// Study Configuration
// ====================
async function loadStudyConfig(studyName) {
currentConfigStudy = studyName;
// Hide other views, show config view
document.getElementById('welcomeScreen').style.display = 'none';
document.getElementById('studyDetails').style.display = 'none';
document.getElementById('studyConfig').style.display = 'block';
// Update header
document.getElementById('configStudyTitle').textContent = `Configure: ${studyName}`;
document.getElementById('configStudyMeta').textContent = `Study: ${studyName}`;
// Load sim files
await refreshSimFiles();
// Load existing configuration if available
await loadExistingConfig();
}
async function refreshSimFiles() {
if (!currentConfigStudy) return;
try {
const response = await fetch(`${API_BASE}/study/${currentConfigStudy}/sim/files`);
const data = await response.json();
if (data.success) {
// Update sim folder path
document.getElementById('simFolderPath').textContent = data.sim_folder;
// Render files list
const filesList = document.getElementById('simFilesList');
if (data.files.length === 0) {
filesList.innerHTML = '
No files yet. Drop your .sim and .prt files in the folder.
';
} else {
const html = data.files.map(file => `
${file.name}
${(file.size / 1024).toFixed(1)} KB
`).join('');
filesList.innerHTML = html;
}
// Enable/disable explore button
document.getElementById('exploreBtn').disabled = !data.has_sim_file;
}
} catch (error) {
showError('Failed to load sim files: ' + error.message);
}
}
async function exploreSimFile() {
if (!currentConfigStudy) return;
try {
showSuccess('Exploring .sim file with NX... This may take a minute.');
const response = await fetch(`${API_BASE}/study/${currentConfigStudy}/explore`, {
method: 'POST'
});
const data = await response.json();
if (data.success) {
extractedExpressions = data.expressions;
displayExpressions(data.expressions);
showSuccess('Expression extraction complete!');
} else {
showError('Failed to explore .sim file: ' + data.error);
}
} catch (error) {
showError('Connection error: ' + error.message);
}
}
function displayExpressions(expressionsData) {
const container = document.getElementById('expressionsList');
container.style.display = 'block';
const expressions = expressionsData.expressions_by_part;
const metadata = expressionsData.metadata;
let html = `
Expressions Found: ${metadata.total_expressions}
(${metadata.variable_candidates} potential design variables)
`;
// Display expressions by part
for (const [partName, exprs] of Object.entries(expressions)) {
if (exprs.length === 0) continue;
html += `${partName}
`;
exprs.forEach(expr => {
const isCandidate = expr.is_variable_candidate ? '✓' : '';
html += `
${isCandidate} ${expr.name}
Value: ${expr.value} ${expr.units} | Formula: ${expr.formula}
`;
});
}
container.innerHTML = html;
}
function selectExpressionForVariable(partName, exprName) {
// Find the expression
const expressions = extractedExpressions.expressions_by_part[partName];
const expr = expressions.find(e => e.name === exprName);
if (!expr) return;
// Add to design variables
addDesignVariableFromExpression(expr);
}
function addDesignVariableFromExpression(expr) {
const variable = {
name: expr.name,
min: expr.value * 0.8, // 20% below current
max: expr.value * 1.2, // 20% above current
units: expr.units
};
studyConfiguration.design_variables.push(variable);
renderDesignVariablesConfig();
}
function addDesignVariable() {
const variable = {
name: `variable_${studyConfiguration.design_variables.length + 1}`,
min: 0,
max: 100,
units: 'mm'
};
studyConfiguration.design_variables.push(variable);
renderDesignVariablesConfig();
}
function renderDesignVariablesConfig() {
const container = document.getElementById('designVariablesConfig');
if (studyConfiguration.design_variables.length === 0) {
container.innerHTML = 'No design variables yet
';
return;
}
const html = studyConfiguration.design_variables.map((variable, index) => `
`).join('');
container.innerHTML = html;
}
function updateDesignVariable(index, field, value) {
studyConfiguration.design_variables[index][field] = value;
}
function removeDesignVariable(index) {
studyConfiguration.design_variables.splice(index, 1);
renderDesignVariablesConfig();
}
// ====================
// Objectives Configuration
// ====================
function addObjective() {
const objective = {
name: `objective_${studyConfiguration.objectives.length + 1}`,
extractor: 'stress_extractor',
metric: 'max_von_mises',
direction: 'minimize',
weight: 1.0,
units: 'MPa'
};
studyConfiguration.objectives.push(objective);
renderObjectivesConfig();
}
function renderObjectivesConfig() {
const container = document.getElementById('objectivesConfig');
if (studyConfiguration.objectives.length === 0) {
container.innerHTML = 'No objectives yet
';
return;
}
const html = studyConfiguration.objectives.map((objective, index) => `
`).join('');
container.innerHTML = html;
}
function updateObjective(index, field, value) {
studyConfiguration.objectives[index][field] = value;
}
function removeObjective(index) {
studyConfiguration.objectives.splice(index, 1);
renderObjectivesConfig();
}
// ====================
// Constraints Configuration
// ====================
function addConstraint() {
const constraint = {
name: `constraint_${studyConfiguration.constraints.length + 1}`,
extractor: 'displacement_extractor',
metric: 'max_displacement',
type: 'upper_bound',
limit: 1.0,
units: 'mm'
};
studyConfiguration.constraints.push(constraint);
renderConstraintsConfig();
}
function renderConstraintsConfig() {
const container = document.getElementById('constraintsConfig');
if (studyConfiguration.constraints.length === 0) {
container.innerHTML = 'No constraints yet
';
return;
}
const html = studyConfiguration.constraints.map((constraint, index) => `
`).join('');
container.innerHTML = html;
}
function updateConstraint(index, field, value) {
studyConfiguration.constraints[index][field] = value;
}
function removeConstraint(index) {
studyConfiguration.constraints.splice(index, 1);
renderConstraintsConfig();
}
// ====================
// Save Configuration
// ====================
async function saveStudyConfiguration() {
if (!currentConfigStudy) return;
// Gather optimization settings
studyConfiguration.optimization_settings = {
n_trials: parseInt(document.getElementById('nTrials').value) || 50,
sampler: document.getElementById('samplerType').value,
n_startup_trials: parseInt(document.getElementById('startupTrials').value) || 20
};
try {
const response = await fetch(`${API_BASE}/study/${currentConfigStudy}/config`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(studyConfiguration)
});
const data = await response.json();
if (data.success) {
showSuccess('Configuration saved successfully!');
} else {
showError('Failed to save configuration: ' + data.error);
}
} catch (error) {
showError('Connection error: ' + error.message);
}
}
async function loadExistingConfig() {
if (!currentConfigStudy) return;
try {
const response = await fetch(`${API_BASE}/study/${currentConfigStudy}/config`);
const data = await response.json();
if (data.success && data.config) {
studyConfiguration = data.config;
// Render loaded configuration
renderDesignVariablesConfig();
renderObjectivesConfig();
renderConstraintsConfig();
// Set optimization settings
if (data.config.optimization_settings) {
document.getElementById('nTrials').value = data.config.optimization_settings.n_trials || 50;
document.getElementById('samplerType').value = data.config.optimization_settings.sampler || 'TPE';
document.getElementById('startupTrials').value = data.config.optimization_settings.n_startup_trials || 20;
}
}
} catch (error) {
console.error('Failed to load existing config:', error);
}
}
// ====================
// Utility Functions
// ====================
function openSimFolder() {
if (!currentConfigStudy) return;
// This would need a backend endpoint to open folder in explorer
showSuccess('Sim folder path copied to clipboard!');
}
function backToStudyList() {
document.getElementById('studyConfig').style.display = 'none';
document.getElementById('welcomeScreen').style.display = 'block';
currentConfigStudy = null;
extractedExpressions = null;
}