From 360daac84373ab378bc46631c13f9bdc31c522be 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 21:38:05 +0000 Subject: [PATCH 1/8] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/XRGrabbable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/three/XRGrabbable.tsx b/src/components/three/XRGrabbable.tsx index 0cb8774..6b6a9ea 100644 --- a/src/components/three/XRGrabbable.tsx +++ b/src/components/three/XRGrabbable.tsx @@ -6,8 +6,8 @@ import { useModelStore } from '@/stores/useModelStore'; interface XRGrabbableProps { /** - * @deprecated Mantido por compatibilidade — agora o zoom por duas mãos - * está SEMPRE ativo (faz parte do mapeamento padrão do grip). + * Quando true (padrão), o grip duplo aplica ZOOM uniforme além da rotação. + * Quando false, o grip duplo só ORBITA — a escala da peça é preservada. */ allowScale?: boolean; /** When true, the active model is locked — grab is ignored entirely */ From 71b1e9ae3fc92b8140eb119896627e66131b814c 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 21:38:11 +0000 Subject: [PATCH 2/8] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/XRGrabbable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/three/XRGrabbable.tsx b/src/components/three/XRGrabbable.tsx index 6b6a9ea..cd1804b 100644 --- a/src/components/three/XRGrabbable.tsx +++ b/src/components/three/XRGrabbable.tsx @@ -51,7 +51,7 @@ const _tmpQuat = new THREE.Quaternion(); * * Trigger não interfere aqui (reservado para seleção/medição). */ -export function XRGrabbable({ lockedActive = false, onGrabStart, children }: XRGrabbableProps) { +export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabStart, children }: XRGrabbableProps) { const groupRef = useRef(null); const grab = useControllerGrab(); From 073a3f8dc85697deece91d364508ed63bddae55d 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 21:38:18 +0000 Subject: [PATCH 3/8] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/XRGrabbable.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/three/XRGrabbable.tsx b/src/components/three/XRGrabbable.tsx index cd1804b..69acba7 100644 --- a/src/components/three/XRGrabbable.tsx +++ b/src/components/three/XRGrabbable.tsx @@ -121,8 +121,10 @@ export function XRGrabbable({ allowScale = true, lockedActive = false, onGrabSta const d = dual.current; const deltaQuat = new THREE.Quaternion().setFromUnitVectors(d.startAxis, axisNow); - // ZOOM sempre ativo no grip duplo - const scaleRatio = THREE.MathUtils.clamp(distNow / d.startDist, 0.1, 10); + // ZOOM só quando allowScale=true. Caso contrário, mantém escala 1× (só orbita). + const scaleRatio = allowScale + ? THREE.MathUtils.clamp(distNow / d.startDist, 0.1, 10) + : 1; const tToOrigin = new THREE.Matrix4().makeTranslation(-d.startMid.x, -d.startMid.y, -d.startMid.z); const tBack = new THREE.Matrix4().makeTranslation(midNow.x, midNow.y, midNow.z); From da9399305e351f2875a62444e4d058cf82002e1e 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 21:38:25 +0000 Subject: [PATCH 4/8] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/pages/XRSession.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pages/XRSession.tsx b/src/pages/XRSession.tsx index 9164e34..8bb52ae 100644 --- a/src/pages/XRSession.tsx +++ b/src/pages/XRSession.tsx @@ -471,9 +471,8 @@ const XRSession = () => { const [freeMove, setFreeMove] = useState(true); const [placementMode, setPlacementMode] = useState(true); // start in placement mode const [snapToPlanes, setSnapToPlanes] = useState(true); - // Zoom por grip duplo é SEMPRE ligado (parte do mapeamento padrão). - const allowScale = true; - const setAllowScale = (_v?: boolean) => {}; + // Zoom por grip duplo — toggle real, padrão ligado. + const [allowScale, setAllowScale] = useState(true); const [liveCode, setLiveCode] = useState(null); const [liveViewers, setLiveViewers] = useState(0); // Rig que envolve — locomoção por joystick escreve aqui. From de11c4e1197bdaeb684d0a956c83c13bc34b3b45 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 21:38:42 +0000 Subject: [PATCH 5/8] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/XRControllerMeasure.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/three/XRControllerMeasure.tsx b/src/components/three/XRControllerMeasure.tsx index 63b9af9..72b542b 100644 --- a/src/components/three/XRControllerMeasure.tsx +++ b/src/components/three/XRControllerMeasure.tsx @@ -149,18 +149,20 @@ export function XRControllerMeasure() { snappedPoint = bestHoleCenter; snapKind = 'hole'; } else if (snapEnabled) { - // Try detecting a circular edge (hole) at hit - const circle = detectCircularEdgeAtPoint(hit.object, hit.point, fakeCam, canvasSize, 60); + // Try detecting a circular edge (hole) at hit — radius mais generoso + const circle = detectCircularEdgeAtPoint(hit.object, hit.point, fakeCam, canvasSize, 90); if (circle) { snappedPoint = circle.center; snapKind = 'hole'; hoverDetected = { kind: 'hole', value: circle.diameterMM, position: circle.center }; } else { - const snap = resolveSnap(hit.object, hit.point, fakeCam, canvasSize, 14, 18); + // Snap mais "magnético": raios maiores de busca em pixels + // vértice: 32px (antes 14), aresta: 48px (antes 18) + const snap = resolveSnap(hit.object, hit.point, fakeCam, canvasSize, 32, 48); snappedPoint = snap.point; snapKind = snap.type; if (snap.type === 'edge') { - const seg = findNearestEdgeSegment(hit.object, hit.point, fakeCam, canvasSize, 18); + const seg = findNearestEdgeSegment(hit.object, hit.point, fakeCam, canvasSize, 48); if (seg) { const lenMM = seg.a.distanceTo(seg.b) * 1000; hoverDetected = { kind: 'edge', value: lenMM, position: seg.midpoint, endpoints: { a: seg.a, b: seg.b } }; @@ -172,7 +174,9 @@ export function XRControllerMeasure() { snapKind = 'surface'; } } - // Cache for non-heavy frames so the laser + trigger still have a target + // Cache for non-heavy frames so the laser + trigger still have a target. + // Sticky: se conseguimos um snap "forte" (vertex/edge/hole), preservamos + // ele entre frames leves para evitar cintilação. cachedSnappedPoint.current = snappedPoint ? snappedPoint.clone() : null; cachedSnapKind.current = snapKind; } else { From 33b32fb2680ed54c6287e3339fe98a6d8d00d5b8 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 21:39:01 +0000 Subject: [PATCH 6/8] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/XRControllerMeasure.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/three/XRControllerMeasure.tsx b/src/components/three/XRControllerMeasure.tsx index 72b542b..d5149e8 100644 --- a/src/components/three/XRControllerMeasure.tsx +++ b/src/components/three/XRControllerMeasure.tsx @@ -231,9 +231,13 @@ export function XRControllerMeasure() { if (snappedPoint) { tipRef.current.visible = true; tipRef.current.position.copy(snappedPoint); - // Scale tip with distance from controller (~12mm at 1m) + // Tip scales with distance from controller. Snap "forte" (vértice, + // aresta, furo) recebe um marcador ~3× maior do que uma superfície + // genérica, para que o ponto destacado fique bem visível. const dist = tmpOrigin.current.distanceTo(snappedPoint); - const s = Math.max(0.004, dist * 0.012); + const strongSnap = snapKind === 'vertex' || snapKind === 'edge' || snapKind === 'hole'; + const factor = strongSnap ? 0.030 : 0.012; + const s = Math.max(strongSnap ? 0.010 : 0.004, dist * factor); tipRef.current.scale.setScalar(s); } } From 6c022c514f87ffc024cf3a14355b34cbbdc4cdc4 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 21:39:39 +0000 Subject: [PATCH 7/8] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/XRHudInWorld.tsx | 52 ++++++++++++++++++--------- 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/src/components/three/XRHudInWorld.tsx b/src/components/three/XRHudInWorld.tsx index 4f89c49..dd41d6e 100644 --- a/src/components/three/XRHudInWorld.tsx +++ b/src/components/three/XRHudInWorld.tsx @@ -328,6 +328,15 @@ function ToolsTab(p: XRHudInWorldProps) { const setShowGrid = useModelStore((s) => s.setShowGrid); const measureMode = useModelStore((s) => s.measureMode); const setMeasureMode = useModelStore((s) => s.setMeasureMode); + const selectionMode = useModelStore((s) => s.selectionMode); + const setSelectionMode = useModelStore((s) => s.setSelectionMode); + const selectedCount = useModelStore((s) => s.selectedElementKeys.size); + const hideSelectedElements = useModelStore((s) => s.hideSelectedElements); + const isolateSelectedElements = useModelStore((s) => s.isolateSelectedElements); + const showAllElements = useModelStore((s) => s.showAllElements); + const hasHidden = useModelStore((s) => + s.hiddenElementKeys.size > 0 || s.isolatedElementKeys !== null + ); const scaleRatio = useModelStore((s) => s.scaleRatio); const setScaleRatio = useModelStore((s) => s.setScaleRatio); const resetScale = useModelStore((s) => s.resetScale); @@ -353,52 +362,61 @@ function ToolsTab(p: XRHudInWorldProps) { + - + {/* Selecionar — paridade com a tela inicial */} + Selecionar + 0 ? `${selectedCount} sel.` : 'Selec…') : 'Selec'} + active={selectionMode} onClick={() => setSelectionMode(!selectionMode)} /> + + + + + Opacidade {Math.round(opacity * 100)}% - setOpacity(Math.max(0, opacity - 0.25))} /> - setOpacity(Math.max(0, opacity - 0.05))} /> - setOpacity(Math.min(1, opacity + 0.05))} /> - setOpacity(Math.min(1, opacity + 0.25))} /> - setOpacity(1)} /> - + Escala {scaleRatio.label} {SCALE_LABELS.map((lbl, i) => { const preset = SCALE_PRESETS.find((sp) => sp.label === lbl)!; return ( setScaleRatio(preset)} /> ); })} - {/* Reset Escala — devolve a peça ao tamanho 1:1 (cancela qualquer - escala manual feita com duas mãos). */} - - {/* Limpa todos os labels de medição. */} - - {/* Legenda dos controles para Medir */} {measureMode && ( - + Gatilho D: marcar · A: desfazer · B: limpar · Gatilho E: alternar snap )} - - {/* Grid floor controls */} ); From 45a7bdbd1b8716adc86dfc9438b5d6690afb73ea 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 21:39:49 +0000 Subject: [PATCH 8/8] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/XRHud.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/XRHud.tsx b/src/components/XRHud.tsx index 386072c..9d2c3fb 100644 --- a/src/components/XRHud.tsx +++ b/src/components/XRHud.tsx @@ -310,6 +310,19 @@ export function XRHud({ freeMove, onToggleFreeMove, placementMode = false, onTog )} + {/* Allow scale (two-hand zoom) toggle */} + {onToggleAllowScale && ( + + )} + {/* Scale presets */}