From 57b38437e76401502007a80e89602f916b4f5083 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 02:31:29 +0000 Subject: [PATCH] Changes --- src/lib/convertToGLB.ts | 67 +++++++++++++++++++++++++++++++++++++++++ src/pages/Index.tsx | 46 +++++++++++++++++++++------- 2 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 src/lib/convertToGLB.ts diff --git a/src/lib/convertToGLB.ts b/src/lib/convertToGLB.ts new file mode 100644 index 0000000..8fd63ae --- /dev/null +++ b/src/lib/convertToGLB.ts @@ -0,0 +1,67 @@ +import * as THREE from 'three'; +import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js'; +import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; +import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'; + +type SupportedExt = 'glb' | 'obj' | 'stl'; + +export function getSupportedExtension(fileName: string): SupportedExt | null { + const ext = fileName.split('.').pop()?.toLowerCase(); + if (ext === 'glb' || ext === 'obj' || ext === 'stl') return ext; + return null; +} + +export const ACCEPTED_EXTENSIONS = '.glb,.obj,.stl'; + +/** + * Convert an OBJ or STL file (ArrayBuffer) into a GLB Blob. + * GLB files are returned as-is from their original blob. + */ +export async function convertToGLB( + buffer: ArrayBuffer, + ext: 'obj' | 'stl', + fileName: string +): Promise<{ blob: Blob; fileName: string; fileSize: number }> { + const scene = new THREE.Scene(); + + const material = new THREE.MeshStandardMaterial({ + color: 0x8899aa, + metalness: 0.85, + roughness: 0.35, + }); + + if (ext === 'stl') { + const loader = new STLLoader(); + const geometry = loader.parse(buffer); + geometry.computeVertexNormals(); + const mesh = new THREE.Mesh(geometry, material); + mesh.name = fileName.replace(/\.stl$/i, ''); + scene.add(mesh); + } else { + const loader = new OBJLoader(); + const text = new TextDecoder().decode(buffer); + const obj = loader.parse(text); + obj.traverse((child) => { + if (child instanceof THREE.Mesh) { + child.material = material; + } + }); + obj.name = fileName.replace(/\.obj$/i, ''); + scene.add(obj); + } + + const exporter = new GLTFExporter(); + const glb = await new Promise((resolve, reject) => { + exporter.parse( + scene, + (result) => resolve(result as ArrayBuffer), + (error) => reject(error), + { binary: true } + ); + }); + + const glbFileName = fileName.replace(/\.(obj|stl)$/i, '.glb'); + const blob = new Blob([glb], { type: 'model/gltf-binary' }); + + return { blob, fileName: glbFileName, fileSize: blob.size }; +} diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index fe064e3..68d05b1 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -5,11 +5,13 @@ import { Button } from '@/components/ui/button'; import { useModelStore } from '@/stores/useModelStore'; import { toast } from 'sonner'; import { generateDemoBeamGLB } from '@/lib/generateDemoBeam'; +import { getSupportedExtension, convertToGLB, ACCEPTED_EXTENSIONS } from '@/lib/convertToGLB'; const Index = () => { const navigate = useNavigate(); const fileInputRef = useRef(null); const [loadingDemo, setLoadingDemo] = useState(false); + const [converting, setConverting] = useState(false); const { model, setModel, xrSupported, setXrSupported } = useModelStore(); useEffect(() => { @@ -20,16 +22,37 @@ const Index = () => { } }, [setXrSupported]); - const handleFileUpload = useCallback((e: React.ChangeEvent) => { + const handleFileUpload = useCallback(async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; - if (!file.name.toLowerCase().endsWith('.glb')) { - toast.error('Formato inválido. Por favor, selecione um arquivo .GLB'); + + const ext = getSupportedExtension(file.name); + if (!ext) { + toast.error('Formato inválido. Selecione um arquivo .GLB, .OBJ ou .STL'); return; } - const url = URL.createObjectURL(file); - setModel({ fileName: file.name, fileSize: file.size, url }); - toast.success(`Modelo "${file.name}" carregado com sucesso!`); + + if (ext === 'glb') { + const url = URL.createObjectURL(file); + setModel({ fileName: file.name, fileSize: file.size, url }); + toast.success(`Modelo "${file.name}" carregado com sucesso!`); + return; + } + + // Convert OBJ/STL to GLB + setConverting(true); + try { + const buffer = await file.arrayBuffer(); + const { blob, fileName, fileSize } = await convertToGLB(buffer, ext, file.name); + const url = URL.createObjectURL(blob); + setModel({ fileName, fileSize, url }); + toast.success(`"${file.name}" convertido para GLB e carregado!`); + } catch (err) { + console.error(err); + toast.error(`Falha ao converter "${file.name}"`); + } finally { + setConverting(false); + } }, [setModel]); const handleLoadDemo = useCallback(async () => { @@ -60,7 +83,7 @@ const Index = () => { @@ -84,12 +107,13 @@ const Index = () => {