Fix SparkPlug 3D preview, remove global loader overlay during generation, and fix empty scene hierarchy bug when transferring to Editor
Build and Deploy / build-and-push (push) Successful in 43s

This commit is contained in:
AI Bot
2026-09-02 15:52:07 +05:30
parent 276da43d28
commit 28989d0630
2 changed files with 112 additions and 41 deletions
+45
View File
@@ -1026,6 +1026,51 @@ export default function Editor() {
child.frustumCulled = false; // Prevent flickering on large models child.frustumCulled = false; // Prevent flickering on large models
} }
}); });
setSceneObjects(prev => {
// If the scene already has objects, don't overwrite them
if (prev.length > 0) return prev;
// Build default hierarchy for fresh imported models (like from SparkPlug)
const mainModelId = `model-${Date.now()}`;
const builtObjects: SceneObject[] = [
{
id: mainModelId,
name: modelInfo.name || 'Imported Model',
type: 'model',
parentId: null,
visible: true,
transformProps: { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] }
}
];
let mIdx = 0;
object!.traverse((child) => {
if (child instanceof THREE.Mesh) {
const meshId = `mesh-${mIdx++}`;
const material = Array.isArray(child.material) ? child.material[0] : child.material;
const mProps = { ...globalMaterialProps };
if (material) {
if (material.color) mProps.color = `#${material.color.getHexString()}`;
if (material.roughness !== undefined) mProps.roughness = material.roughness;
if (material.metalness !== undefined) mProps.metalness = material.metalness;
if (material.opacity !== undefined) mProps.opacity = material.opacity;
}
builtObjects.push({
id: meshId,
name: child.name || `Mesh ${meshId.slice(0, 4)}`,
type: 'mesh',
parentId: mainModelId,
visible: true,
materialProps: mProps
});
}
});
return builtObjects;
});
setLoadedModel(object); setLoadedModel(object);
setBlobURLs(prev => [...prev, url]); setBlobURLs(prev => [...prev, url]);
} }
+67 -41
View File
@@ -1,7 +1,14 @@
import React, { useState } from 'react'; import React, { useState, Suspense } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Sparkles, Upload, FileText, Image as ImageIcon, Box, Check, Loader2, ArrowRight, RefreshCw, ChevronLeft } from 'lucide-react'; import { Sparkles, Upload, FileText, Image as ImageIcon, Box, Check, Loader2, ArrowRight, RefreshCw, ChevronLeft } from 'lucide-react';
import { api } from '../lib/api'; import { api } from '../lib/api';
import { Canvas } from '@react-three/fiber';
import { OrbitControls, Stage, useGLTF } from '@react-three/drei';
function ModelPreview({ url }: { url: string }) {
const { scene } = useGLTF(url);
return <primitive object={scene} />;
}
const STEPS = [ const STEPS = [
{ id: 1, title: 'Upload PDF', icon: Upload }, { id: 1, title: 'Upload PDF', icon: Upload },
@@ -115,6 +122,7 @@ export default function SparkPlug() {
// Immediately move to step 4 to show the progress bar // Immediately move to step 4 to show the progress bar
setCurrentStep(4); setCurrentStep(4);
setIsLoading(false);
// Poll Meshy status // Poll Meshy status
let isDone = false; let isDone = false;
@@ -131,6 +139,7 @@ export default function SparkPlug() {
} }
if (status === 'SUCCEEDED') { if (status === 'SUCCEEDED') {
setModelProgress(100);
setGeneratedGlbUrl(model_urls.glb); setGeneratedGlbUrl(model_urls.glb);
isDone = true; isDone = true;
// The step is already 4, user just clicks "Open in Editor" when progress >= 100 // The step is already 4, user just clicks "Open in Editor" when progress >= 100
@@ -163,20 +172,19 @@ export default function SparkPlug() {
// Create project and save with model injected into scene_data // Create project and save with model injected into scene_data
const res = await api.saveProject({ const res = await api.saveProject({
name: "Spark Plug Output", name: pdfFile?.name ? pdfFile.name.replace(/\.[^/.]+$/, "") : "Spark Plug Output",
scene_data: { scene_data: {
objects: [ sceneObjects: [],
{ modelData: {
id: "model_" + Date.now(), name: "Generated Product Model",
type: "model", data: generatedGlbUrl,
name: "Generated Product Model", extension: "glb"
visible: true, },
position: [0, 0, 0], backgroundColor: '#444444',
rotation: [0, 0, 0], backgroundType: 'color',
scale: [1, 1, 1], envPreset: 'studio',
url: generatedGlbUrl lightProps: { intensity: 1, color: '#ffffff', shadowBias: -0.0005, shadowRadius: 4 },
} groundProps: { showShadow: true, shadowIntensity: 0.4, shadowRotation: 0, shadowSoftness: 0.5, shadowLength: 10 }
]
} }
}); });
if (res.id) { if (res.id) {
@@ -367,35 +375,53 @@ export default function SparkPlug() {
)} )}
{currentStep === 4 && ( {currentStep === 4 && (
<div className="flex-1 flex flex-col items-center justify-center gap-6 animate-in fade-in slide-in-from-right-8 duration-300"> <div className="flex-1 flex flex-col items-center justify-center gap-6 animate-in fade-in slide-in-from-right-8 duration-300 w-full">
<div className="w-24 h-24 rounded-full border-4 border-zinc-800 border-t-amber-500 animate-spin flex items-center justify-center"> {modelProgress < 100 ? (
<Box size={32} className="text-zinc-500 animate-pulse" /> <>
</div> <div className="w-24 h-24 rounded-full border-4 border-zinc-800 border-t-amber-500 animate-spin flex items-center justify-center">
<Box size={32} className="text-zinc-500 animate-pulse" />
<div className="text-center w-full max-w-sm"> </div>
<h2 className="text-xl font-bold mb-2">Generating 3D Model...</h2>
<p className="text-sm text-zinc-400 mb-6"> <div className="text-center w-full max-w-sm">
Meshy is constructing the model based on the Nano Banana concept images. <h2 className="text-xl font-bold mb-2">Generating 3D Model...</h2>
</p> <p className="text-sm text-zinc-400 mb-6">
Meshy is constructing the model based on the concept images.
</p>
<div className="w-full bg-zinc-950 rounded-full h-3 overflow-hidden"> <div className="w-full bg-zinc-950 rounded-full h-3 overflow-hidden">
<div <div
className="h-full bg-amber-500 transition-all duration-300" className="h-full bg-amber-500 transition-all duration-300"
style={{ width: `${modelProgress}%` }} style={{ width: `${modelProgress}%` }}
/> />
</div>
<div className="text-xs font-bold text-amber-500 mt-2">{modelProgress}% Complete</div>
</div>
</>
) : (
<div className="w-full h-[400px] bg-zinc-900 rounded-xl overflow-hidden relative border border-zinc-800 flex flex-col items-center justify-center">
{generatedGlbUrl ? (
<Canvas shadows dpr={[1, 2]} camera={{ position: [0, 0, 4], fov: 45 }}>
<Suspense fallback={null}>
<Stage environment="studio" intensity={0.5}>
<ModelPreview url={generatedGlbUrl} />
</Stage>
</Suspense>
<OrbitControls autoRotate />
</Canvas>
) : (
<div className="text-zinc-500">Failed to load preview</div>
)}
<div className="absolute bottom-6 flex justify-center w-full z-10 pointer-events-none">
<button
onClick={processStep4}
disabled={isLoading}
className="pointer-events-auto flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-black px-8 py-3 rounded-xl font-bold transition-all shadow-xl disabled:opacity-50 animate-in fade-in slide-in-from-bottom-4"
>
{isLoading ? <Loader2 className="animate-spin" size={18} /> : <Check size={18} />}
Open in Editor
</button>
</div>
</div> </div>
<div className="text-xs font-bold text-amber-500 mt-2">{modelProgress}% Complete</div>
</div>
{modelProgress >= 100 && (
<button
onClick={processStep4}
disabled={isLoading}
className="mt-6 flex items-center gap-2 bg-amber-500 hover:bg-amber-600 text-black px-8 py-3 rounded-xl font-bold transition-all disabled:opacity-50 animate-in fade-in slide-in-from-bottom-4"
>
{isLoading ? <Loader2 className="animate-spin" size={18} /> : <Check size={18} />}
Open in Editor
</button>
)} )}
</div> </div>
)} )}