diff --git a/apps/editor/components/editor/index.tsx b/apps/editor/components/editor/index.tsx
index 7d36e1ab..f2d15187 100644
--- a/apps/editor/components/editor/index.tsx
+++ b/apps/editor/components/editor/index.tsx
@@ -178,29 +178,3 @@ const Grid = ({
)
}
-
-const LevelModeSwitcher = () => {
- const setLevelMode = useViewer((state) => state.setLevelMode)
- const levelMode = useViewer((state) => state.levelMode)
-
- return (
-
-
-
-
- )
-}
diff --git a/apps/editor/components/tools/item/use-placement-coordinator.tsx b/apps/editor/components/tools/item/use-placement-coordinator.tsx
index 94a4285a..7e163096 100644
--- a/apps/editor/components/tools/item/use-placement-coordinator.tsx
+++ b/apps/editor/components/tools/item/use-placement-coordinator.tsx
@@ -418,8 +418,10 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
const newRotationY = (currentRotation[1] ?? 0) + rotationDelta
draft.rotation = [currentRotation[0], newRotationY, currentRotation[2]]
- // Ref + cursor mesh only — no store update during drag
+ // Ref + cursor mesh + item mesh — no store update during drag
cursorRef.current.rotation.y = newRotationY
+ const mesh = sceneRegistry.nodes.get(draft.id)
+ if (mesh) mesh.rotation.y = newRotationY
revalidate()
}
}
diff --git a/apps/editor/components/ui/action-menu/index.tsx b/apps/editor/components/ui/action-menu/index.tsx
index bc3ceec9..6c5b16c8 100644
--- a/apps/editor/components/ui/action-menu/index.tsx
+++ b/apps/editor/components/ui/action-menu/index.tsx
@@ -10,7 +10,7 @@ import useEditor from "@/store/use-editor";
import { AnimatePresence, motion } from "motion/react";
import { ItemCatalog } from "../item-catalog/item-catalog";
import { FurnishTools } from "./furnish-tools";
-// import { ViewToggles } from "./view-toggles";
+import { ViewToggles } from "./view-toggles";
export function ActionMenu({ className }: { className?: string }) {
const phase = useEditor((state) => state.phase);
@@ -138,8 +138,8 @@ export function ActionMenu({ className }: { className?: string }) {
- {/* */}
- {/* */}
+
+
diff --git a/apps/editor/components/ui/action-menu/view-toggles.tsx b/apps/editor/components/ui/action-menu/view-toggles.tsx
new file mode 100644
index 00000000..48bd6294
--- /dev/null
+++ b/apps/editor/components/ui/action-menu/view-toggles.tsx
@@ -0,0 +1,139 @@
+'use client'
+
+import { useViewer } from '@pascal-app/viewer'
+import { Box, Camera, Diamond, Image, Layers, Layers2 } from 'lucide-react'
+import { Button } from '@/components/ui/primitives/button'
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/components/ui/primitives/tooltip'
+import { cn } from '@/lib/utils'
+
+const levelModeLabels: Record<'stacked' | 'exploded' | 'solo', string> = {
+ stacked: 'Stacked',
+ exploded: 'Exploded',
+ solo: 'Solo',
+}
+
+const levelModeOrder: ('stacked' | 'exploded' | 'solo')[] = ['stacked', 'exploded', 'solo']
+
+export function ViewToggles() {
+ const cameraMode = useViewer((state) => state.cameraMode)
+ const setCameraMode = useViewer((state) => state.setCameraMode)
+ const levelMode = useViewer((state) => state.levelMode)
+ const setLevelMode = useViewer((state) => state.setLevelMode)
+ const showScans = useViewer((state) => state.showScans)
+ const setShowScans = useViewer((state) => state.setShowScans)
+ const showGuides = useViewer((state) => state.showGuides)
+ const setShowGuides = useViewer((state) => state.setShowGuides)
+
+ const toggleCameraMode = () => {
+ setCameraMode(cameraMode === 'perspective' ? 'orthographic' : 'perspective')
+ }
+
+ const cycleLevelMode = () => {
+ if (levelMode === 'manual') {
+ setLevelMode('stacked')
+ return
+ }
+ const currentIndex = levelModeOrder.indexOf(levelMode as 'stacked' | 'exploded' | 'solo')
+ const nextIndex = (currentIndex + 1) % levelModeOrder.length
+ const nextMode = levelModeOrder[nextIndex]
+ if (nextMode) setLevelMode(nextMode)
+ }
+
+ return (
+
+ {/* Camera Mode */}
+
+
+
+
+
+ Camera: {cameraMode === 'perspective' ? 'Perspective' : 'Orthographic'}
+
+
+
+ {/* Level Mode */}
+
+
+
+
+
+ Levels: {levelMode === 'manual' ? 'Manual' : levelModeLabels[levelMode as keyof typeof levelModeLabels]}
+
+
+
+ {/* Show Scans */}
+
+
+
+
+
+ Scans: {showScans ? 'Visible' : 'Hidden'}
+
+
+
+ {/* Show Guides */}
+
+
+
+
+
+ Guides: {showGuides ? 'Visible' : 'Hidden'}
+
+
+
+ )
+}
diff --git a/packages/viewer/src/components/viewer/index.tsx b/packages/viewer/src/components/viewer/index.tsx
index fc4cfaa1..0f10ed49 100644
--- a/packages/viewer/src/components/viewer/index.tsx
+++ b/packages/viewer/src/components/viewer/index.tsx
@@ -7,6 +7,7 @@ import * as THREE from 'three/webgpu'
import { LevelSystem } from '../../systems/level/level-system'
import { SceneRenderer } from '../renderers/scene-renderer'
import PostProcessing from './post-processing'
+import { ViewerCamera } from './viewer-camera'
declare module '@react-three/fiber' {
interface ThreeElements extends ThreeToJSXElements {}
@@ -31,6 +32,7 @@ const Viewer: React.FC = ({ children }) => {
camera={{ position: [50, 50, 50], fov: 50 }}
>
+
diff --git a/packages/viewer/src/components/viewer/viewer-camera.tsx b/packages/viewer/src/components/viewer/viewer-camera.tsx
new file mode 100644
index 00000000..20d7ccaa
--- /dev/null
+++ b/packages/viewer/src/components/viewer/viewer-camera.tsx
@@ -0,0 +1,11 @@
+import { OrthographicCamera, PerspectiveCamera } from "@react-three/drei"
+import useViewer from "../../store/use-viewer"
+
+export const ViewerCamera = () => {
+ const cameraMode = useViewer((state) => state.cameraMode)
+ return cameraMode === 'perspective' ? (
+
+ ) : (
+
+ )
+}
\ No newline at end of file
diff --git a/packages/viewer/src/store/use-viewer.ts b/packages/viewer/src/store/use-viewer.ts
index 57a6ac4f..88e1f784 100644
--- a/packages/viewer/src/store/use-viewer.ts
+++ b/packages/viewer/src/store/use-viewer.ts
@@ -34,6 +34,12 @@ type ViewerState = {
levelMode: "stacked" | "exploded" | "solo" | "manual";
setLevelMode: (mode: "stacked" | "exploded" | "solo" | "manual") => void;
+ showScans: boolean;
+ setShowScans: (show: boolean) => void;
+
+ showGuides: boolean;
+ setShowGuides: (show: boolean) => void;
+
// Smart selection update
setSelection: (updates: Partial) => void;
resetSelection: () => void;
@@ -52,6 +58,12 @@ const useViewer = create()((set, get) => ({
levelMode: "stacked",
setLevelMode: (mode) => set({ levelMode: mode }),
+ showScans: true,
+ setShowScans: (show) => set({ showScans: show }),
+
+ showGuides: true,
+ setShowGuides: (show) => set({ showGuides: show }),
+
setSelection: (updates) =>
set((state) => {
const newSelection = { ...state.selection, ...updates };
diff --git a/packages/viewer/src/systems/level/level-system.tsx b/packages/viewer/src/systems/level/level-system.tsx
index 3f573f6d..3151851d 100644
--- a/packages/viewer/src/systems/level/level-system.tsx
+++ b/packages/viewer/src/systems/level/level-system.tsx
@@ -9,14 +9,17 @@ const EXPLODED_GAP = 5
export const LevelSystem = () => {
useFrame((_, delta) => {
const levelMode = useViewer.getState().levelMode
+ const selectedLevel = useViewer.getState().selection.levelId
sceneRegistry.byType.level.forEach((levelId) => {
const obj = sceneRegistry.nodes.get(levelId)
if (obj) {
const level = useScene.getState().nodes[levelId as LevelNode['id']]
const targetY =
((level as any).level || 0) *
- (LEVEL_HEIGHT + (levelMode === 'stacked' ? 0 : EXPLODED_GAP))
+ (LEVEL_HEIGHT + (levelMode === 'exploded' ? EXPLODED_GAP: 0))
obj.position.y = lerp(obj.position.y, targetY, delta * 3)
+
+ obj.visible = levelMode !== 'solo' || level?.id === selectedLevel || !selectedLevel
}
})
})