From 06f645f3ee3679b9a65a871676c47133a0c28eab Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 16:44:54 +0000 Subject: [PATCH] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/pages/Viewer.tsx | 85 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/src/pages/Viewer.tsx b/src/pages/Viewer.tsx index d19e899..3a8a672 100644 --- a/src/pages/Viewer.tsx +++ b/src/pages/Viewer.tsx @@ -20,7 +20,9 @@ import { convertIFCtoGLB } from "@/lib/convertIFC"; const Viewer = () => { const navigate = useNavigate(); - const { model, xrSupported } = useModelStore(); + const { model, xrSupported, addModel, models, maxModels } = useModelStore(); + const fileInputRef = useRef(null); + const [converting, setConverting] = useState(false); useEffect(() => { if (!model) { @@ -28,6 +30,42 @@ const Viewer = () => { } }, [model, navigate]); + const handleFileUpload = useCallback(async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + e.target.value = ''; + if (!file) return; + if (models.length >= maxModels) { + toast.error(`Limite de ${maxModels} peças simultâneas atingido`); + return; + } + const ext = getSupportedExtension(file.name); + if (!ext) { + toast.error('Formato inválido. Selecione .GLB, .OBJ, .STL ou .IFC'); + return; + } + if (ext === 'glb') { + const url = URL.createObjectURL(file); + addModel({ fileName: file.name, fileSize: file.size, url }); + toast.success(`"${file.name}" adicionado à cena`); + return; + } + setConverting(true); + try { + const buffer = await file.arrayBuffer(); + const result = ext === 'ifc' + ? await convertIFCtoGLB(buffer, file.name) + : await convertToGLB(buffer, ext, file.name); + const url = URL.createObjectURL(result.blob); + addModel({ fileName: result.fileName, fileSize: result.fileSize, url }); + toast.success(`"${file.name}" convertido e adicionado!`); + } catch (err) { + console.error(err); + toast.error(`Falha ao converter "${file.name}"`); + } finally { + setConverting(false); + } + }, [addModel, models.length, maxModels]); + if (!model) return null; const handleEnterXR = async () => { @@ -38,8 +76,18 @@ const Viewer = () => { navigate("/xr"); }; + const canAddMore = models.length < maxModels; + return (
+ + {/* Top bar */}
@@ -66,7 +114,7 @@ const Viewer = () => {
{/* 3D Canvas with floating controls */}
- +
@@ -75,8 +123,33 @@ const Viewer = () => {
-

- Informações do Modelo +
+

+ Cena · {models.length}/{maxModels} peças +

+
+ +
+ + +
+

+ + + +
+

+ Peça selecionada

@@ -102,8 +175,8 @@ const Viewer = () => {

- Use o mouse para orbitar, scroll para zoom. Controle a opacidade e o modo de renderização nos - controles flutuantes. + Clique em uma peça na cena para selecioná-la — os controles de ajuste fino + passam a operar sobre ela. Use "Adicionar" / "Nuvem" para empilhar peças.