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
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
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 { Sparkles, Upload, FileText, Image as ImageIcon, Box, Check, Loader2, ArrowRight, RefreshCw, ChevronLeft } from 'lucide-react';
|
||||
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 = [
|
||||
{ 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
|
||||
setCurrentStep(4);
|
||||
setIsLoading(false);
|
||||
|
||||
// Poll Meshy status
|
||||
let isDone = false;
|
||||
@@ -131,6 +139,7 @@ export default function SparkPlug() {
|
||||
}
|
||||
|
||||
if (status === 'SUCCEEDED') {
|
||||
setModelProgress(100);
|
||||
setGeneratedGlbUrl(model_urls.glb);
|
||||
isDone = true;
|
||||
// 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
|
||||
const res = await api.saveProject({
|
||||
name: "Spark Plug Output",
|
||||
name: pdfFile?.name ? pdfFile.name.replace(/\.[^/.]+$/, "") : "Spark Plug Output",
|
||||
scene_data: {
|
||||
objects: [
|
||||
{
|
||||
id: "model_" + Date.now(),
|
||||
type: "model",
|
||||
sceneObjects: [],
|
||||
modelData: {
|
||||
name: "Generated Product Model",
|
||||
visible: true,
|
||||
position: [0, 0, 0],
|
||||
rotation: [0, 0, 0],
|
||||
scale: [1, 1, 1],
|
||||
url: generatedGlbUrl
|
||||
}
|
||||
]
|
||||
data: generatedGlbUrl,
|
||||
extension: "glb"
|
||||
},
|
||||
backgroundColor: '#444444',
|
||||
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) {
|
||||
@@ -367,7 +375,9 @@ export default function SparkPlug() {
|
||||
)}
|
||||
|
||||
{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">
|
||||
<Box size={32} className="text-zinc-500 animate-pulse" />
|
||||
</div>
|
||||
@@ -375,7 +385,7 @@ export default function SparkPlug() {
|
||||
<div className="text-center w-full max-w-sm">
|
||||
<h2 className="text-xl font-bold mb-2">Generating 3D Model...</h2>
|
||||
<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>
|
||||
|
||||
<div className="w-full bg-zinc-950 rounded-full h-3 overflow-hidden">
|
||||
@@ -386,16 +396,32 @@ export default function SparkPlug() {
|
||||
</div>
|
||||
<div className="text-xs font-bold text-amber-500 mt-2">{modelProgress}% Complete</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
|
||||
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"
|
||||
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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user