Adicionou grid adaptável ao IFC
X-Lovable-Edit-ID: edt-0b286cd7-2265-46f5-ba51-e84ee407a19c Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com>
This commit is contained in:
@@ -454,10 +454,11 @@ function MeasureClickHandler() {
|
|||||||
|
|
||||||
function GridLayer() {
|
function GridLayer() {
|
||||||
const showGrid = useModelStore((s) => s.showGrid);
|
const showGrid = useModelStore((s) => s.showGrid);
|
||||||
|
const gridY = useModelStore((s) => s.gridY);
|
||||||
if (!showGrid) return null;
|
if (!showGrid) return null;
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
position={[0, -0.09, 0]}
|
position={[0, gridY, 0]}
|
||||||
infiniteGrid
|
infiniteGrid
|
||||||
cellSize={0.01}
|
cellSize={0.01}
|
||||||
sectionSize={0.1}
|
sectionSize={0.1}
|
||||||
@@ -471,6 +472,41 @@ function GridLayer() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Computes min Y of all visible models and updates store gridY when auto-follow is on. */
|
||||||
|
function GridAutoFollower() {
|
||||||
|
const models = useModelStore((s) => s.models);
|
||||||
|
const { scene } = useThree();
|
||||||
|
useEffect(() => {
|
||||||
|
const auto = useModelStore.getState().gridAutoFollow;
|
||||||
|
if (!auto) return;
|
||||||
|
// Defer to next tick so models have rendered/positioned
|
||||||
|
const id = setTimeout(() => {
|
||||||
|
if (!useModelStore.getState().gridAutoFollow) return;
|
||||||
|
const box = new THREE.Box3();
|
||||||
|
let has = false;
|
||||||
|
scene.traverse((obj) => {
|
||||||
|
if (obj instanceof THREE.Mesh && obj.geometry) {
|
||||||
|
// Skip helpers (markers, rings)
|
||||||
|
if (obj.geometry instanceof THREE.SphereGeometry) return;
|
||||||
|
if (obj.geometry instanceof THREE.RingGeometry) return;
|
||||||
|
if (obj.userData.__edgeLine) return;
|
||||||
|
obj.updateWorldMatrix(true, false);
|
||||||
|
const b = new THREE.Box3().setFromObject(obj);
|
||||||
|
if (isFinite(b.min.y)) {
|
||||||
|
if (!has) { box.copy(b); has = true; }
|
||||||
|
else box.union(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (has) {
|
||||||
|
useModelStore.setState({ gridY: box.min.y - 0.005 });
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
}, [models, scene]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
||||||
return (
|
return (
|
||||||
<Canvas
|
<Canvas
|
||||||
@@ -498,6 +534,7 @@ export function ModelViewerCanvas({ url }: ModelViewerProps) {
|
|||||||
<HoverDetector />
|
<HoverDetector />
|
||||||
|
|
||||||
<GridLayer />
|
<GridLayer />
|
||||||
|
<GridAutoFollower />
|
||||||
|
|
||||||
<OrbitControls
|
<OrbitControls
|
||||||
makeDefault
|
makeDefault
|
||||||
|
|||||||
@@ -309,6 +309,40 @@ function ToolsTab(p: XRHudInWorldProps) {
|
|||||||
onClick={() => setScaleRatio(preset)} />
|
onClick={() => setScaleRatio(preset)} />
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* Grid floor controls */}
|
||||||
|
<GridFloorRow />
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function GridFloorRow() {
|
||||||
|
const { camera } = useThree();
|
||||||
|
const gridY = useModelStore((s) => s.gridY);
|
||||||
|
const gridAutoFollow = useModelStore((s) => s.gridAutoFollow);
|
||||||
|
const setGridAutoFollow = useModelStore((s) => s.setGridAutoFollow);
|
||||||
|
const setGridY = useModelStore((s) => s.setGridY);
|
||||||
|
const nudgeGridY = useModelStore((s) => s.nudgeGridY);
|
||||||
|
const placeAtFloor = () => {
|
||||||
|
// Headset eye Y minus avg standing eye height (1.6 m)
|
||||||
|
const floor = camera.position.y - 1.6;
|
||||||
|
setGridY(floor);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<group>
|
||||||
|
<Text position={[-0.24, -0.15, 0]} fontSize={0.008} color="#94a3b8" anchorX="left">
|
||||||
|
Grid Y {(gridY * 1000).toFixed(0)}mm {gridAutoFollow ? '(auto)' : '(travado)'}
|
||||||
|
</Text>
|
||||||
|
<XR3DButton position={[-0.16, -0.175, 0]} size={[0.055, 0.022]} label="Auto"
|
||||||
|
active={gridAutoFollow} onClick={() => setGridAutoFollow(true)} />
|
||||||
|
<XR3DButton position={[-0.10, -0.175, 0]} size={[0.055, 0.022]} label="Travar"
|
||||||
|
active={!gridAutoFollow} onClick={() => setGridAutoFollow(false)} />
|
||||||
|
<XR3DButton position={[-0.04, -0.175, 0]} size={[0.04, 0.022]} label="−1cm"
|
||||||
|
onClick={() => nudgeGridY(-0.01)} />
|
||||||
|
<XR3DButton position={[0.005, -0.175, 0]} size={[0.04, 0.022]} label="+1cm"
|
||||||
|
onClick={() => nudgeGridY(0.01)} />
|
||||||
|
<XR3DButton position={[0.07, -0.175, 0]} size={[0.075, 0.022]} label="Pousar chão"
|
||||||
|
onClick={placeAtFloor} />
|
||||||
</group>
|
</group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-1
@@ -359,10 +359,11 @@ function XRSnapHandler() {
|
|||||||
// ─── XR Grid ───────────────────────────────────────────
|
// ─── XR Grid ───────────────────────────────────────────
|
||||||
function XRGrid() {
|
function XRGrid() {
|
||||||
const showGrid = useModelStore((s) => s.showGrid);
|
const showGrid = useModelStore((s) => s.showGrid);
|
||||||
|
const gridY = useModelStore((s) => s.gridY);
|
||||||
if (!showGrid) return null;
|
if (!showGrid) return null;
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
position={[0, -0.09, 0]}
|
position={[0, gridY, 0]}
|
||||||
infiniteGrid
|
infiniteGrid
|
||||||
cellSize={0.01}
|
cellSize={0.01}
|
||||||
sectionSize={0.1}
|
sectionSize={0.1}
|
||||||
@@ -376,6 +377,36 @@ function XRGrid() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Auto-follow grid to model bottom when enabled. */
|
||||||
|
function XRGridAutoFollower() {
|
||||||
|
const models = useModelStore((s) => s.models);
|
||||||
|
const { scene } = useThree();
|
||||||
|
useEffect(() => {
|
||||||
|
if (!useModelStore.getState().gridAutoFollow) return;
|
||||||
|
const id = setTimeout(() => {
|
||||||
|
if (!useModelStore.getState().gridAutoFollow) return;
|
||||||
|
const box = new THREE.Box3();
|
||||||
|
let has = false;
|
||||||
|
scene.traverse((obj) => {
|
||||||
|
if (obj instanceof THREE.Mesh && obj.geometry) {
|
||||||
|
if (obj.geometry instanceof THREE.SphereGeometry) return;
|
||||||
|
if (obj.geometry instanceof THREE.RingGeometry) return;
|
||||||
|
if (obj.userData.__edgeLine) return;
|
||||||
|
obj.updateWorldMatrix(true, false);
|
||||||
|
const b = new THREE.Box3().setFromObject(obj);
|
||||||
|
if (isFinite(b.min.y)) {
|
||||||
|
if (!has) { box.copy(b); has = true; }
|
||||||
|
else box.union(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (has) useModelStore.setState({ gridY: box.min.y - 0.005 });
|
||||||
|
}, 150);
|
||||||
|
return () => clearTimeout(id);
|
||||||
|
}, [models, scene]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// ImageTrackingAnchor removed — replaced by XRHitTestPlacement
|
// ImageTrackingAnchor removed — replaced by XRHitTestPlacement
|
||||||
|
|
||||||
// ─── XRSession Page ────────────────────────────────────
|
// ─── XRSession Page ────────────────────────────────────
|
||||||
@@ -585,6 +616,7 @@ const XRSession = () => {
|
|||||||
|
|
||||||
<XRSnapHandler />
|
<XRSnapHandler />
|
||||||
<XRGrid />
|
<XRGrid />
|
||||||
|
<XRGridAutoFollower />
|
||||||
</XR>
|
</XR>
|
||||||
</Canvas>
|
</Canvas>
|
||||||
|
|
||||||
|
|||||||
@@ -133,6 +133,15 @@ interface ModelStore {
|
|||||||
showGrid: boolean;
|
showGrid: boolean;
|
||||||
setShowGrid: (show: boolean) => void;
|
setShowGrid: (show: boolean) => void;
|
||||||
|
|
||||||
|
/** World Y position of the grid plane (meters). */
|
||||||
|
gridY: number;
|
||||||
|
setGridY: (y: number) => void;
|
||||||
|
/** When true, grid follows the bottom of the loaded model automatically. */
|
||||||
|
gridAutoFollow: boolean;
|
||||||
|
setGridAutoFollow: (b: boolean) => void;
|
||||||
|
/** Nudge grid by delta meters and disable auto-follow. */
|
||||||
|
nudgeGridY: (delta: number) => void;
|
||||||
|
|
||||||
wireframeColor: string;
|
wireframeColor: string;
|
||||||
setWireframeColor: (color: string) => void;
|
setWireframeColor: (color: string) => void;
|
||||||
|
|
||||||
@@ -308,6 +317,12 @@ export const useModelStore = create<ModelStore>((set, get) => ({
|
|||||||
showGrid: true,
|
showGrid: true,
|
||||||
setShowGrid: (showGrid) => set({ showGrid }),
|
setShowGrid: (showGrid) => set({ showGrid }),
|
||||||
|
|
||||||
|
gridY: -0.09,
|
||||||
|
setGridY: (gridY) => set({ gridY, gridAutoFollow: false }),
|
||||||
|
gridAutoFollow: true,
|
||||||
|
setGridAutoFollow: (gridAutoFollow) => set({ gridAutoFollow }),
|
||||||
|
nudgeGridY: (delta) => set((s) => ({ gridY: s.gridY + delta, gridAutoFollow: false })),
|
||||||
|
|
||||||
wireframeColor: '#8899aa',
|
wireframeColor: '#8899aa',
|
||||||
setWireframeColor: (wireframeColor) => set({ wireframeColor }),
|
setWireframeColor: (wireframeColor) => set({ wireframeColor }),
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user