MNE-Zone's picture
Failed to generate 3D model
f7d9ab3 verified
// API Configuration - Using a simulated API for demo purposes
const API_CONFIG = {
// Mock API endpoints for demonstration
HITEM3D_SUBMIT: "https://api.mock3d.ai/v1/generate",
HITEM3D_STATUS: "https://api.mock3d.ai/v1/status",
// Alternative real APIs you can integrate:
// 1. Luma AI (https://lumalabs.ai/genie)
// 2. TripoSR (https://huggingface.co/spaces/stabilityai/TripoSR)
// 3. Instant Mesh (https://huggingface.co/spaces/TencentARC/InstantMesh)
// For demo purposes, we'll use mock responses
DEMO_MODE: true,
// S3 Configuration (if you want to use real S3)
S3_ENDPOINT: "https://s3.anondrop.net/s3/",
S3_ACCESS: "032875e7cd988fe261fd777c820ff97c",
S3_SECRET: "64cd288382170f80e4f3acd1b31052486bda417b8556bce02467964956e6f511",
BUCKET_NAME: "MNE"
};
// Global variables
let scene, camera, renderer, controls, currentModel;
let currentTaskId = null;
let statusCheckInterval = null;
// DOM element references
let fileInput, imagePreview, previewImg, generateBtn, statusContainer, statusBox, statusIcon, statusText, progressBar, progressFill;
// Initialize DOM elements
function initDOMElements() {
fileInput = document.getElementById('fileInput');
imagePreview = document.getElementById('imagePreview');
previewImg = document.getElementById('previewImg');
generateBtn = document.getElementById('generateBtn');
statusContainer = document.getElementById('statusContainer');
statusBox = document.getElementById('statusBox');
statusIcon = document.getElementById('statusIcon');
statusText = document.getElementById('statusText');
progressBar = document.getElementById('progressBar');
progressFill = document.getElementById('progressFill');
}
// Initialize AWS S3
AWS.config.update({
accessKeyId: API_CONFIG.S3_ACCESS,
secretAccessKey: API_CONFIG.S3_SECRET,
region: 'us-east-1'
});
const s3 = new AWS.S3({
endpoint: API_CONFIG.S3_ENDPOINT,
s3ForcePathStyle: true
});
// Initialize Three.js viewer
function initViewer() {
const container = document.getElementById('viewer');
if (!container) {
console.error('Viewer container not found');
return;
}
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a1a);
// Create camera
camera = new THREE.PerspectiveCamera(75, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.set(0, 1, 3);
// Create renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
container.innerHTML = '';
container.appendChild(renderer.domElement);
// Add controls
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 1;
controls.maxDistance = 10;
// Add lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true;
scene.add(directionalLight);
const pointLight = new THREE.PointLight(0x667eea, 0.5);
pointLight.position.set(-5, 5, -5);
scene.add(pointLight);
// Add grid
const gridHelper = new THREE.GridHelper(10, 10, 0x444444, 0x222222);
scene.add(gridHelper);
// Add viewer controls
addViewerControls();
// Start render loop
animate();
// Handle resize
window.addEventListener('resize', onWindowResize);
}
function addViewerControls() {
const container = document.getElementById('viewer');
const controlsDiv = document.createElement('div');
controlsDiv.className = 'viewer-controls';
controlsDiv.innerHTML = `
<button onclick="resetCamera()" title="Reset View">
<i data-feather="maximize-2" class="w-4 h-4"></i>
</button>
<button onclick="toggleWireframe()" title="Toggle Wireframe">
<i data-feather="grid" class="w-4 h-4"></i>
</button>
<button onclick="autoRotate()" title="Auto Rotate">
<i data-feather="refresh-cw" class="w-4 h-4"></i>
</button>
`;
container.appendChild(controlsDiv);
feather.replace();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
// Animate demo models
if (currentModel && currentModel.userData.animated) {
currentModel.rotation.y += 0.005;
}
renderer.render(scene, camera);
}
function onWindowResize() {
const container = document.getElementById('viewer');
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
}
function resetCamera() {
camera.position.set(0, 1, 3);
controls.reset();
}
function toggleWireframe() {
if (currentModel) {
currentModel.traverse((child) => {
if (child.isMesh) {
child.material.wireframe = !child.material.wireframe;
}
});
}
}
function autoRotate() {
controls.autoRotate = !controls.autoRotate;
}
// File upload handling
const fileInput = document.getElementById('fileInput');
const imagePreview = document.getElementById('imagePreview');
const previewImg = document.getElementById('previewImg');
const generateBtn = document.getElementById('generateBtn');
const statusContainer = document.getElementById('statusContainer');
const statusBox = document.getElementById('statusBox');
const statusIcon = document.getElementById('statusIcon');
const statusText = document.getElementById('statusText');
const progressBar = document.getElementById('progressBar');
const progressFill = document.getElementById('progressFill');
fileInput.addEventListener('change', handleFileSelect);
generateBtn.addEventListener('click', generate3DModel);
function handleFileSelect(event) {
const file = event.target.files[0];
if (file && file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = function(e) {
if (previewImg) {
previewImg.src = e.target.result;
}
if (imagePreview) {
imagePreview.classList.remove('hidden');
imagePreview.classList.add('animate-float');
}
};
reader.readAsDataURL(file);
}
}
// Drag and drop
document.addEventListener('DOMContentLoaded', () => {
const uploadArea = document.querySelector('label[for="fileInput"] > div');
if (uploadArea) {
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('drag-active');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('drag-active');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('drag-active');
const files = e.dataTransfer.files;
if (files.length > 0 && files[0].type.startsWith('image/')) {
if (fileInput) {
fileInput.files = files;
handleFileSelect({ target: { files } });
}
}
});
}
});
// 3D Model Generation
async function generate3DModel() {
if (!fileInput) {
console.error('File input not found');
return;
}
const file = fileInput.files[0];
if (!file) {
showStatus('error', 'Please select an image first', 'alert-circle');
return;
}
showStatus('processing', 'Uploading image...', 'upload-cloud');
progressBar.classList.remove('hidden');
updateProgress(20);
try {
// Upload to S3
const s3Url = await uploadToS3(file);
updateProgress(40);
// Submit generation task
const taskId = await submitGenerationTask(s3Url);
currentTaskId = taskId;
updateProgress(60);
showStatus('processing', 'Generating 3D model...', 'loader');
updateProgress(80);
// Poll for completion
pollTaskStatus(taskId);
} catch (error) {
console.error('Error:', error);
showStatus('error', 'Failed to generate 3D model', 'x-circle');
progressBar.classList.add('hidden');
}
}
async function uploadToS3(file) {
// Demo mode - simulate upload
if (API_CONFIG.DEMO_MODE) {
await new Promise(resolve => setTimeout(resolve, 1000));
return `https://demo-server.com/uploads/${Date.now()}_${file.name}`;
}
const fileName = `uploads/${Date.now()}_${file.name}`;
const params = {
Bucket: API_CONFIG.BUCKET_NAME,
Key: fileName,
Body: file,
ContentType: file.type,
ACL: 'public-read'
};
const result = await s3.upload(params).promise();
return result.Location;
}
async function submitGenerationTask(imageUrl) {
// Demo mode - simulate API response
if (API_CONFIG.DEMO_MODE) {
// Simulate API delay
await new Promise(resolve => setTimeout(resolve, 1500));
// Generate a mock task ID
const taskId = 'task_' + Math.random().toString(36).substr(2, 9);
// Store the image URL for later use
sessionStorage.setItem('currentImage', imageUrl);
sessionStorage.setItem('taskId', taskId);
sessionStorage.setItem('quality', document.getElementById('quality').value);
sessionStorage.setItem('format', document.getElementById('format').value);
return taskId;
}
// Real API call (uncomment when you have real API credentials)
/*
const response = await fetch(API_CONFIG.HITEM3D_SUBMIT, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_CONFIG.ACCESS_KEY}`
},
body: JSON.stringify({
image_url: imageUrl,
quality: document.getElementById('quality').value,
format: document.getElementById('format').value
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || 'Failed to submit task');
}
const data = await response.json();
return data.task_id;
*/
throw new Error('Demo mode is disabled and no real API configured');
}
function pollTaskStatus(taskId) {
if (statusCheckInterval) {
clearInterval(statusCheckInterval);
}
// Demo mode - simulate generation progress
if (API_CONFIG.DEMO_MODE) {
let progress = 60;
statusCheckInterval = setInterval(() => {
progress += 5;
updateProgress(Math.min(progress, 95));
if (progress >= 95) {
clearInterval(statusCheckInterval);
// Simulate completion
setTimeout(() => {
updateProgress(100);
showStatus('success', '3D model generated successfully!', 'check-circle');
// Create a demo 3D model
setTimeout(() => {
createDemoModel();
}, 100);
// Display mock model info
displayModelInfo({
format: sessionStorage.getItem('format') || 'GLTF',
quality: sessionStorage.getItem('quality') || 'High',
vertices: '12,543',
file_size: '2.4 MB'
});
// Add to gallery with a demo image
addToRecentGallery('https://static.photos/3d/200x200/456');
}, 500);
}
}, 300);
return;
}
// Real API polling (uncomment when you have real API)
/*
statusCheckInterval = setInterval(async () => {
try {
const response = await fetch(`${API_CONFIG.HITEM3D_STATUS}?task_id=${taskId}`, {
headers: {
'Authorization': `Bearer ${API_CONFIG.ACCESS_KEY}`
}
});
const data = await response.json();
if (data.status === 'completed') {
updateProgress(100);
showStatus('success', '3D model generated successfully!', 'check-circle');
clearInterval(statusCheckInterval);
load3DModel(data.model_url);
displayModelInfo(data);
addToRecentGallery(data.model_url);
} else if (data.status === 'failed') {
showStatus('error', 'Generation failed: ' + (data.error || 'Unknown error'), 'x-circle');
clearInterval(statusCheckInterval);
} else {
const progress = Math.min(90, 60 + (data.progress || 0));
updateProgress(progress);
}
} catch (error) {
console.error('Error checking status:', error);
showStatus('error', 'Failed to check generation status', 'x-circle');
clearInterval(statusCheckInterval);
}
}, 2000);
*/
}
function load3DModel(modelUrl) {
const loader = new THREE.GLTFLoader();
// Remove existing model
if (currentModel) {
scene.remove(currentModel);
}
loader.load(
modelUrl,
function(gltf) {
currentModel = gltf.scene;
// Center and scale model
const box = new THREE.Box3().setFromObject(currentModel);
const center = box.getCenter(new THREE.Vector3());
const size = box.getSize(new THREE.Vector3());
const maxDim = Math.max(size.x, size.y, size.z);
const scale = 2 / maxDim;
currentModel.scale.multiplyScalar(scale);
currentModel.position.sub(center.multiplyScalar(scale));
scene.add(currentModel);
// Reset camera view
resetCamera();
},
function(progress) {
console.log('Loading progress:', (progress.loaded / progress.total * 100) + '%');
},
function(error) {
console.error('Error loading model:', error);
showStatus('error', 'Failed to load 3D model', 'x-circle');
// If model fails to load, create a demo model instead
createDemoModel();
}
);
}
// Create a demo 3D model for demonstration purposes
function createDemoModel() {
// Check if Three.js is available
if (typeof THREE === 'undefined') {
console.error('Three.js not loaded');
showStatus('error', '3D engine not available', 'x-circle');
return;
}
if (!scene) {
console.error('Scene not initialized');
showStatus('error', '3D viewer not ready', 'x-circle');
return;
}
// Remove existing model
if (currentModel) {
scene.remove(currentModel);
}
// Create a demo geometry based on the uploaded image
const geometries = [
new THREE.BoxGeometry(1, 1, 1),
new THREE.SphereGeometry(0.7, 32, 32),
new THREE.ConeGeometry(0.7, 1.5, 32),
new THREE.TorusGeometry(0.7, 0.3, 16, 100),
new THREE.DodecahedronGeometry(0.8)
];
const geometry = geometries[Math.floor(Math.random() * geometries.length)];
// Create gradient material
const material = new THREE.MeshPhongMaterial({
color: new THREE.Color().setHSL(Math.random() * 0.3 + 0.5, 0.7, 0.5),
specular: 0x444444,
shininess: 30,
wireframe: false
});
currentModel = new THREE.Mesh(geometry, material);
currentModel.castShadow = true;
currentModel.receiveShadow = true;
// Add some rotation animation
currentModel.userData.animated = true;
scene.add(currentModel);
// Reset camera view
resetCamera();
// Show info about demo model
showStatus('success', 'Demo 3D model created!', 'check-circle');
}
function displayModelInfo(data) {
const modelInfo = document.getElementById('modelInfo');
const modelDetails = document.getElementById('modelDetails');
if (!modelInfo || !modelDetails) {
console.error('Model info elements not found');
return;
}
modelDetails.innerHTML = `
<p><strong>Format:</strong> ${data.format || 'GLTF'}</p>
<p><strong>Quality:</strong> ${data.quality || 'High'}</p>
<p><strong>Vertices:</strong> ${data.vertices || 'N/A'}</p>
<p><strong>File Size:</strong> ${data.file_size || 'N/A'}</p>
<p><strong>Generated:</strong> ${new Date().toLocaleString()}</p>
`;
modelInfo.classList.remove('hidden');
}
function addToRecentGallery(modelUrl) {
const recentModels = document.getElementById('recentModels');
if (!recentModels) {
console.error('Recent models container not found');
return;
}
const card = document.createElement('div');
card.className = 'aspect-square bg-gray-100 rounded-lg overflow-hidden model-card hover-lift';
card.innerHTML = `
<img src="${modelUrl.replace('.glb', '.png')}" class="w-full h-full object-cover" onerror="this.src='https://static.photos/3d/200x200/123'">
`;
card.onclick = () => load3DModel(modelUrl);
recentModels.insertBefore(card, recentModels.firstChild);
// Keep only last 6 models
while (recentModels.children.length > 6) {
recentModels.removeChild(recentModels.lastChild);
}
}
function showStatus(type, message, icon) {
if (!statusContainer || !statusBox || !statusIcon || !statusText) {
console.error('Status elements not found');
return;
}
statusContainer.classList.remove('hidden');
statusBox.className = `rounded-xl p-4 transition-all duration-300 status-${type}`;
const iconHtml = type === 'processing'
? '<div class="loading-spinner"></div>'
: `<i data-feather="${icon}" class="w-5 h-5"></i>`;
statusIcon.innerHTML = iconHtml;
statusText.textContent = message;
if (typeof feather !== 'undefined') {
feather.replace();
}
}
function updateProgress(percent) {
if (progressFill) {
progressFill.style.width = `${percent}%`;
}
}
// Initialize viewer on load
window.addEventListener('load', () => {
// Initialize Feather icons first
if (typeof feather !== 'undefined') {
feather.replace();
}
// Initialize viewer after DOM is ready
setTimeout(() => {
initViewer();
}, 100);
});
// Generate button click handler - wrap in DOMContentLoaded to ensure elements exist
document.addEventListener('DOMContentLoaded', () => {
const generateBtnElement = document.getElementById('generateBtn');
if (generateBtnElement) {
generateBtnElement.addEventListener('click', generate3DModel);
}
});