## Modo Posicionar (desktop) Permite que, antes de entrar no AR, o usuário mova e gire a peça **ativa** com o mouse — exatamente como faria com o controle no AR. As outras peças permanecem travadas. Ao desativar o botão, todas ficam fixas nas posições atuais e o estado persiste (já temos `savePlacement` por `fileName`). ### Comportamento - Botão **Posicionar** (ícone Move3D) na barra `ViewerControls`, ao lado de Medir. - Quando ativo: - `OrbitControls` é desabilitado (não conflitar com o arrasto da peça). - Cursor vira `move`. - Apenas a peça com `activeModelId` responde ao mouse. Clicar em outra peça troca a ativa e passa a manipulá-la (as outras voltam a ficar travadas). - **Arrastar com botão esquerdo** → translada no plano da câmera (X/Y da tela projetados para o mundo). - **Shift + arrastar esquerdo** ou **arrastar com botão do meio** → translada no eixo Z (profundidade) usando o movimento vertical do mouse. - **Arrastar com botão direito** → rotaciona: movimento horizontal = rotY (yaw), movimento vertical = rotX (pitch). **Shift + direito** = rotZ (roll). - Cada movimento atualiza `fineTuning` via `setFineTuning` da peça ativa, o que já dispara `savePlacement` (persistência entre sessões/entrar-sair do AR — mesma chave usada hoje). - Quando desativado: tudo volta ao normal (Orbit ativo, peças travadas onde foram deixadas). Modo Medir e Posicionar são mutuamente exclusivos. ### Arquivos a alterar 1. **`src/stores/useModelStore.ts`** - Adicionar `positionMode: boolean` + `setPositionMode(on)`. - Em `setPositionMode(true)` desligar `measureMode`; e vice-versa em `setMeasureMode`. 2. **`src/components/three/ModelViewer.tsx`** - Novo componente `PositionDragHandler` (dentro do Canvas) que: - Escuta `pointerdown/move/up` no `gl.domElement` quando `positionMode` está on e existe `activeModelId`. - Converte delta de pixels em delta de mundo usando a distância câmera↔peça e o FOV (`2 * dist * tan(fov/2) / canvasHeight`), depois divide pelo `scaleRatio.factor` para gravar em coordenadas locais coerentes com `fineTuning`. - Aplica via `setFineTuning({ posX: ..., posY: ..., posZ: ..., rotX: ..., rotY: ..., rotZ: ... })`. - Em `OrbitControls`, definir `enabled={!positionMode && !measureMode}` (medir já não rotaciona hoje só por cursor, mas reforça). - Cursor: quando `positionMode`, setar `gl.domElement.style.cursor = 'move'`. 3. **`src/components/ViewerControls.tsx`** - Importar ícone `Move3D` do lucide. - Novo botão **Posicionar** com `variant={positionMode ? 'default' : 'outline'}` chamando `setPositionMode(!positionMode)`. Label dinâmica: `Posicionar` / `Posicionando — [nome da peça ativa]`. ### Detalhes técnicos - A translação é gravada em `fineTuning.pos{X,Y,Z}` (espaço local da peça, antes do `renderFactor` da escala). Para que 1 pixel ↔ 1 mm visual fique coerente, o cálculo divide o delta-mundo por `scaleRatio.factor` (ex: em 1:50, mover 100 px arrasta o objeto 100 px de tela tanto na visualização quanto na coordenada interna). - Rotação em graus (já é o formato de `fineTuning.rotX/Y/Z`), sensibilidade ~`0.4°/px`. - O bloqueio de outras peças é automático: o handler só atualiza a `activeModelId`. Clicar em outra peça (handler `onClick` já existente em `GLBModel`) muda a ativa. - Persistência: `setFineTuning` já chama `savePlacement(fileName, fineTuning)`, então sair/voltar do AR mantém posições. - Sem mudanças no AR; apenas no viewer desktop.