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
Build and Deploy / build-and-push (push) Successful in 43s
This commit is contained in:
@@ -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]);
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-18
@@ -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(),
|
|
||||||
type: "model",
|
|
||||||
name: "Generated Product Model",
|
name: "Generated Product Model",
|
||||||
visible: true,
|
data: generatedGlbUrl,
|
||||||
position: [0, 0, 0],
|
extension: "glb"
|
||||||
rotation: [0, 0, 0],
|
},
|
||||||
scale: [1, 1, 1],
|
backgroundColor: '#444444',
|
||||||
url: generatedGlbUrl
|
backgroundType: 'color',
|
||||||
}
|
envPreset: 'studio',
|
||||||
]
|
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,7 +375,9 @@ 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">
|
||||||
|
{modelProgress < 100 ? (
|
||||||
|
<>
|
||||||
<div className="w-24 h-24 rounded-full border-4 border-zinc-800 border-t-amber-500 animate-spin flex items-center justify-center">
|
<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" />
|
<Box size={32} className="text-zinc-500 animate-pulse" />
|
||||||
</div>
|
</div>
|
||||||
@@ -375,7 +385,7 @@ export default function SparkPlug() {
|
|||||||
<div className="text-center w-full max-w-sm">
|
<div className="text-center w-full max-w-sm">
|
||||||
<h2 className="text-xl font-bold mb-2">Generating 3D Model...</h2>
|
<h2 className="text-xl font-bold mb-2">Generating 3D Model...</h2>
|
||||||
<p className="text-sm text-zinc-400 mb-6">
|
<p className="text-sm text-zinc-400 mb-6">
|
||||||
Meshy is constructing the model based on the Nano Banana concept images.
|
Meshy is constructing the model based on the concept images.
|
||||||
</p>
|
</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">
|
||||||
@@ -386,16 +396,32 @@ export default function SparkPlug() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-xs font-bold text-amber-500 mt-2">{modelProgress}% Complete</div>
|
<div className="text-xs font-bold text-amber-500 mt-2">{modelProgress}% Complete</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
{modelProgress >= 100 && (
|
) : (
|
||||||
|
<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
|
<button
|
||||||
onClick={processStep4}
|
onClick={processStep4}
|
||||||
disabled={isLoading}
|
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"
|
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} />}
|
{isLoading ? <Loader2 className="animate-spin" size={18} /> : <Check size={18} />}
|
||||||
Open in Editor
|
Open in Editor
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user