Spaces:
Sleeping
Sleeping
Upload app.py
Browse files
app.py
CHANGED
|
@@ -145,57 +145,49 @@ def run(fibers: Image.Image, rings: Image.Image, num_steps: int):
|
|
| 145 |
|
| 146 |
# Construire le HTML/JS pour le visualiseur Three.js
|
| 147 |
html = f"""
|
| 148 |
-
<div id="viewer" style="width:100%;height:480px; border:1px solid #ddd;"
|
| 149 |
-
<
|
|
|
|
|
|
|
|
|
|
| 150 |
<script src="https://unpkg.com/three@0.152.2/build/three.min.js"></script>
|
| 151 |
<script src="https://unpkg.com/three@0.152.2/examples/js/controls/OrbitControls.js"></script>
|
| 152 |
<script>
|
| 153 |
(function() {{
|
| 154 |
-
const images = {data_uris!r};
|
| 155 |
-
|
| 156 |
-
// cleanup previous canvas if any
|
| 157 |
const container = document.getElementById('viewer');
|
| 158 |
-
container.innerHTML = "";
|
| 159 |
-
|
| 160 |
-
const scene = new THREE.Scene();
|
| 161 |
-
const camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 1000);
|
| 162 |
-
camera.position.set(2.5, 2.0, 3.5);
|
| 163 |
-
|
| 164 |
-
const renderer = new THREE.WebGLRenderer({{antialias:true}});
|
| 165 |
-
renderer.setSize(container.clientWidth, container.clientHeight);
|
| 166 |
-
renderer.setPixelRatio(window.devicePixelRatio ? window.devicePixelRatio : 1);
|
| 167 |
-
container.appendChild(renderer.domElement);
|
| 168 |
-
|
| 169 |
-
// Lights (soft)
|
| 170 |
-
const hemi = new THREE.HemisphereLight(0xffffff, 0x444444, 1.0);
|
| 171 |
-
scene.add(hemi);
|
| 172 |
-
|
| 173 |
-
// Load textures from data URIs
|
| 174 |
-
const loader = new THREE.TextureLoader();
|
| 175 |
-
const texPromises = images.map((uri) => new Promise((res, rej) => {{
|
| 176 |
-
loader.load(uri, (tex) => {{ tex.flipY = false; res(tex); }}, undefined, rej);
|
| 177 |
-
}}));
|
| 178 |
-
|
| 179 |
-
Promise.all(texPromises).then((textures) => {{
|
| 180 |
-
// materials for box: order is [right, left, top, bottom, front, back]
|
| 181 |
-
const neutral = new THREE.MeshBasicMaterial({{ color:0xcccccc }});
|
| 182 |
-
const mats = [
|
| 183 |
-
new THREE.MeshBasicMaterial({{ map: textures[0] }}), // right
|
| 184 |
-
new THREE.MeshBasicMaterial({{ map: textures[1] }}), // left
|
| 185 |
-
neutral, // top
|
| 186 |
-
neutral, // bottom
|
| 187 |
-
new THREE.MeshBasicMaterial({{ map: textures[2] }}), // front
|
| 188 |
-
new THREE.MeshBasicMaterial({{ map: textures[3] }}) // back
|
| 189 |
-
];
|
| 190 |
-
|
| 191 |
-
// ensure correct filtering / orientation
|
| 192 |
-
mats.forEach(m => {{ if (m.map) {{ m.map.minFilter = THREE.LinearFilter; m.map.wrapS = THREE.ClampToEdgeWrapping; m.map.wrapT = THREE.ClampToEdgeWrapping; }} }});
|
| 193 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
const geometry = new THREE.BoxGeometry(1.6,1.6,1.6);
|
| 195 |
-
const
|
|
|
|
|
|
|
| 196 |
scene.add(cube);
|
| 197 |
|
| 198 |
-
// grid
|
| 199 |
const grid = new THREE.GridHelper(6, 12, 0x888888, 0x444444);
|
| 200 |
grid.position.y = -1.2;
|
| 201 |
scene.add(grid);
|
|
@@ -203,27 +195,85 @@ def run(fibers: Image.Image, rings: Image.Image, num_steps: int):
|
|
| 203 |
// controls
|
| 204 |
const controls = new THREE.OrbitControls(camera, renderer.domElement);
|
| 205 |
controls.enableDamping = true;
|
| 206 |
-
controls.dampingFactor = 0.
|
| 207 |
controls.target.set(0,0,0);
|
| 208 |
|
| 209 |
-
//
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
// animation loop
|
| 218 |
-
|
| 219 |
requestAnimationFrame(animate);
|
| 220 |
controls.update();
|
| 221 |
renderer.render(scene, camera);
|
| 222 |
-
}}
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
}})();
|
| 228 |
</script>
|
| 229 |
"""
|
|
|
|
| 145 |
|
| 146 |
# Construire le HTML/JS pour le visualiseur Three.js
|
| 147 |
html = f"""
|
| 148 |
+
<div id="viewer" style="width:100%;height:480px; border:1px solid #ddd; position:relative;background:#f6f6f6;">
|
| 149 |
+
<div id="viewer-msg" style="position:absolute;left:8px;top:8px;padding:6px 8px;background:rgba(255,255,255,0.9);border-radius:6px;font-size:12px;color:#333;z-index:2;">
|
| 150 |
+
Manipulez la souris pour tourner, molette pour zoomer.
|
| 151 |
+
</div>
|
| 152 |
+
</div>
|
| 153 |
<script src="https://unpkg.com/three@0.152.2/build/three.min.js"></script>
|
| 154 |
<script src="https://unpkg.com/three@0.152.2/examples/js/controls/OrbitControls.js"></script>
|
| 155 |
<script>
|
| 156 |
(function() {{
|
| 157 |
+
const images = {data_uris!r};
|
|
|
|
|
|
|
| 158 |
const container = document.getElementById('viewer');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
|
| 160 |
+
function safeLog(...args) {{ try{{console.log(...args)}}catch(e){{}} }}
|
| 161 |
+
|
| 162 |
+
function initScene() {{
|
| 163 |
+
const w = container.clientWidth || 800;
|
| 164 |
+
const h = container.clientHeight || 480;
|
| 165 |
+
|
| 166 |
+
const scene = new THREE.Scene();
|
| 167 |
+
const camera = new THREE.PerspectiveCamera(45, w/h, 0.1, 1000);
|
| 168 |
+
camera.position.set(2.5, 2.0, 3.5);
|
| 169 |
+
|
| 170 |
+
const renderer = new THREE.WebGLRenderer({{antialias:true}});
|
| 171 |
+
renderer.setSize(w, h);
|
| 172 |
+
renderer.domElement.style.width = '100%';
|
| 173 |
+
renderer.domElement.style.height = '100%';
|
| 174 |
+
container.appendChild(renderer.domElement);
|
| 175 |
+
|
| 176 |
+
// lights
|
| 177 |
+
const ambient = new THREE.AmbientLight(0xffffff, 0.7);
|
| 178 |
+
scene.add(ambient);
|
| 179 |
+
const dir = new THREE.DirectionalLight(0xffffff, 0.6);
|
| 180 |
+
dir.position.set(5,10,7);
|
| 181 |
+
scene.add(dir);
|
| 182 |
+
|
| 183 |
+
// fallback cube (visible immédiatement)
|
| 184 |
const geometry = new THREE.BoxGeometry(1.6,1.6,1.6);
|
| 185 |
+
const fallbackMats = [];
|
| 186 |
+
for (let i=0;i<6;i++) fallbackMats.push(new THREE.MeshStandardMaterial({{color:0x999999}}));
|
| 187 |
+
const cube = new THREE.Mesh(geometry, fallbackMats);
|
| 188 |
scene.add(cube);
|
| 189 |
|
| 190 |
+
// grid for context
|
| 191 |
const grid = new THREE.GridHelper(6, 12, 0x888888, 0x444444);
|
| 192 |
grid.position.y = -1.2;
|
| 193 |
scene.add(grid);
|
|
|
|
| 195 |
// controls
|
| 196 |
const controls = new THREE.OrbitControls(camera, renderer.domElement);
|
| 197 |
controls.enableDamping = true;
|
| 198 |
+
controls.dampingFactor = 0.08;
|
| 199 |
controls.target.set(0,0,0);
|
| 200 |
|
| 201 |
+
// attempt to load textures, but keep fallback if it fails
|
| 202 |
+
const loader = new THREE.TextureLoader();
|
| 203 |
+
Promise.all(images.map(uri => new Promise((res, rej) => {{
|
| 204 |
+
try {{
|
| 205 |
+
loader.load(uri, tex => {{ tex.flipY = false; tex.generateMipmaps = true; res(tex); }}, undefined, err => rej(err));
|
| 206 |
+
}} catch(e) {{ rej(e); }}
|
| 207 |
+
}}))).then(textures => {{
|
| 208 |
+
safeLog('Textures loaded', textures);
|
| 209 |
+
const mats = [
|
| 210 |
+
new THREE.MeshStandardMaterial({{map: textures[0]}}), // right
|
| 211 |
+
new THREE.MeshStandardMaterial({{map: textures[1]}}), // left
|
| 212 |
+
new THREE.MeshStandardMaterial({{color:0xcccccc}}), // top
|
| 213 |
+
new THREE.MeshStandardMaterial({{color:0xcccccc}}), // bottom
|
| 214 |
+
new THREE.MeshStandardMaterial({{map: textures[2]}}), // front
|
| 215 |
+
new THREE.MeshStandardMaterial({{map: textures[3]}}) // back
|
| 216 |
+
];
|
| 217 |
+
// set wrapping/filter
|
| 218 |
+
mats.forEach(m => {{
|
| 219 |
+
if (m.map) {{
|
| 220 |
+
m.map.minFilter = THREE.LinearFilter;
|
| 221 |
+
m.map.wrapS = THREE.ClampToEdgeWrapping;
|
| 222 |
+
m.map.wrapT = THREE.ClampToEdgeWrapping;
|
| 223 |
+
}}
|
| 224 |
+
}});
|
| 225 |
+
cube.material = mats;
|
| 226 |
+
}}).catch(err => {{
|
| 227 |
+
safeLog('Erreur chargement textures (on garde fallback):', err);
|
| 228 |
+
// affiche un petit message utilisateur (optionnel)
|
| 229 |
+
const msg = document.getElementById('viewer-msg');
|
| 230 |
+
if (msg) msg.innerText = "Rendu 3D : textures non chargées, affichage fallback (voir console).";
|
| 231 |
+
}});
|
| 232 |
|
| 233 |
// animation loop
|
| 234 |
+
function animate() {{
|
| 235 |
requestAnimationFrame(animate);
|
| 236 |
controls.update();
|
| 237 |
renderer.render(scene, camera);
|
| 238 |
+
}}
|
| 239 |
+
animate();
|
| 240 |
+
|
| 241 |
+
// responsive: observe container size
|
| 242 |
+
if (typeof ResizeObserver !== 'undefined') {{
|
| 243 |
+
const ro = new ResizeObserver(() => {{
|
| 244 |
+
const ww = container.clientWidth || 800;
|
| 245 |
+
const hh = container.clientHeight || 480;
|
| 246 |
+
renderer.setSize(ww, hh);
|
| 247 |
+
camera.aspect = ww / hh;
|
| 248 |
+
camera.updateProjectionMatrix();
|
| 249 |
+
}});
|
| 250 |
+
ro.observe(container);
|
| 251 |
+
}} else {{
|
| 252 |
+
window.addEventListener('resize', () => {{
|
| 253 |
+
const ww = container.clientWidth || 800;
|
| 254 |
+
const hh = container.clientHeight || 480;
|
| 255 |
+
renderer.setSize(ww, hh);
|
| 256 |
+
camera.aspect = ww / hh;
|
| 257 |
+
camera.updateProjectionMatrix();
|
| 258 |
+
}});
|
| 259 |
+
}}
|
| 260 |
+
}}
|
| 261 |
+
|
| 262 |
+
// wait until container has non-zero size and THREE is available
|
| 263 |
+
let tries = 0;
|
| 264 |
+
function waitForReady() {{
|
| 265 |
+
tries++;
|
| 266 |
+
if (container.clientWidth > 0 && typeof THREE !== 'undefined') {{
|
| 267 |
+
try {{ initScene(); }} catch(e) {{ safeLog('Erreur initScene', e); container.innerHTML = "<div style='padding:10px;color:#900;'>Erreur initialisation rendu 3D (voir console)</div>"; }}
|
| 268 |
+
}} else {{
|
| 269 |
+
if (tries < 60) setTimeout(waitForReady, 100);
|
| 270 |
+
else {{
|
| 271 |
+
// give it one last try
|
| 272 |
+
try {{ initScene(); }} catch(e) {{ safeLog('Derniere tentative échouée', e); container.innerHTML = "<div style='padding:10px;color:#900;'>Impossible d'initialiser le rendu 3D (voir console)</div>"; }}
|
| 273 |
+
}}
|
| 274 |
+
}}
|
| 275 |
+
}}
|
| 276 |
+
waitForReady();
|
| 277 |
}})();
|
| 278 |
</script>
|
| 279 |
"""
|