image-stl / index.html
Invader1's picture
Ça ne génère aucun fichier STL..je veux que tu génère un stl - Follow Up Deployment
597c4d7 verified
<!DOCTYPE html>
<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>