From 580036d956800d85b245a393dda022a7e5ab5571 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 19:13:51 +0000 Subject: [PATCH 1/5] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/ModelViewer.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 91350cd..1262bf2 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -83,6 +83,7 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive: const edgeThresholdAngle = useModelStore((s) => s.edgeThresholdAngle); const checklist = useModelStore((s) => s.checklist); const setActive = useModelStore((s) => s.setActiveModel); + const selectionMode = useModelStore((s) => s.selectionMode); const fineTuning = sceneModel.fineTuning; const modelInfo = useMemo(() => { @@ -184,7 +185,20 @@ function GLBModel({ sceneModel, isActive }: { sceneModel: SceneModel; isActive: return ( { e.stopPropagation(); setActive(sceneModel.id); }} + onClick={(e) => { + e.stopPropagation(); + if ((e.delta ?? 0) > 4) return; + + if (selectionMode) { + const element = findElementRoot(e.object); + if (element) { + useModelStore.getState().toggleElementSelection(elementKey(sceneModel.id, element)); + } + return; + } + + setActive(sceneModel.id); + }} userData={{ modelId: sceneModel.id, dominantAxis }} > {/* Translation */} From 361135a24e1c93d06cc86c2b8f5179b25d786ae1 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 19:14:01 +0000 Subject: [PATCH 2/5] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/ModelViewer.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 1262bf2..178f115 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -753,11 +753,30 @@ function SelectionHandler() { useModelStore.getState().toggleElementSelection(key); }; + const onR3FMiss = (event: Event) => { + const detail = (event as CustomEvent).detail; + if (!detail || detail.button !== 0) return; + const rect = canvas.getBoundingClientRect(); + mouse.x = ((detail.clientX - rect.left) / rect.width) * 2 - 1; + mouse.y = -((detail.clientY - rect.top) / rect.height) * 2 + 1; + raycaster.setFromCamera(mouse, camera); + const intersects = raycaster.intersectObjects(scene.children, true); + const hit = intersects.find((i) => isPickableModelMesh(i.object)); + if (!hit) return; + + const element = findElementRoot(hit.object); + const modelId = useModelStore.getState().activeModelId; + if (!modelId || !element) return; + useModelStore.getState().toggleElementSelection(elementKey(modelId, element)); + }; + canvas.addEventListener('pointerdown', onDown); canvas.addEventListener('pointerup', onUp); + canvas.addEventListener('tsxr-selection-miss', onR3FMiss); return () => { canvas.removeEventListener('pointerdown', onDown); canvas.removeEventListener('pointerup', onUp); + canvas.removeEventListener('tsxr-selection-miss', onR3FMiss); gl.domElement.style.cursor = prevCursor; }; }, [selectionMode, camera, scene, gl, raycaster, mouse]); From 11fcc998967bef2d26becc5afb0f1181939154c8 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 19:14:09 +0000 Subject: [PATCH 3/5] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/ModelViewer.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 178f115..3baf129 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -1054,6 +1054,11 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) { gl={{ antialias: true, alpha: true, powerPreference: 'high-performance', preserveDrawingBuffer: true }} frameloop="always" className="!bg-background" + onPointerMissed={(e) => { + if (useModelStore.getState().selectionMode) { + gl.domElement.dispatchEvent(new CustomEvent('tsxr-selection-miss', { detail: e })); + } + }} onCreated={({ gl }) => { const isApple = /Mac|iPhone|iPad/.test(navigator.platform); const maxRatio = isApple ? 2 : 1.5; From bd13018f485604d5d2de85d2eae879b43621edf4 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 19:14:18 +0000 Subject: [PATCH 4/5] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/ModelViewer.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 3baf129..eaaa2cc 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -1056,7 +1056,7 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) { className="!bg-background" onPointerMissed={(e) => { if (useModelStore.getState().selectionMode) { - gl.domElement.dispatchEvent(new CustomEvent('tsxr-selection-miss', { detail: e })); + (e.target as HTMLCanvasElement).dispatchEvent(new CustomEvent('tsxr-selection-miss', { detail: e })); } }} onCreated={({ gl }) => { From e5b6c2211b9a51d32b40eb2dea99a37818246c1e 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 19:16:14 +0000 Subject: [PATCH 5/5] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/ModelViewer.tsx | 35 ---------------------------- 1 file changed, 35 deletions(-) diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index eaaa2cc..62cc5e0 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -721,37 +721,6 @@ function SelectionHandler() { const prevCursor = gl.domElement.style.cursor; gl.domElement.style.cursor = 'cell'; const canvas = gl.domElement; - let downX = 0, downY = 0, armed = false; - - const onDown = (e: PointerEvent) => { - if (e.button !== 0) { armed = false; return; } - downX = e.clientX; downY = e.clientY; armed = true; - }; - const onUp = (e: PointerEvent) => { - if (!armed) return; - armed = false; - if (Math.hypot(e.clientX - downX, e.clientY - downY) > 4) return; - - const rect = canvas.getBoundingClientRect(); - mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1; - mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1; - raycaster.setFromCamera(mouse, camera); - const intersects = raycaster.intersectObjects(scene.children, true); - const hit = intersects.find((i) => isPickableModelMesh(i.object)); - if (!hit) return; - - const element = findElementRoot(hit.object); - let cur: THREE.Object3D | null = hit.object; - let modelId: string | null = null; - while (cur) { - if (!modelId && cur.userData?.modelId) modelId = cur.userData.modelId as string; - cur = cur.parent; - } - if (!modelId) modelId = useModelStore.getState().activeModelId; - if (!modelId || !element) return; - const key = elementKey(modelId, element); - useModelStore.getState().toggleElementSelection(key); - }; const onR3FMiss = (event: Event) => { const detail = (event as CustomEvent).detail; @@ -770,12 +739,8 @@ function SelectionHandler() { useModelStore.getState().toggleElementSelection(elementKey(modelId, element)); }; - canvas.addEventListener('pointerdown', onDown); - canvas.addEventListener('pointerup', onUp); canvas.addEventListener('tsxr-selection-miss', onR3FMiss); return () => { - canvas.removeEventListener('pointerdown', onDown); - canvas.removeEventListener('pointerup', onUp); canvas.removeEventListener('tsxr-selection-miss', onR3FMiss); gl.domElement.style.cursor = prevCursor; };