From 515cb97fbdc3567cf11cb35a7412aaaa9e1962eb Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 20:49:35 +0000 Subject: [PATCH 1/9] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/lib/modelTransforms.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/modelTransforms.ts b/src/lib/modelTransforms.ts index c3ef211..c69f135 100644 --- a/src/lib/modelTransforms.ts +++ b/src/lib/modelTransforms.ts @@ -22,6 +22,10 @@ export function getModelLocalGroup(modelId: string | null | undefined): THREE.Ob return localGroups.get(modelId); } +export function getAllModelLocalGroups(): THREE.Object3D[] { + return Array.from(localGroups.values()); +} + export function worldToModelLocal( modelId: string | null | undefined, p: { x: number; y: number; z: number }, From 69b9a6b97ece78a8d8bac30744fed550cf28bde3 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 20:49:48 +0000 Subject: [PATCH 2/9] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/stores/useModelStore.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/stores/useModelStore.ts b/src/stores/useModelStore.ts index 82680f3..62cc9f9 100644 --- a/src/stores/useModelStore.ts +++ b/src/stores/useModelStore.ts @@ -257,6 +257,9 @@ interface ModelStore { setGridAutoFollow: (b: boolean) => void; /** Nudge grid by delta meters and disable auto-follow. */ nudgeGridY: (delta: number) => void; + /** When true, the next click on a model face sets grid Y to that point. */ + gridCalibMode: boolean; + setGridCalibMode: (b: boolean) => void; wireframeColor: string; setWireframeColor: (color: string) => void; From 06f0b6875693aa6c6f37faedb7b0ed2f66b7d6bd Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 20:49:56 +0000 Subject: [PATCH 3/9] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/stores/useModelStore.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stores/useModelStore.ts b/src/stores/useModelStore.ts index 62cc9f9..ba96846 100644 --- a/src/stores/useModelStore.ts +++ b/src/stores/useModelStore.ts @@ -538,6 +538,8 @@ export const useModelStore = create((set, get) => ({ gridAutoFollow: true, setGridAutoFollow: (gridAutoFollow) => set({ gridAutoFollow }), nudgeGridY: (delta) => set((s) => ({ gridY: s.gridY + delta, gridAutoFollow: false })), + gridCalibMode: false, + setGridCalibMode: (gridCalibMode) => set({ gridCalibMode }), wireframeColor: '#00f3ff', setWireframeColor: (wireframeColor) => set({ wireframeColor }), From 15f2bdfbb2aa983d844a53eb6073ee66af710d93 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 20:50:03 +0000 Subject: [PATCH 4/9] 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 2ff0ced..14cbf51 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -5,7 +5,7 @@ import { Loader2 } from 'lucide-react'; import * as THREE from 'three'; import { useModelStore, type SceneModel } from '@/stores/useModelStore'; import { findNearestVertex, detectHoleAtFace, findNearestEdgeSegment, detectCircularEdgeAtPoint } from './SmartMeasure'; -import { registerModelLocalGroup, unregisterModelLocalGroup, getModelWorldScaleFactor } from '@/lib/modelTransforms'; +import { registerModelLocalGroup, unregisterModelLocalGroup, getModelWorldScaleFactor, getAllModelLocalGroups } from '@/lib/modelTransforms'; import { parseIFCtoThree } from '@/lib/convertIFC'; import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; import { mainCameraRef, mainControlsRef, viewAnim, calibration, pushModelFaceNormal, computeCalibrationQuaternion, subscribeCalibration } from './viewCubeBus'; From a769d51d9f9d328a6b39a7a3c5def0c99a706089 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 20:50:18 +0000 Subject: [PATCH 5/9] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/ModelViewer.tsx | 29 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 14cbf51..9fa1ffa 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -1035,34 +1035,39 @@ function GridLayer() { ); } -/** Continuously aligns the grid Y to the bottom of all visible models so the - * piece always looks like it is sitting on the grid — including after - * calibration, fine-tuning, repositioning or scale changes. */ +/** Continuously aligns the grid Y to the bottom of all registered model + * groups (not the entire scene), so the piece always sits on the grid — + * including after calibration, fine-tuning or repositioning. */ function GridAutoFollower() { - const { scene } = useThree(); const lastY = useRef(Number.NaN); const acc = useRef(0); useFrame((_, dt) => { const auto = useModelStore.getState().gridAutoFollow; if (!auto) return; - // Throttle to ~10 Hz to keep this cheap. acc.current += dt; if (acc.current < 0.1) return; acc.current = 0; + const groups = getAllModelLocalGroups(); + if (groups.length === 0) return; const box = new THREE.Box3(); let has = false; - scene.traverse((obj) => { - if (obj instanceof THREE.Mesh && obj.geometry) { + const tmp = new THREE.Box3(); + for (const g of groups) { + g.updateWorldMatrix(true, true); + tmp.makeEmpty(); + g.traverse((obj) => { + if (!(obj instanceof THREE.Mesh) || !obj.geometry) return; if (obj.geometry instanceof THREE.SphereGeometry) return; if (obj.geometry instanceof THREE.RingGeometry) return; if (obj.userData.__edgeLine) return; const b = new THREE.Box3().setFromObject(obj); - if (isFinite(b.min.y)) { - if (!has) { box.copy(b); has = true; } - else box.union(b); - } + if (isFinite(b.min.y)) tmp.union(b); + }); + if (!tmp.isEmpty() && isFinite(tmp.min.y)) { + if (!has) { box.copy(tmp); has = true; } + else box.union(tmp); } - }); + } if (!has) return; const target = box.min.y - 0.005; if (!Number.isFinite(lastY.current) || Math.abs(target - lastY.current) > 1e-4) { From d02afa17977d8080a95053734cc21a6e2f2dd0f8 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 20:50:47 +0000 Subject: [PATCH 6/9] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/ModelViewer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 9fa1ffa..5d9c785 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -1264,6 +1264,7 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) { + From c3d0f6c01d8af6967fc749f4dcfba6a36fd10846 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 20:51:01 +0000 Subject: [PATCH 7/9] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/three/ModelViewer.tsx | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/components/three/ModelViewer.tsx b/src/components/three/ModelViewer.tsx index 5d9c785..992c984 100644 --- a/src/components/three/ModelViewer.tsx +++ b/src/components/three/ModelViewer.tsx @@ -1078,6 +1078,43 @@ function GridAutoFollower() { return null; } +/** When gridCalibMode is on, the next click on the model sets the grid Y to + * the world Y of the clicked surface point and disables auto-follow. */ +function GridCalibrationHandler() { + const { camera, scene, gl } = useThree(); + const gridCalibMode = useModelStore((s) => s.gridCalibMode); + const raycaster = useMemo(() => new THREE.Raycaster(), []); + const mouse = useMemo(() => new THREE.Vector2(), []); + useEffect(() => { + if (!gridCalibMode) return; + const canvas = gl.domElement; + const prevCursor = canvas.style.cursor; + canvas.style.cursor = 'crosshair'; + const onPointerUp = (ev: PointerEvent) => { + if (ev.button !== 0) return; + const rect = canvas.getBoundingClientRect(); + mouse.x = ((ev.clientX - rect.left) / rect.width) * 2 - 1; + mouse.y = -((ev.clientY - rect.top) / rect.height) * 2 + 1; + raycaster.setFromCamera(mouse, camera); + const hits = raycaster.intersectObjects(scene.children, true); + const hit = hits.find((h) => isPickableModelMesh(h.object)); + if (!hit) return; + useModelStore.setState({ + gridY: hit.point.y - 0.001, + gridAutoFollow: false, + gridCalibMode: false, + showGrid: true, + }); + }; + canvas.addEventListener('pointerup', onPointerUp); + return () => { + canvas.removeEventListener('pointerup', onPointerUp); + canvas.style.cursor = prevCursor; + }; + }, [gridCalibMode, camera, scene, gl, raycaster, mouse]); + return null; +} + /** Mouse drag handler for desktop "Posicionar" mode: translates/rotates the active model. */ function PositionDragHandler() { const { camera, gl, scene } = useThree(); From 083c9d83aebe8fce9adaaaad0a1c4aedfc8bd1f2 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 20:51:12 +0000 Subject: [PATCH 8/9] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/ViewerControls.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ViewerControls.tsx b/src/components/ViewerControls.tsx index b7eb4cf..bc8b3ff 100644 --- a/src/components/ViewerControls.tsx +++ b/src/components/ViewerControls.tsx @@ -36,6 +36,7 @@ export function ViewerControls() { selectedElementKeys, hiddenElementKeys, isolatedElementKeys, hideSelectedElements, isolateSelectedElements, showAllElements, clearElementSelection, cameraMode, setCameraMode, + gridCalibMode, setGridCalibMode, setGridAutoFollow, } = useModelStore(); const activeModel = models.find(m => m.id === activeModelId); const hasSelection = selectedElementKeys.size > 0; From 5790df154165215ddcc7334f9bf2b98977f35d26 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sun, 24 May 2026 20:51:23 +0000 Subject: [PATCH 9/9] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/ViewerControls.tsx | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/components/ViewerControls.tsx b/src/components/ViewerControls.tsx index bc8b3ff..741304d 100644 --- a/src/components/ViewerControls.tsx +++ b/src/components/ViewerControls.tsx @@ -107,6 +107,30 @@ export function ViewerControls() { Grid + {/* Grid calibration: click a face on the piece to anchor the grid there */} + + {/* Scale presets */}