2dto3d-converter / index.html
Mathieu2025's picture
undefined - Initial Deployment
c1771b2 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>2D to 3D STL Generator</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/[email protected]/dist/stl-export.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/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.1);
}
#previewCanvas {
background-color: #f3f4f6;
border-radius: 0.5rem;
}
#3dViewer {
width: 100%;
height: 400px;
background-color: #f3f4f6;
border-radius: 0.5rem;
}
.slider-thumb::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
.slider-thumb::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: #3b82f6;
cursor: pointer;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800 mb-2">2D to 3D STL Generator</h1>
<p class="text-gray-600">Convert your 2D images into printable 3D STL files</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
<!-- Image Upload Section -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-4">1. Upload 2D Image</h2>
<div id="dropzone" class="dropzone rounded-lg p-8 text-center cursor-pointer mb-6">
<div class="flex flex-col items-center justify-center">
<i class="fas fa-cloud-upload-alt text-4xl text-blue-500 mb-3"></i>
<p class="text-gray-600 mb-2">Drag & drop your image here</p>
<p class="text-sm text-gray-500 mb-4">or</p>
<label for="fileInput" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg cursor-pointer transition">
Select Image
</label>
<input type="file" id="fileInput" accept="image/*" class="hidden">
</div>
</div>
<div id="imagePreviewContainer" class="hidden">
<h3 class="text-lg font-medium text-gray-700 mb-3">Image Preview</h3>
<div class="flex justify-center">
<canvas id="previewCanvas" class="w-full max-w-md h-auto"></canvas>
</div>
</div>
</div>
<!-- 3D Settings Section -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-4">2. 3D Generation Settings</h2>
<div class="space-y-4">
<div>
<label for="heightRange" class="block text-sm font-medium text-gray-700 mb-1">3D Height (mm)</label>
<div class="flex items-center">
<input type="range" id="heightRange" min="1" max="50" value="10" class="w-full slider-thumb">
<span id="heightValue" class="ml-3 text-gray-700 w-12 text-center">10</span>
</div>
</div>
<div>
<label for="smoothnessRange" class="block text-sm font-medium text-gray-700 mb-1">Edge Smoothness</label>
<div class="flex items-center">
<input type="range" id="smoothnessRange" min="1" max="10" value="5" class="w-full slider-thumb">
<span id="smoothnessValue" class="ml-3 text-gray-700 w-12 text-center">5</span>
</div>
</div>
<div>
<label for="baseThicknessRange" class="block text-sm font-medium text-gray-700 mb-1">Base Thickness (mm)</label>
<div class="flex items-center">
<input type="range" id="baseThicknessRange" min="0" max="10" value="2" class="w-full slider-thumb">
<span id="baseThicknessValue" class="ml-3 text-gray-700 w-12 text-center">2</span>
</div>
</div>
<div class="pt-4">
<button id="generateBtn" class="w-full bg-blue-500 hover:bg-blue-600 text-white py-3 px-4 rounded-lg font-medium transition flex items-center justify-center">
<i class="fas fa-cube mr-2"></i> Generate 3D Model
</button>
</div>
</div>
</div>
</div>
<!-- 3D Preview Section -->
<div id="3dPreviewSection" class="hidden mt-8 bg-white rounded-xl shadow-md p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-semibold text-gray-800">3D Model Preview</h2>
<button id="downloadBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center">
<i class="fas fa-download mr-2"></i> Download STL
</button>
</div>
<div id="3dViewer"></div>
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-700 mb-2">Model Info</h3>
<p class="text-sm text-gray-600">Height: <span id="modelHeight" class="font-medium">10</span> mm</p>
<p class="text-sm text-gray-600">Base: <span id="modelBase" class="font-medium">2</span> mm</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-700 mb-2">Estimated Print Time</h3>
<p class="text-sm text-gray-600">Approx. <span id="printTime" class="font-medium">2-4</span> hours</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="font-medium text-gray-700 mb-2">File Size</h3>
<p class="text-sm text-gray-600"><span id="fileSize" class="font-medium">1.2</span> MB</p>
</div>
</div>
</div>
<!-- How It Works Section -->
<div class="mt-12 bg-white rounded-xl shadow-md p-6">
<h2 class="text-2xl font-semibold text-gray-800 mb-4">How It Works</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="text-center">
<div class="bg-blue-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-3">
<i class="fas fa-upload text-blue-500 text-2xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Upload Image</h3>
<p class="text-gray-600 text-sm">Upload any 2D image in JPG, PNG, or GIF format.</p>
</div>
<div class="text-center">
<div class="bg-blue-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-3">
<i class="fas fa-sliders-h text-blue-500 text-2xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Adjust Settings</h3>
<p class="text-gray-600 text-sm">Customize the height, smoothness, and base thickness.</p>
</div>
<div class="text-center">
<div class="bg-blue-100 w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-3">
<i class="fas fa-cube text-blue-500 text-2xl"></i>
</div>
<h3 class="font-medium text-gray-800 mb-2">Download STL</h3>
<p class="text-gray-600 text-sm">Generate and download your 3D model in STL format.</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// DOM Elements
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const previewCanvas = document.getElementById('previewCanvas');
const ctx = previewCanvas.getContext('2d');
const imagePreviewContainer = document.getElementById('imagePreviewContainer');
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const heightRange = document.getElementById('heightRange');
const heightValue = document.getElementById('heightValue');
const smoothnessRange = document.getElementById('smoothnessRange');
const smoothnessValue = document.getElementById('smoothnessValue');
const baseThicknessRange = document.getElementById('baseThicknessRange');
const baseThicknessValue = document.getElementById('baseThicknessValue');
const modelHeight = document.getElementById('modelHeight');
const modelBase = document.getElementById('modelBase');
const printTime = document.getElementById('printTime');
const fileSize = document.getElementById('fileSize');
const dPreviewSection = document.getElementById('3dPreviewSection');
const dViewer = document.getElementById('3dViewer');
// Variables
let uploadedImage = null;
let generatedMesh = null;
let scene, camera, renderer, controls;
// Initialize Three.js viewer
function init3DViewer() {
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf3f4f6);
camera = new THREE.PerspectiveCamera(75, dViewer.clientWidth / dViewer.clientHeight, 0.1, 1000);
camera.position.z = 5;
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(dViewer.clientWidth, dViewer.clientHeight);
dViewer.appendChild(renderer.domElement);
// Add lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Add orbit controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.25;
// Handle window resize
window.addEventListener('resize', onWindowResize);
// Start animation loop
animate();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
function onWindowResize() {
camera.aspect = dViewer.clientWidth / dViewer.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(dViewer.clientWidth, dViewer.clientHeight);
}
// Initialize the 3D viewer
init3DViewer();
// Update slider values display
heightRange.addEventListener('input', function() {
heightValue.textContent = this.value;
});
smoothnessRange.addEventListener('input', function() {
smoothnessValue.textContent = this.value;
});
baseThicknessRange.addEventListener('input', function() {
baseThicknessValue.textContent = this.value;
});
// 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');
}
dropzone.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
fileInput.addEventListener('change', function() {
handleFiles(this.files);
});
function handleFiles(files) {
if (files.length > 0) {
const file = files[0];
if (file.type.match('image.*')) {
const reader = new FileReader();
reader.onload = function(e) {
const img = new Image();
img.onload = function() {
uploadedImage = img;
displayImagePreview(img);
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
} else {
alert('Please upload an image file (JPG, PNG, GIF)');
}
}
}
function displayImagePreview(img) {
// Set canvas dimensions
const maxWidth = 500;
const maxHeight = 400;
let width = img.width;
let height = img.height;
if (width > maxWidth) {
height = (maxWidth / width) * height;
width = maxWidth;
}
if (height > maxHeight) {
width = (maxHeight / height) * width;
height = maxHeight;
}
previewCanvas.width = width;
previewCanvas.height = height;
// Draw image on canvas
ctx.drawImage(img, 0, 0, width, height);
// Show preview container
imagePreviewContainer.classList.remove('hidden');
}
// Generate 3D model
generateBtn.addEventListener('click', function() {
if (!uploadedImage) {
alert('Please upload an image first');
return;
}
// Show loading state
generateBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Generating...';
generateBtn.disabled = true;
// Simulate generation process (in a real app, this would be more complex)
setTimeout(function() {
generate3DModel();
// Update model info
modelHeight.textContent = heightRange.value;
modelBase.textContent = baseThicknessRange.value;
// Estimate print time and file size (these would be calculated in a real app)
const height = parseInt(heightRange.value);
const base = parseInt(baseThicknessRange.value);
const estTime = Math.round(height * 0.2 + base * 0.1);
printTime.textContent = `${estTime}-${estTime + 2}`;
const fileSizeEst = (height * width * 0.001).toFixed(1);
fileSize.textContent = fileSizeEst;
// Show 3D preview section
dPreviewSection.classList.remove('hidden');
// Reset button
generateBtn.innerHTML = '<i class="fas fa-cube mr-2"></i> Generate 3D Model';
generateBtn.disabled = false;
// Scroll to 3D preview
dPreviewSection.scrollIntoView({ behavior: 'smooth' });
}, 1500);
});
function generate3DModel() {
// Clear previous mesh
if (generatedMesh) {
scene.remove(generatedMesh);
}
// Create a simple 3D shape from the image (simplified for demo)
// In a real app, you would use more sophisticated algorithms
const geometry = new THREE.BoxGeometry(
3,
parseFloat(baseThicknessRange.value) * 0.1,
parseFloat(heightRange.value) * 0.1
);
// Create texture from image
const texture = new THREE.Texture(previewCanvas);
texture.needsUpdate = true;
const material = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide
});
generatedMesh = new THREE.Mesh(geometry, material);
scene.add(generatedMesh);
// Reset camera position
camera.position.z = 5;
controls.reset();
}
// Download STL
downloadBtn.addEventListener('click', function() {
if (!generatedMesh) {
alert('Please generate a 3D model first');
return;
}
// Show loading state
downloadBtn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i> Preparing Download...';
downloadBtn.disabled = true;
// Simulate STL export (in a real app, you would use a proper STL exporter)
setTimeout(function() {
// Create a simplified STL export (demo only)
const exporter = new THREE.STLExporter();
const stlString = exporter.parse(generatedMesh);
// Create download link
const blob = new Blob([stlString], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = '3d-model.stl';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// Reset button
downloadBtn.innerHTML = '<i class="fas fa-download mr-2"></i> Download STL';
downloadBtn.disabled = false;
}, 1000);
});
});
</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=Mathieu2025/2dto3d-converter" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>