From ad37b2c37aef6529f7d7b2b538e093eedd4044c5 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Thu, 21 May 2026 14:48:19 +0000 Subject: [PATCH] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- .lovable/plan.md | 113 ++++++++++++++++++++++++++++++----------------- 1 file changed, 73 insertions(+), 40 deletions(-) diff --git a/.lovable/plan.md b/.lovable/plan.md index 0c60c7a..f82e664 100644 --- a/.lovable/plan.md +++ b/.lovable/plan.md @@ -1,50 +1,83 @@ -# Correções no modo Medir -## Problemas -1. Ao ativar **Medir**, o OrbitControls é desabilitado (`enabled={!positionMode && !measureMode}`), bloqueando rotação/zoom/pan naturais com o mouse. -2. O label da medida usa `useLabelDistanceFactor` que recalcula `distanceFactor` por frame para manter tamanho **constante** na tela. Resultado: ao aproximar a peça o texto não cresce e fica ilegível. +# Seleção de elementos IFC — Esconder, Isolar e Exportar GLB -## Mudanças em `src/components/three/ModelViewer.tsx` +Adicionar um modo de **seleção por elemento** (viga, chapa, etc.) que permite ao usuário escolher partes do modelo e então **esconder**, **isolar** ou **exportar como GLB reduzido** para reuso. Funciona no desktop e no AR/XR (Quest 3) em uma única fase. -### 1. Reabilitar controles de câmera no modo Medir -No `` (linha ~792), trocar: -```tsx -enabled={!positionMode && !measureMode} +## Fluxo do usuário + +1. Usuário ativa botão **"Selecionar"** no ViewerControls (e no XRHud). +2. Clica em elementos do modelo — cada clique alterna seleção (highlight laranja/emissive). +3. Contador mostra "N elementos selecionados". +4. Três ações disponíveis: + - **Esconder selecionados** → some com eles + - **Isolar selecionados** → esconde todo o resto + - **Mostrar tudo** → restaura +5. Botão extra **"Exportar GLB"** salva arquivo `.glb` contendo apenas os elementos selecionados, baixável pelo navegador. Esse GLB pode ser recarregado no app pelo fluxo normal de import. +6. Estado de visibilidade é **persistido por `fileName`** no `localStorage`, igual ao `PLACEMENT_KEY`, sobrevivendo a recarregar a página e a sair/entrar do AR. + +## Granularidade + +Seleção opera no **elemento IFC inteiro** (uma viga, uma chapa). O converter de IFC preserva a hierarquia: cada `IfcProduct` vira um `Group` ou `Mesh` nomeado no GLB. O picking sobe na árvore até encontrar o ancestral marcado como "elemento IFC raiz" (campo `userData.ifcElement = true` adicionado durante a conversão), garantindo que clicar em qualquer face seleciona a viga toda. + +## Detalhes técnicos + +### 1. `src/lib/convertIFC.ts` +- Durante a conversão IFC→GLB, marcar cada `IfcProduct` resultante com `obj.userData.ifcElement = true` e `userData.ifcId = expressId`. Sem isso, a árvore three.js fica plana e não dá pra distinguir "elemento" de "submesh". + +### 2. `src/stores/useModelStore.ts` +Novo slice de seleção: +```ts +selectionMode: boolean +selectedElementIds: Set // chave: `${modelId}:${ifcId|uuid}` +hiddenElementIds: Set +isolatedElementIds: Set | null // null = sem isolamento +toggleSelection(key), clearSelection(), +hideSelected(), isolateSelected(), showAll() ``` -por: -```tsx -enabled={!positionMode} +Persistência de `hiddenElementIds`/`isolatedElementIds` por `fileName` em `localStorage` (chave nova `tsxr_visibility_v1`), aplicada ao carregar o modelo. + +### 3. `src/components/three/ModelViewer.tsx` +- Novo componente `SelectionHandler`: ativo quando `selectionMode === true`. Raycaster no `onPointerDown`, sobe a árvore via `object.parent` até achar `userData.ifcElement === true`, faz toggle no store. Desabilita OrbitControls durante o clique para não conflitar. +- Aplicar visibilidade: `useEffect` percorre `scene.traverse`, define `obj.visible` conforme `hiddenElementIds`/`isolatedElementIds`. +- Highlight de seleção: material `emissive` laranja (`#f59e0b`, mesma cor de holes) com `emissiveIntensity = 0.4`, restaurado ao deselecionar. Guardar material original em `userData.originalMaterial`. +- `selectionMode` é mutuamente exclusivo com `measureMode` e `positionMode` (mesma lógica que esses dois já têm entre si). + +### 4. `src/components/three/XRControllerMeasure.tsx` (ou novo `XRControllerSelect.tsx`) +- Quando `selectionMode === true` em XR: usar o mesmo ray do controller, no `selectstart` (trigger) faz toggle de seleção em vez de pontos de medição. +- Tip do laser muda para laranja durante o modo seleção. + +### 5. `src/components/ViewerControls.tsx` + `src/components/XRHud.tsx` +Novos botões: +- `Selecionar` (toggle, ícone `MousePointerClick`) +- `Esconder` / `Isolar` / `Mostrar tudo` (visíveis quando `selectionMode` ou há ocultos) +- `Exportar GLB` (visível quando há seleção) +- Badge com contador "N selecionados" + +### 6. `src/lib/exportSelectionGLB.ts` (novo) +```ts +import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'; +// Clona apenas os elementos selecionados em uma nova Scene, +// preserva transformações mundiais, exporta como .glb binário, +// dispara download via Blob + . ``` -O `MeasureClickHandler` já tem **drag gate de 4 px** (`pointerdown` + `pointerup` só conta como clique se o mouse moveu menos que 4 px e foi botão esquerdo). Isso significa: -- Botão esquerdo arrastando → OrbitControls rotaciona, nenhum ponto é adicionado. -- Botão direito → pan do OrbitControls (handler ignora `e.button !== 0`). -- Roda → zoom do OrbitControls (handler ignora). -- Clique curto esquerdo → adiciona ponto de medida (com snap). +Nome do arquivo: `{original}_selecao_{N}elem.glb`. O GLB resultante pode ser aberto pelo próprio app via fluxo normal de import (já aceita `.glb`). -### 2. Label da medida escala com o zoom e fica ~4x maior -Substituir `MeasurementLabel` para usar `distanceFactor` **fixo** (em vez de calculado por frame). Com ``, o tamanho na tela é proporcional a `N / distance` — ou seja, **cresce naturalmente ao aproximar a câmera**. +### 7. Performance +- Aplicar visibilidade via `obj.visible = false` (não remove geometria — reversível e barato). +- Frustum culling do three.js já ignora invisíveis no render → ganho real de FPS no Quest mesmo sem exportar. -- Remover o uso de `useLabelDistanceFactor` no label de medida (manter, se desejado, apenas no `hoverInfo`). -- Definir `distanceFactor` fixo calibrado para 1:1 mm (valor inicial ~`8`, ajustável). -- Aumentar o tamanho base do texto de `text-[11px]` para `text-[16px]` e `px-2 py-0.5` → `px-3 py-1` para acompanhar a proporção (~4x área visual final somando o aumento do distanceFactor + texto). +## Arquivos tocados -```tsx -function MeasurementLabel({ position, text, variant }: { ... }) { - const borderClass = variant === 'success' ? 'border-success/60' : 'border-primary/60'; - const textClass = variant === 'success' ? 'text-success' : 'text-primary'; - return ( - -
- - {text} - -
- - ); -} -``` +- `src/lib/convertIFC.ts` — marcar `userData.ifcElement` +- `src/lib/exportSelectionGLB.ts` — **novo** +- `src/stores/useModelStore.ts` — slice de seleção + persistência +- `src/components/three/ModelViewer.tsx` — `SelectionHandler`, highlight, aplicar visibilidade +- `src/components/three/XRControllerMeasure.tsx` — handler de seleção em XR +- `src/components/ViewerControls.tsx` — botões desktop +- `src/components/XRHud.tsx` — botões XR -O `hoverInfo` (auto-detect) pode manter o comportamento atual de tamanho constante para não poluir a cena ao passar o mouse. +## Fora do escopo (decidido) -## Sem mudanças em outros arquivos -Store, AR, snap e PointMarker permanecem intocados. +- **Não** exportar IFC reduzido (decidido: arquivo é só pra este app, GLB resolve e é trivial). +- **Não** suportar seleção por face/sub-mesh (decidido: elemento inteiro). +- **Não** salvar conjuntos nomeados de seleção ("layout A", "layout B") — pode virar Fase 2 se você pedir.