Download index.html from Invader1/image-stl: direct link, hf CLI and curl.
- Browser
- Download file 25.6 kB
-
https://huggingface.co/spaces/Invader1/image-stl/resolve/main/index.html
- Command line
-
hf download hf://spaces/Invader1/image-stl/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Invader1/image-stl/resolve/main/index.html
25.6 kB
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Image2STL - Convertir des images en modèles 3D</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/exporters/STLExporter.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .dropzone { | |
| border: 2px dashed #4b5563; | |
| transition: all 0.3s ease; | |
| } | |
| .dropzone.active { | |
| border-color: #3b82f6; | |
| background-color: rgba(59, 130, 246, 0.05); | |
| } | |
| .preview-container { | |
| transition: all 0.3s ease; | |
| opacity: 0; | |
| height: 0; | |
| overflow: hidden; | |
| } | |
| .preview-container.show { | |
| opacity: 1; | |
| height: auto; | |
| } | |
| #stlPreview { | |
| width: 100%; | |
| height: 400px; | |
| background-color: #f3f4f6; | |
| } | |
| .slider-thumb::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| background: #3b82f6; | |
| cursor: pointer; | |
| border-radius: 50%; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50 min-h-screen"> | |
| <!-- Navigation --> | |
| <nav class="bg-white shadow-sm"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <div class="flex justify-between h-16"> | |
| <div class="flex items-center"> | |
| <i class="fas fa-cube text-blue-500 text-2xl mr-2"></i> | |
| <span class="text-xl font-bold text-gray-800">Image2STL</span> | |
| </div> | |
| <div class="hidden md:flex items-center space-x-8"> | |
| <a href="#" class="text-gray-700 hover:text-blue-500">Accueil</a> | |
| <a href="#" class="text-gray-700 hover:text-blue-500">Comment ça marche</a> | |
| <a href="#" class="text-gray-700 hover:text-blue-500">Exemples</a> | |
| <a href="#" class="text-gray-700 hover:text-blue-500">Contact</a> | |
| </div> | |
| <button class="md:hidden text-gray-500"> | |
| <i class="fas fa-bars text-xl"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- Hero Section --> | |
| <div class="bg-gradient-to-r from-blue-500 to-indigo-600 text-white py-16"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center"> | |
| <h1 class="text-4xl md:text-5xl font-bold mb-6">Transformez vos images en modèles 3D</h1> | |
| <p class="text-xl mb-8 max-w-3xl mx-auto">Convertissez simplement n'importe quelle image en fichier STL prêt pour l'impression 3D</p> | |
| <button id="startConversion" class="bg-white text-blue-600 font-bold py-3 px-8 rounded-full hover:bg-gray-100 transition duration-300 transform hover:scale-105"> | |
| Commencer maintenant <i class="fas fa-arrow-right ml-2"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Conversion Tool --> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12"> | |
| <div class="bg-white rounded-xl shadow-md overflow-hidden"> | |
| <div class="p-6 md:p-8"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-6">Convertisseur Image vers STL</h2> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
| <!-- Upload Section --> | |
| <div> | |
| <div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-6"> | |
| <i class="fas fa-cloud-upload-alt text-4xl text-blue-500 mb-4"></i> | |
| <h3 class="text-lg font-semibold mb-2">Glissez-déposez votre image ici</h3> | |
| <p class="text-gray-500 mb-4">ou</p> | |
| <input type="file" id="fileInput" accept="image/*" multiple class="hidden"> | |
| <label for="fileInput" class="inline-block bg-blue-500 text-white py-2 px-6 rounded-full hover:bg-blue-600 cursor-pointer transition"> | |
| Sélectionner un fichier | |
| </label> | |
| <p class="text-gray-500 text-sm mt-4">Formats supportés: JPG, PNG, BMP (Max 10MB par fichier)</p> | |
| </div> | |
| <div id="imagePreviewContainer" class="preview-container mb-6"> | |
| <h4 class="font-medium text-gray-700 mb-2">Aperçu des images</h4> | |
| <div id="imagePreviews" class="grid grid-cols-2 gap-4"> | |
| <!-- Les aperçus seront ajoutés ici dynamiquement --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Settings Section --> | |
| <div> | |
| <div class="bg-gray-50 p-6 rounded-lg"> | |
| <h3 class="text-lg font-semibold mb-4">Paramètres de conversion</h3> | |
| <div class="space-y-6"> | |
| <div> | |
| <label for="thickness" class="block text-sm font-medium text-gray-700 mb-1">Épaisseur du modèle</label> | |
| <input type="range" id="thickness" min="1" max="10" value="5" class="slider-thumb w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> | |
| <span>Min</span> | |
| <span>Max</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label for="smoothing" class="block text-sm font-medium text-gray-700 mb-1">Niveau de lissage</label> | |
| <input type="range" id="smoothing" min="0" max="100" value="70" class="slider-thumb w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> | |
| <span>Détails</span> | |
| <span>Lisse</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label for="resolution" class="block text-sm font-medium text-gray-700 mb-1">Résolution</label> | |
| <select id="resolution" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md"> | |
| <option value="low">Basse (rapide)</option> | |
| <option value="medium">Moyenne</option> | |
| <option value="high" selected>Haute (précise)</option> | |
| <option value="ultra">Ultra (AI optimisé)</option> | |
| </select> | |
| </div> | |
| <div class="pt-2"> | |
| <button id="convertBtn" class="w-full bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-4 rounded-lg transition flex items-center justify-center"> | |
| <i class="fas fa-robot mr-2"></i> Optimiser avec IA | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="resultContainer" class="preview-container mt-6"> | |
| <h4 class="font-medium text-gray-700 mb-2">Résultat STL</h4> | |
| <div id="stlPreview" class="border rounded-lg flex items-center justify-center"> | |
| <p class="text-gray-500">Votre modèle 3D apparaîtra ici</p> | |
| </div> | |
| <div class="mt-4 flex space-x-3"> | |
| <button id="downloadBtn" class="flex-1 bg-green-500 hover:bg-green-600 text-white font-medium py-2 px-4 rounded-lg transition flex items-center justify-center"> | |
| <i class="fas fa-download mr-2"></i> Télécharger | |
| </button> | |
| <button id="resetBtn" class="flex-1 bg-gray-200 hover:bg-gray-300 text-gray-700 font-medium py-2 px-4 rounded-lg transition flex items-center justify-center"> | |
| <i class="fas fa-redo mr-2"></i> Recommencer | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Features Section --> | |
| <div class="bg-gray-100 py-12"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <h2 class="text-3xl font-bold text-center text-gray-800 mb-12">Pourquoi choisir Image2STL ?</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-8"> | |
| <div class="bg-white p-6 rounded-lg shadow-sm text-center"> | |
| <div class="text-blue-500 mb-4"> | |
| <i class="fas fa-bolt text-4xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold mb-3">Conversion rapide</h3> | |
| <p class="text-gray-600">Transformez vos images en fichiers STL en quelques secondes grâce à notre algorithme optimisé.</p> | |
| </div> | |
| <div class="bg-white p-6 rounded-lg shadow-sm text-center"> | |
| <div class="text-blue-500 mb-4"> | |
| <i class="fas fa-brain text-4xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold mb-3">IA avancée</h3> | |
| <p class="text-gray-600">Notre intelligence artificielle optimise automatiquement votre modèle pour une qualité d'impression exceptionnelle.</p> | |
| </div> | |
| <div class="bg-white p-6 rounded-lg shadow-sm text-center"> | |
| <div class="text-blue-500 mb-4"> | |
| <i class="fas fa-print text-4xl"></i> | |
| </div> | |
| <h3 class="text-xl font-semibold mb-3">Prêt à imprimer</h3> | |
| <p class="text-gray-600">Fichiers STL optimisés pour une impression 3D sans problème sur toutes les imprimantes.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <footer class="bg-gray-800 text-white py-8"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <div class="grid grid-cols-1 md:grid-cols-4 gap-8"> | |
| <div> | |
| <h4 class="text-lg font-semibold mb-4">Image2STL</h4> | |
| <p class="text-gray-400">Transformez vos images en modèles 3D imprimables en quelques clics.</p> | |
| </div> | |
| <div> | |
| <h4 class="text-lg font-semibold mb-4">Liens utiles</h4> | |
| <ul class="space-y-2"> | |
| <li><a href="#" class="text-gray-400 hover:text-white">Comment ça marche</a></li> | |
| <li><a href="#" class="text-gray-400 hover:text-white">FAQ</a></li> | |
| <li><a href="#" class="text-gray-400 hover:text-white">Conditions d'utilisation</a></li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="text-lg font-semibold mb-4">Ressources</h4> | |
| <ul class="space-y-2"> | |
| <li><a href="#" class="text-gray-400 hover:text-white">Documentation</a></li> | |
| <li><a href="#" class="text-gray-400 hover:text-white">Exemples</a></li> | |
| <li><a href="#" class="text-gray-400 hover:text-white">API</a></li> | |
| </ul> | |
| </div> | |
| <div> | |
| <h4 class="text-lg font-semibold mb-4">Contact</h4> | |
| <ul class="space-y-2"> | |
| <li class="flex items-center text-gray-400"><i class="fas fa-envelope mr-2"></i> contact@image2stl.com</li> | |
| <li class="flex items-center text-gray-400"><i class="fas fa-phone mr-2"></i> +33 1 23 45 67 89</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="border-t border-gray-700 mt-8 pt-8 text-center text-gray-400"> | |
| <p>© 2023 Image2STL. Tous droits réservés.</p> | |
| </div> | |
| </div> | |
| </footer> | |
| <script> | |
| // DOM Elements | |
| const dropzone = document.getElementById('dropzone'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const imagePreview = document.getElementById('imagePreview'); | |
| const imagePreviewContainer = document.getElementById('imagePreviewContainer'); | |
| const convertBtn = document.getElementById('convertBtn'); | |
| const resultContainer = document.getElementById('resultContainer'); | |
| const downloadBtn = document.getElementById('downloadBtn'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| const startConversion = document.getElementById('startConversion'); | |
| // Event Listeners | |
| startConversion.addEventListener('click', () => { | |
| document.querySelector('#conversion-tool').scrollIntoView({ behavior: 'smooth' }); | |
| }); | |
| // Handle drag and drop | |
| ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, preventDefaults, false); | |
| }); | |
| function preventDefaults(e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| } | |
| ['dragenter', 'dragover'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, highlight, false); | |
| }); | |
| ['dragleave', 'drop'].forEach(eventName => { | |
| dropzone.addEventListener(eventName, unhighlight, false); | |
| }); | |
| function highlight() { | |
| dropzone.classList.add('active'); | |
| } | |
| function unhighlight() { | |
| dropzone.classList.remove('active'); | |
| } | |
| // Handle drop | |
| dropzone.addEventListener('drop', handleDrop, false); | |
| function handleDrop(e) { | |
| const dt = e.dataTransfer; | |
| const files = dt.files; | |
| handleFiles(files); | |
| } | |
| // Handle file selection | |
| fileInput.addEventListener('change', function() { | |
| handleFiles(this.files); | |
| }); | |
| // Process selected files | |
| function handleFiles(files) { | |
| if (files.length > 0) { | |
| const previewsContainer = document.getElementById('imagePreviews'); | |
| previewsContainer.innerHTML = ''; | |
| let validFiles = 0; | |
| Array.from(files).forEach((file, index) => { | |
| if (file.type.match('image.*')) { | |
| validFiles++; | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const previewDiv = document.createElement('div'); | |
| previewDiv.className = 'border rounded-lg overflow-hidden relative'; | |
| const img = document.createElement('img'); | |
| img.src = e.target.result; | |
| img.alt = `Aperçu image ${index + 1}`; | |
| img.className = 'w-full h-auto'; | |
| const badge = document.createElement('div'); | |
| badge.className = 'absolute top-2 right-2 bg-blue-500 text-white text-xs px-2 py-1 rounded-full'; | |
| badge.textContent = `#${index + 1}`; | |
| previewDiv.appendChild(img); | |
| previewDiv.appendChild(badge); | |
| previewsContainer.appendChild(previewDiv); | |
| if (validFiles === files.length) { | |
| imagePreviewContainer.classList.add('show'); | |
| } | |
| } | |
| reader.readAsDataURL(file); | |
| } else { | |
| alert(`Le fichier "${file.name}" n'est pas une image valide (JPG, PNG, BMP).`); | |
| } | |
| }); | |
| if (validFiles > 0) { | |
| imagePreviewContainer.classList.add('show'); | |
| } | |
| } | |
| } | |
| // Convert button click | |
| convertBtn.addEventListener('click', function() { | |
| const previews = document.querySelectorAll('#imagePreviews img'); | |
| if (previews.length === 0) { | |
| alert('Veuillez d\'abord sélectionner au moins une image.'); | |
| return; | |
| } | |
| // Show loading state | |
| convertBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Conversion en cours...'; | |
| convertBtn.disabled = true; | |
| // Get first image | |
| const img = previews[0]; | |
| // Create canvas to process image | |
| const canvas = document.createElement('canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| canvas.width = img.width; | |
| canvas.height = img.height; | |
| ctx.drawImage(img, 0, 0); | |
| // Get image data | |
| const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); | |
| const data = imgData.data; | |
| // Create 3D geometry from image | |
| const geometry = new THREE.BufferGeometry(); | |
| const vertices = []; | |
| const indices = []; | |
| // Improved heightmap generation | |
| const thickness = parseInt(document.getElementById('thickness').value); | |
| const smoothing = parseInt(document.getElementById('smoothing').value) / 100; | |
| const resolution = document.getElementById('resolution').value; | |
| // Set resolution based on selection | |
| let segments = 50; | |
| if (resolution === 'low') segments = 30; | |
| else if (resolution === 'medium') segments = 60; | |
| else if (resolution === 'high') segments = 100; | |
| else if (resolution === 'ultra') segments = 150; | |
| const size = 1; | |
| const segmentSize = size / segments; | |
| // Create heightmap data | |
| const heightmap = []; | |
| for (let y = 0; y <= segments; y++) { | |
| heightmap[y] = []; | |
| for (let x = 0; x <= segments; x++) { | |
| const px = Math.floor(x / segments * img.width); | |
| const py = Math.floor(y / segments * img.height); | |
| const idx = (py * img.width + px) * 4; | |
| const gray = (data[idx] + data[idx+1] + data[idx+2]) / 765; // 0-1 | |
| heightmap[y][x] = gray * thickness; | |
| } | |
| } | |
| // Apply smoothing | |
| if (smoothing > 0) { | |
| for (let y = 1; y < segments; y++) { | |
| for (let x = 1; x < segments; x++) { | |
| heightmap[y][x] = heightmap[y][x] * (1 - smoothing) + | |
| ((heightmap[y-1][x] + heightmap[y+1][x] + | |
| heightmap[y][x-1] + heightmap[y][x+1]) / 4) * smoothing; | |
| } | |
| } | |
| } | |
| // Create vertices | |
| for (let y = 0; y <= segments; y++) { | |
| for (let x = 0; x <= segments; x++) { | |
| vertices.push( | |
| x * segmentSize - size/2, | |
| y * segmentSize - size/2, | |
| heightmap[y][x] | |
| ); | |
| } | |
| } | |
| // Create faces | |
| for (let y = 0; y < segments; y++) { | |
| for (let x = 0; x < segments; x++) { | |
| const a = y * (segments + 1) + x; | |
| const b = y * (segments + 1) + x + 1; | |
| const c = (y + 1) * (segments + 1) + x; | |
| const d = (y + 1) * (segments + 1) + x + 1; | |
| // Create two triangles per square | |
| indices.push(a, b, d); | |
| indices.push(a, d, c); | |
| } | |
| } | |
| geometry.setIndex(indices); | |
| geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); | |
| geometry.computeVertexNormals(); | |
| // Create mesh | |
| const mesh = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial()); | |
| // Add to scene for preview | |
| const scene = new THREE.Scene(); | |
| scene.add(mesh); | |
| // Add lights | |
| const light1 = new THREE.DirectionalLight(0xffffff, 1); | |
| light1.position.set(1, 1, 1); | |
| scene.add(light1); | |
| const light2 = new THREE.DirectionalLight(0xffffff, 0.5); | |
| light2.position.set(-1, -1, -1); | |
| scene.add(light2); | |
| // Set up renderer | |
| const renderer = new THREE.WebGLRenderer({ antialias: true }); | |
| renderer.setSize(document.getElementById('stlPreview').clientWidth, | |
| document.getElementById('stlPreview').clientHeight); | |
| document.getElementById('stlPreview').innerHTML = ''; | |
| document.getElementById('stlPreview').appendChild(renderer.domElement); | |
| // Set up camera | |
| const camera = new THREE.PerspectiveCamera(45, | |
| document.getElementById('stlPreview').clientWidth / | |
| document.getElementById('stlPreview').clientHeight, 0.1, 1000); | |
| camera.position.z = 2; | |
| // Animation loop | |
| function animate() { | |
| requestAnimationFrame(animate); | |
| mesh.rotation.x += 0.005; | |
| mesh.rotation.y += 0.007; | |
| renderer.render(scene, camera); | |
| } | |
| animate(); | |
| // Export to STL | |
| const exporter = new THREE.STLExporter(); | |
| const stlString = exporter.parse(mesh); | |
| // Save STL data for download | |
| window.stlData = stlString; | |
| // Show result | |
| resultContainer.classList.add('show'); | |
| convertBtn.innerHTML = '<i class="fas fa-magic mr-2"></i> Convertir en STL'; | |
| convertBtn.disabled = false; | |
| // Scroll to result | |
| resultContainer.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | |
| }); | |
| // Download button click | |
| downloadBtn.addEventListener('click', function() { | |
| if (!window.stlData) { | |
| alert('Aucun modèle STL à télécharger. Veuillez d\'abord convertir une image.'); | |
| return; | |
| } | |
| const blob = new Blob([window.stlData], { type: 'application/octet-stream' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = 'modele_3d.stl'; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| }); | |
| // Reset button click | |
| resetBtn.addEventListener('click', function() { | |
| imagePreview.src = ''; | |
| imagePreviewContainer.classList.remove('show'); | |
| resultContainer.classList.remove('show'); | |
| fileInput.value = ''; | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Invader1/image-stl" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |