download
raw
37.6 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>SimSite - Applications de Simulation</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* { box-sizing: border-box; }
body { font-family: 'Segoe UI', sans-serif; margin: 0; padding: 0; background: #0a0a1a; color: #eee; min-height: 100vh; }
/* Navigation */
.navbar {
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
padding: 15px 30px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #0f3460;
position: sticky;
top: 0;
z-index: 100;
}
.navbar-brand {
font-size: 1.5em;
font-weight: bold;
color: #00d4ff;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
}
.navbar-brand:hover { opacity: 0.8; }
.nav-breadcrumb {
color: #888;
font-size: 0.9em;
}
.nav-breadcrumb span { color: #00d4ff; }
/* Views */
.view { display: none; padding: 30px; max-width: 1400px; margin: 0 auto; }
.view.active { display: block; }
/* Home Page */
.home-header {
text-align: center;
padding: 60px 20px;
background: linear-gradient(135deg, #1a1a2e 0%, #0f3460 100%);
margin: -30px -30px 30px -30px;
border-bottom: 2px solid #00d4ff;
}
.home-header h1 {
font-size: 3em;
margin: 0;
background: linear-gradient(135deg, #00d4ff, #00ff88);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.home-header p {
color: #aaa;
font-size: 1.2em;
margin-top: 10px;
}
/* App Cards */
.apps-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 30px;
padding: 20px 0;
}
.app-card {
background: linear-gradient(145deg, #1a1a2e, #16213e);
border: 1px solid #0f3460;
border-radius: 16px;
padding: 30px;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.app-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--card-color), transparent);
}
.app-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 40px rgba(0, 212, 255, 0.2);
border-color: var(--card-color);
}
.app-card-icon {
font-size: 3em;
margin-bottom: 15px;
}
.app-card h2 {
color: #fff;
margin: 0 0 10px 0;
font-size: 1.5em;
}
.app-card p {
color: #aaa;
margin: 0 0 20px 0;
line-height: 1.6;
}
.app-card-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.app-card-tag {
background: rgba(0, 212, 255, 0.1);
color: #00d4ff;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.8em;
}
.app-card-arrow {
position: absolute;
bottom: 30px;
right: 30px;
font-size: 1.5em;
color: var(--card-color);
opacity: 0;
transform: translateX(-10px);
transition: all 0.3s ease;
}
.app-card:hover .app-card-arrow {
opacity: 1;
transform: translateX(0);
}
/* App-specific styles */
.section-title {
color: #00d4ff;
border-bottom: 2px solid #00d4ff;
padding-bottom: 10px;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.panel {
background: #16213e;
padding: 25px;
border-radius: 12px;
border: 1px solid #0f3460;
margin-bottom: 20px;
}
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; color: #00d4ff; font-weight: 500; }
.form-group input[type="text"],
.form-group input[type="number"],
.form-group textarea {
width: 100%;
padding: 12px;
border-radius: 8px;
border: 1px solid #0f3460;
background: #1a1a2e;
color: #fff;
font-size: 1em;
transition: border-color 0.3s;
}
.form-group input:focus, .form-group textarea:focus {
outline: none;
border-color: #00d4ff;
}
.form-group input[type="range"] { width: 100%; accent-color: #00d4ff; }
.form-group input[type="file"] { color: #00d4ff; }
.form-group textarea { font-family: 'Consolas', monospace; }
button, .btn {
background: linear-gradient(135deg, #00d4ff, #0099cc);
color: #1a1a2e;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-weight: bold;
font-size: 1em;
transition: all 0.2s;
}
button:hover, .btn:hover { transform: scale(1.02); box-shadow: 0 5px 20px rgba(0, 212, 255, 0.3); }
button:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.btn-secondary { background: #0f3460; color: #00d4ff; }
.btn-danger { background: #ff1744; color: #fff; }
/* Grid layouts */
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.grid-2-1 { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; }
@media (max-width: 900px) {
.grid-2, .grid-2-1 { grid-template-columns: 1fr; }
}
/* Table */
table { width: 100%; border-collapse: collapse; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #0f3460; }
th { color: #00d4ff; font-weight: 600; }
tr:hover { background: rgba(0, 212, 255, 0.05); }
/* Status badges */
.badge { padding: 4px 12px; border-radius: 20px; font-weight: bold; font-size: 0.85em; }
.badge-success { background: #00c853; color: #1a1a2e; }
.badge-warning { background: #ffab00; color: #1a1a2e; }
.badge-danger { background: #ff1744; color: #fff; }
/* Results */
.result-card {
background: #1a1a2e;
padding: 20px;
border-radius: 12px;
border: 1px solid #0f3460;
}
.result-card h3 { color: #00d4ff; margin: 0 0 15px 0; }
.result-value { font-size: 2.5em; color: #00d4ff; font-weight: bold; }
.chart-container { height: 350px; position: relative; }
/* Alerts */
.alert { padding: 15px 20px; border-radius: 8px; margin-bottom: 15px; }
.alert-error { background: rgba(255, 23, 68, 0.2); border: 1px solid #ff1744; color: #ff6b6b; }
.alert-success { background: rgba(0, 200, 83, 0.2); border: 1px solid #00c853; color: #69f0ae; }
.alert-info { background: rgba(0, 212, 255, 0.1); border: 1px solid #0f3460; color: #00d4ff; }
/* Tabs */
.tabs { display: flex; gap: 5px; margin-bottom: 20px; }
.tab { padding: 10px 20px; background: #0f3460; color: #00d4ff; border: none; border-radius: 8px 8px 0 0; cursor: pointer; transition: all 0.2s; }
.tab.active { background: #00d4ff; color: #1a1a2e; }
.tab-content { display: none; }
.tab-content.active { display: block; }
/* DV specific */
.dv-safe { color: #00c853; font-weight: bold; }
.dv-unsafe { color: #ff1744; font-weight: bold; }
/* Equation box */
.equation-box {
background: #0f3460;
padding: 20px;
border-radius: 12px;
text-align: center;
font-size: 1.1em;
margin-bottom: 20px;
border-left: 4px solid #00d4ff;
}
.equation-box .equation { font-size: 1.3em; margin: 10px 0; color: #00d4ff; }
.equation-box small { color: #888; }
/* Animation */
.animation-container { text-align: center; }
.animation-controls { margin-top: 15px; display: flex; justify-content: center; gap: 10px; }
.animation-controls button { width: auto; }
/* Hidden utility */
.hidden { display: none !important; }
</style>
</head>
<body>
<!-- Navigation Bar -->
<nav class="navbar">
<div class="navbar-brand" onclick="navigateTo('home')">
<span>SimSite</span>
</div>
<div class="nav-breadcrumb" id="breadcrumb"></div>
</nav>
<!-- HOME VIEW -->
<div id="view-home" class="view active">
<div class="home-header">
<h1>SimSite</h1>
<p>Plateforme de simulation numerique et d'analyse</p>
</div>
<div class="apps-grid">
<!-- FEniCS App Card -->
<div class="app-card" style="--card-color: #00d4ff;" onclick="navigateTo('fenics')">
<div class="app-card-icon">🔬</div>
<h2>Simulation FEniCS</h2>
<p>Resolution d'equations aux derivees partielles (EDP). Simulez la diffusion thermique sur un domaine 2D avec visualisation en temps reel.</p>
<div class="app-card-tags">
<span class="app-card-tag">EDP</span>
<span class="app-card-tag">Diffusion</span>
<span class="app-card-tag">Elements Finis</span>
</div>
<div class="app-card-arrow"></div>
</div>
<!-- Dang Van App Card -->
<div class="app-card" style="--card-color: #ff6b6b;" onclick="navigateTo('dangvan')">
<div class="app-card-icon">⚙️</div>
<h2>Analyse Dang Van</h2>
<p>Critere de fatigue multiaxiale pour l'analyse de la tenue en fatigue des pieces mecaniques soumises a des chargements complexes.</p>
<div class="app-card-tags">
<span class="app-card-tag">Fatigue</span>
<span class="app-card-tag">Multiaxial</span>
<span class="app-card-tag">Mecanique</span>
</div>
<div class="app-card-arrow"></div>
</div>
<!-- Placeholder for future apps -->
<div class="app-card" style="--card-color: #888; opacity: 0.5; cursor: default;">
<div class="app-card-icon">🚀</div>
<h2>Prochainement...</h2>
<p>D'autres applications de simulation seront ajoutees. Restez connectes pour decouvrir de nouveaux outils d'analyse.</p>
<div class="app-card-tags">
<span class="app-card-tag">A venir</span>
</div>
</div>
</div>
</div>
<!-- FENICS VIEW -->
<div id="view-fenics" class="view">
<h1 class="section-title">🔬 Simulation FEniCS</h1>
<div class="equation-box">
<strong>Equation de diffusion transitoire</strong>
<div class="equation">∂u/∂t = D · ∇²u + Q</div>
<small>
u(x,y,t) = champ de temperature | D = coefficient de diffusion | Q = terme source<br>
Conditions: u(x,y,0) = 0 | u = 0 sur ∂Ω (bords)
</small>
</div>
<div class="grid-2">
<!-- Form Panel -->
<div class="panel">
<h2 class="section-title">Nouvelle Simulation</h2>
<form id="simForm">
<div class="form-group">
<label>Nom de la simulation</label>
<input type="text" id="simName" placeholder="Ma simulation">
</div>
<div class="form-group">
<label>Resolution du maillage: <span id="meshVal">32</span></label>
<input type="range" id="meshResolution" min="8" max="64" step="8" value="32" oninput="document.getElementById('meshVal').textContent = this.value">
</div>
<div class="form-group">
<label>Coefficient de diffusion D: <span id="diffVal">0.1</span></label>
<input type="range" id="diffCoef" min="0.01" max="0.5" step="0.01" value="0.1" oninput="document.getElementById('diffVal').textContent = this.value">
</div>
<div class="form-group">
<label>Terme source Q: <span id="sourceVal">1.0</span></label>
<input type="range" id="sourceTerm" min="0.1" max="3.0" step="0.1" value="1.0" oninput="document.getElementById('sourceVal').textContent = this.value">
</div>
<div class="form-group">
<label>Temps final: <span id="timeVal">1.0</span>s</label>
<input type="range" id="timeFinal" min="0.1" max="2.0" step="0.1" value="1.0" oninput="document.getElementById('timeVal').textContent = this.value">
</div>
<div class="form-group">
<label>Nombre de pas de temps: <span id="stepsVal">20</span></label>
<input type="range" id="numSteps" min="10" max="50" step="5" value="20" oninput="document.getElementById('stepsVal').textContent = this.value">
</div>
<button type="submit" id="submitBtn">Lancer la simulation</button>
</form>
<div id="formError" class="alert alert-error hidden"></div>
</div>
<!-- Simulations List -->
<div class="panel">
<h2 class="section-title">
Simulations recentes
<button class="btn-secondary" style="margin-left: auto; padding: 8px 16px;" onclick="loadSimulations()">Rafraichir</button>
</h2>
<table>
<thead>
<tr><th>ID</th><th>Nom</th><th>Status</th><th>Date</th></tr>
</thead>
<tbody id="simTable">
<tr><td colspan="4">Chargement...</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Results Section (hidden by default) -->
<div id="resultSection" class="panel hidden">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h2 class="section-title" style="margin: 0; border: none;">Resultats - Simulation #<span id="resultId"></span></h2>
<button class="btn-danger" onclick="closeResults()">Fermer</button>
</div>
<div class="grid-2" style="margin-top: 20px;">
<div class="result-card">
<h3>Valeur maximale</h3>
<div class="result-value" id="resultMax">-</div>
</div>
<div class="result-card">
<h3>Valeur moyenne</h3>
<div class="result-value" id="resultMean">-</div>
</div>
</div>
<div class="grid-2" style="margin-top: 20px;">
<div class="result-card">
<h3>Evolution temporelle</h3>
<div class="chart-container"><canvas id="timeChart"></canvas></div>
</div>
<div class="result-card animation-container">
<h3>Animation de la solution</h3>
<canvas id="animationCanvas" width="300" height="300"></canvas>
<div class="animation-controls">
<button onclick="playAnimation()">Play</button>
<button onclick="pauseAnimation()">Pause</button>
<button onclick="resetAnimation()">Reset</button>
</div>
<p>Temps: <span id="animTime">0.00</span> / <span id="animTotal">1.00</span>s</p>
</div>
</div>
</div>
</div>
<!-- DANG VAN VIEW -->
<div id="view-dangvan" class="view">
<h1 class="section-title">⚙️ Analyse de Fatigue - Critere de Dang Van</h1>
<div class="equation-box">
<strong>Critere de Dang Van</strong>
<div class="equation">τ_max + a · σ_H ≤ b</div>
<small>
τ_max = contrainte de cisaillement maximale (balayage des facettes)<br>
σ_H = pression hydrostatique = (σ_xx + σ_yy + σ_zz) / 3<br>
a = pente du critere | b = limite en cisaillement alterne
</small>
</div>
<div class="grid-2-1">
<div class="panel">
<h2 class="section-title">Donnees de contrainte</h2>
<div class="tabs">
<button class="tab active" onclick="switchDvTab('json')">Saisie JSON</button>
<button class="tab" onclick="switchDvTab('file')">Import Fichier</button>
</div>
<div id="dvTabJson" class="tab-content active">
<div class="form-group">
<label>Serie de tenseurs de contrainte (format Voigt 6 composantes)</label>
<textarea id="dvStress" rows="8" placeholder='Exemple:
[[100, 0, 0, 0, 0, 0],
[50, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[-50, 0, 0, 0, 0, 0],
[-100, 0, 0, 0, 0, 0]]
Format: [σ_xx, σ_yy, σ_zz, σ_xy, σ_xz, σ_yz] en MPa'></textarea>
</div>
</div>
<div id="dvTabFile" class="tab-content">
<div class="form-group">
<label>Fichier CSV ou TXT</label>
<input type="file" id="dvFile" accept=".csv,.txt,.dat">
</div>
<div class="alert alert-info">
<strong>Format attendu:</strong><br>
- 6 colonnes: σ_xx, σ_yy, σ_zz, σ_xy, σ_xz, σ_yz<br>
- Une ligne = un pas de temps<br>
- Separateurs: virgule, point-virgule, tabulation ou espace<br>
- Lignes commencant par # ou // ignorees
</div>
<div id="dvFilePreview" class="hidden" style="margin-top: 15px;">
<label>Apercu (<span id="dvFileCount">0</span> pas de temps charges)</label>
<textarea id="dvFileData" rows="4" readonly style="background: #0f3460;"></textarea>
</div>
</div>
</div>
<div class="panel">
<h2 class="section-title">Parametres du critere</h2>
<div class="form-group">
<label>Parametre a (pente)</label>
<input type="number" id="dvA" value="0.3" step="0.01">
</div>
<div class="form-group">
<label>Parametre b (limite en MPa)</label>
<input type="number" id="dvB" value="120.0" step="1">
</div>
<button id="dvBtn" onclick="calculateDangVan()" style="width: 100%; margin-top: 20px;">
Calculer
</button>
</div>
</div>
<div id="dvError" class="alert alert-error hidden"></div>
<!-- Results -->
<div id="dvResults" class="hidden">
<div class="grid-2" style="margin-top: 20px;">
<div class="result-card">
<h3>Resultat de l'analyse</h3>
<table>
<tr><td>DV_max</td><td><strong id="dvMax">-</strong> MPa</td></tr>
<tr><td>Marge (b - DV_max)</td><td><strong id="dvMargin">-</strong> MPa</td></tr>
<tr><td>Verdict</td><td><span id="dvSafe">-</span></td></tr>
<tr><td>Point critique</td><td>Pas de temps #<span id="dvCritical">-</span></td></tr>
</table>
</div>
<div class="result-card">
<h3>Interpretation</h3>
<div id="dvInterpretation" style="color: #aaa; line-height: 1.8;"></div>
</div>
</div>
<div class="panel" style="margin-top: 20px;">
<h3 class="section-title">Diagramme de Dang Van</h3>
<div class="chart-container" style="height: 450px;">
<canvas id="dvChart"></canvas>
</div>
</div>
</div>
</div>
<script>
// ============================================
// NAVIGATION
// ============================================
const views = {
home: { title: '', breadcrumb: '' },
fenics: { title: 'Simulation FEniCS', breadcrumb: '<span>Accueil</span> / Simulation FEniCS' },
dangvan: { title: 'Analyse Dang Van', breadcrumb: '<span>Accueil</span> / Analyse Dang Van' }
};
function navigateTo(viewId) {
// Hide all views
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
// Show target view
document.getElementById('view-' + viewId).classList.add('active');
// Update breadcrumb
document.getElementById('breadcrumb').innerHTML = views[viewId].breadcrumb;
// Scroll to top
window.scrollTo(0, 0);
// Load data if needed
if (viewId === 'fenics') loadSimulations();
}
// ============================================
// GLOBALS
// ============================================
const API_URL = '/api';
let timeChart = null;
let dvChart = null;
let animationId = null;
let animFrame = 0;
let animData = [];
let isAnimating = false;
let animFrameImages = [];
let useImageFrames = false;
let dvStressFromFile = null;
// ============================================
// FENICS SIMULATION
// ============================================
async function loadSimulations() {
try {
const res = await fetch(API_URL + '/simulations/');
if (!res.ok) throw new Error('Erreur serveur: ' + res.status);
const data = await res.json();
const tbody = document.getElementById('simTable');
if (data.length === 0) {
tbody.innerHTML = '<tr><td colspan="4">Aucune simulation</td></tr>';
} else {
tbody.innerHTML = data.slice(0, 10).map(sim => {
const badge = sim.status === 'completed' ? 'badge-success' :
sim.status === 'running' ? 'badge-warning' : 'badge-danger';
return `<tr onclick="showResults(${sim.id})" style="cursor:pointer">
<td>#${sim.id}</td>
<td>${sim.name || 'Sans nom'}</td>
<td><span class="badge ${badge}">${sim.status}</span></td>
<td>${new Date(sim.created_at).toLocaleDateString()}</td>
</tr>`;
}).join('');
}
} catch (err) {
document.getElementById('simTable').innerHTML = `<tr><td colspan="4" class="alert-error">${err.message}</td></tr>`;
}
}
async function showResults(id) {
try {
const res = await fetch(API_URL + '/simulations/' + id + '/');
if (!res.ok) throw new Error('Erreur serveur');
const sim = await res.json();
if (sim.status !== 'completed') {
alert('Simulation non terminee (status: ' + sim.status + ')');
return;
}
if (!sim.result_summary) {
alert('Pas de resultats disponibles');
return;
}
document.getElementById('resultSection').classList.remove('hidden');
document.getElementById('resultId').textContent = sim.id;
document.getElementById('resultMax').textContent = (sim.result_summary.final_max || 0).toFixed(4);
document.getElementById('resultMean').textContent = (sim.result_summary.final_mean || 0).toFixed(4);
animData = sim.result_summary.time_series || [];
updateTimeChart(animData);
resetAnimation();
document.getElementById('resultSection').scrollIntoView({ behavior: 'smooth' });
} catch (err) {
alert('Erreur: ' + err.message);
}
}
function closeResults() {
document.getElementById('resultSection').classList.add('hidden');
stopAnimation();
}
function updateTimeChart(data) {
const ctx = document.getElementById('timeChart').getContext('2d');
if (timeChart) timeChart.destroy();
if (!data || data.length === 0) return;
timeChart = new Chart(ctx, {
type: 'line',
data: {
labels: data.map(d => (d.time || 0).toFixed(2)),
datasets: [{
label: 'Maximum',
data: data.map(d => d.max || 0),
borderColor: '#00d4ff',
backgroundColor: 'rgba(0, 212, 255, 0.1)',
fill: true,
tension: 0.3
}, {
label: 'Moyenne',
data: data.map(d => d.mean || 0),
borderColor: '#ff6b6b',
backgroundColor: 'rgba(255, 107, 107, 0.1)',
fill: true,
tension: 0.3
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { labels: { color: '#fff' } } },
scales: {
x: { ticks: { color: '#888' }, grid: { color: 'rgba(255,255,255,0.1)' }, title: { display: true, text: 'Temps (s)', color: '#00d4ff' } },
y: { ticks: { color: '#888' }, grid: { color: 'rgba(255,255,255,0.1)' }, title: { display: true, text: 'Valeur', color: '#00d4ff' } }
}
}
});
}
// Animation
function stopAnimation() {
if (animationId) cancelAnimationFrame(animationId);
isAnimating = false;
}
function playAnimation() {
if (animData.length === 0) return;
isAnimating = true;
animate();
}
function pauseAnimation() { isAnimating = false; }
function resetAnimation() {
stopAnimation();
animFrame = 0;
drawHeatmap(null);
document.getElementById('animTime').textContent = '0.00';
document.getElementById('animTotal').textContent = animData.length > 0 ? (animData[animData.length-1].time || 0).toFixed(2) : '0.00';
}
function animate() {
if (!isAnimating || animFrame >= animData.length) {
isAnimating = false;
return;
}
const frameData = animData[animFrame];
drawHeatmap(frameData);
document.getElementById('animTime').textContent = (frameData.time || 0).toFixed(2);
animFrame++;
animationId = requestAnimationFrame(() => setTimeout(animate, 100));
}
function drawHeatmap(frameData) {
const canvas = document.getElementById('animationCanvas');
const ctx = canvas.getContext('2d');
const size = canvas.width;
const gridSize = 32;
const cellSize = size / gridSize;
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, size, size);
if (!frameData) {
ctx.fillStyle = '#888';
ctx.font = '14px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Cliquez Play pour demarrer', size/2, size/2);
return;
}
const maxVal = Math.max(...animData.map(d => d.max || 0.001));
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
const distance = Math.sqrt((i - gridSize/2)**2 + (j - gridSize/2)**2);
const normalizedDist = Math.min(distance / (gridSize/2), 1);
const heatValue = Math.max(0, 1 - normalizedDist) * ((frameData.max || 0) / maxVal);
const r = Math.floor(255 * heatValue);
const g = Math.floor(100 * heatValue);
const b = Math.floor(255 * (1 - heatValue));
ctx.fillStyle = `rgb(${r},${g},${b})`;
ctx.fillRect(j * cellSize, (gridSize - 1 - i) * cellSize, cellSize, cellSize);
}
}
}
// Form submission
document.getElementById('simForm').addEventListener('submit', async (e) => {
e.preventDefault();
const btn = document.getElementById('submitBtn');
const errDiv = document.getElementById('formError');
errDiv.classList.add('hidden');
btn.disabled = true;
btn.textContent = 'Calcul en cours...';
const params = {
mesh_resolution: parseInt(document.getElementById('meshResolution').value),
diffusion_coefficient: parseFloat(document.getElementById('diffCoef').value),
source_term: parseFloat(document.getElementById('sourceTerm').value),
time_final: parseFloat(document.getElementById('timeFinal').value),
num_steps: parseInt(document.getElementById('numSteps').value)
};
try {
const res = await fetch(API_URL + '/simulations/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: document.getElementById('simName').value || 'Simulation', parameters: params })
});
if (!res.ok) throw new Error('Erreur creation: ' + res.status);
btn.textContent = 'Simulation lancee!';
setTimeout(() => {
loadSimulations();
btn.disabled = false;
btn.textContent = 'Lancer la simulation';
}, 1500);
} catch (err) {
errDiv.textContent = err.message;
errDiv.classList.remove('hidden');
btn.disabled = false;
btn.textContent = 'Lancer la simulation';
}
});
// ============================================
// DANG VAN
// ============================================
function switchDvTab(tab) {
document.querySelectorAll('.tabs .tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
if (tab === 'json') {
document.querySelector('.tabs .tab:first-child').classList.add('active');
document.getElementById('dvTabJson').classList.add('active');
dvStressFromFile = null;
} else {
document.querySelector('.tabs .tab:last-child').classList.add('active');
document.getElementById('dvTabFile').classList.add('active');
}
}
function parseStressFile(content) {
const lines = content.trim().split('\n');
const data = [];
for (let line of lines) {
line = line.trim();
if (!line || line.startsWith('#') || line.startsWith('//') || line.match(/^[a-zA-Z]/)) continue;
let values;
if (line.includes(',')) values = line.split(',');
else if (line.includes(';')) values = line.split(';');
else if (line.includes('\t')) values = line.split('\t');
else values = line.split(/\s+/);
const nums = values.map(v => parseFloat(v.trim())).filter(n => !isNaN(n));
if (nums.length >= 6) data.push(nums.slice(0, 6));
}
return data;
}
document.getElementById('dvFile').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const content = await file.text();
const parsed = parseStressFile(content);
if (parsed.length === 0) {
alert('Aucune donnee valide trouvee');
return;
}
dvStressFromFile = parsed;
document.getElementById('dvFilePreview').classList.remove('hidden');
document.getElementById('dvFileCount').textContent = parsed.length;
document.getElementById('dvFileData').value = parsed.slice(0, 5).map(r => r.map(v => v.toFixed(1)).join(', ')).join('\n') + (parsed.length > 5 ? '\n...' : '');
} catch (err) {
alert('Erreur lecture: ' + err.message);
}
});
async function calculateDangVan() {
const btn = document.getElementById('dvBtn');
const errDiv = document.getElementById('dvError');
errDiv.classList.add('hidden');
btn.disabled = true;
btn.textContent = 'Calcul en cours...';
let stress;
const jsonTab = document.getElementById('dvTabJson').classList.contains('active');
if (jsonTab) {
try {
const raw = document.getElementById('dvStress').value.trim();
if (!raw) throw new Error('Entrez des donnees JSON');
stress = JSON.parse(raw);
} catch (e) {
errDiv.textContent = 'JSON invalide: ' + e.message;
errDiv.classList.remove('hidden');
btn.disabled = false;
btn.textContent = 'Calculer';
return;
}
} else {
if (!dvStressFromFile || dvStressFromFile.length === 0) {
errDiv.textContent = 'Chargez un fichier CSV/TXT valide';
errDiv.classList.remove('hidden');
btn.disabled = false;
btn.textContent = 'Calculer';
return;
}
stress = dvStressFromFile;
}
const a = parseFloat(document.getElementById('dvA').value);
const b = parseFloat(document.getElementById('dvB').value);
try {
const res = await fetch(API_URL + '/dangvan/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ stress_series: stress, a, b })
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || 'Erreur serveur');
displayDangVanResults(data);
} catch (err) {
errDiv.textContent = err.message;
errDiv.classList.remove('hidden');
} finally {
btn.disabled = false;
btn.textContent = 'Calculer';
}
}
function displayDangVanResults(data) {
document.getElementById('dvResults').classList.remove('hidden');
document.getElementById('dvMax').textContent = data.dv_max.toFixed(2);
document.getElementById('dvMargin').textContent = data.margin.toFixed(2);
document.getElementById('dvCritical').textContent = data.index_max;
const safeEl = document.getElementById('dvSafe');
const interpEl = document.getElementById('dvInterpretation');
if (data.safe) {
safeEl.innerHTML = '<span class="dv-safe">SECURITE OK</span>';
interpEl.innerHTML = `
<p>La piece est <strong style="color:#00c853">securisee</strong> vis-a-vis de la fatigue multiaxiale.</p>
<p>Tous les points de chargement se situent sous la droite de Dang Van.</p>
<p>Marge de securite: <strong>${data.margin.toFixed(2)} MPa</strong></p>
`;
} else {
safeEl.innerHTML = '<span class="dv-unsafe">RISQUE DE FATIGUE</span>';
interpEl.innerHTML = `
<p>La piece presente un <strong style="color:#ff1744">risque de fatigue</strong>.</p>
<p>Un ou plusieurs points de chargement depassent la limite de Dang Van.</p>
<p>Depassement maximal: <strong>${Math.abs(data.margin).toFixed(2)} MPa</strong> au pas de temps #${data.index_max}</p>
`;
}
updateDvChart(data);
document.getElementById('dvResults').scrollIntoView({ behavior: 'smooth' });
}
function updateDvChart(data) {
const ctx = document.getElementById('dvChart').getContext('2d');
if (dvChart) dvChart.destroy();
const points = data.points || [];
const criterionLine = data.criterion_line || [];
const scatterData = points.map(p => ({ x: p.sigma_H, y: p.tau_max }));
const lineData = criterionLine.map(p => ({ x: p.sigma_H, y: p.tau }));
// Find safe/unsafe points
const safePoints = points.filter(p => p.dv <= data.b).map(p => ({ x: p.sigma_H, y: p.tau_max }));
const unsafePoints = points.filter(p => p.dv > data.b).map(p => ({ x: p.sigma_H, y: p.tau_max }));
dvChart = new Chart(ctx, {
type: 'scatter',
data: {
datasets: [
{
label: 'Points securises',
data: safePoints,
backgroundColor: '#00c853',
borderColor: '#00c853',
pointRadius: 6,
pointHoverRadius: 8
},
{
label: 'Points critiques',
data: unsafePoints,
backgroundColor: '#ff1744',
borderColor: '#ff1744',
pointRadius: 6,
pointHoverRadius: 8
},
{
label: `Limite: τ = ${data.b} - ${data.a}·σH`,
data: lineData,
type: 'line',
borderColor: '#ffab00',
borderWidth: 3,
borderDash: [8, 4],
fill: false,
pointRadius: 0
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { labels: { color: '#fff', font: { size: 12 } } },
title: { display: true, text: 'Diagramme de Dang Van (τ_max vs σ_H)', color: '#00d4ff', font: { size: 16 } }
},
scales: {
x: {
type: 'linear',
ticks: { color: '#888' },
grid: { color: 'rgba(255,255,255,0.1)' },
title: { display: true, text: 'Pression hydrostatique σH (MPa)', color: '#00d4ff', font: { size: 14 } }
},
y: {
ticks: { color: '#888' },
grid: { color: 'rgba(255,255,255,0.1)' },
title: { display: true, text: 'Cisaillement max τ_max (MPa)', color: '#00d4ff', font: { size: 14 } }
}
}
}
});
}
// ============================================
// INIT
// ============================================
console.log('SimSite loaded');
</script>
</body>
</html>

Xet Storage Details

Size:
37.6 kB
·
Xet hash:
0ab486f87a8aa7a5da99587360da5f82341daf749289d1e2795034a1936177bb

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.