fix: harden editor scene loading
This commit is contained in:
@@ -71,7 +71,7 @@ export function resolveSurfaceColor(
|
||||
// The active scene theme may tint individual roles (e.g. Mediterranean's blue
|
||||
// roof); fall back to the chosen colour preset's palette when it doesn't.
|
||||
const tints = sceneThemeId ? getSceneTheme(sceneThemeId).clayTints : undefined
|
||||
return tints?.[role] ?? PRESET_PALETTES[preset][role]
|
||||
return tints?.[role] ?? (PRESET_PALETTES[preset] ?? CLAY_PALETTE)[role]
|
||||
}
|
||||
|
||||
// DoubleSide on any NodeMaterial inside the MRT scenePass (SSGI's output /
|
||||
|
||||
@@ -7,6 +7,7 @@ import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import type { EdgeMode } from '../lib/edge-style'
|
||||
import type { ColorPreset, RenderShading } from '../lib/materials'
|
||||
import { SCENE_THEME_IDS } from '../lib/scene-themes'
|
||||
|
||||
export type RenderContext = 'editor' | 'viewer'
|
||||
|
||||
@@ -114,6 +115,85 @@ type ViewerState = {
|
||||
setInputDragging: (dragging: boolean) => void
|
||||
}
|
||||
|
||||
type PersistedViewerState = Partial<
|
||||
Pick<
|
||||
ViewerState,
|
||||
| 'cameraMode'
|
||||
| 'sceneTheme'
|
||||
| 'shadingByContext'
|
||||
| 'textures'
|
||||
| 'colorPreset'
|
||||
| 'edges'
|
||||
| 'shadows'
|
||||
| 'unit'
|
||||
| 'levelMode'
|
||||
| 'wallMode'
|
||||
| 'projectPreferences'
|
||||
>
|
||||
>
|
||||
|
||||
const CAMERA_MODES = ['perspective', 'orthographic'] as const
|
||||
const RENDER_SHADINGS = ['solid', 'rendered'] as const
|
||||
const COLOR_PRESETS = ['clay', 'white', 'mono', 'blueprint'] as const
|
||||
const EDGE_MODES = ['off', 'soft', 'strong'] as const
|
||||
const UNITS = ['metric', 'imperial'] as const
|
||||
const LEVEL_MODES = ['stacked', 'exploded', 'solo', 'manual'] as const
|
||||
const WALL_MODES = ['up', 'cutaway', 'down'] as const
|
||||
|
||||
function pickString<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {
|
||||
return typeof value === 'string' && allowed.includes(value as T) ? (value as T) : fallback
|
||||
}
|
||||
|
||||
function normalizeShadingByContext(value: unknown): ViewerState['shadingByContext'] {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
||||
|
||||
const next: ViewerState['shadingByContext'] = {}
|
||||
for (const [context, shading] of Object.entries(value)) {
|
||||
if (context !== 'editor' && context !== 'viewer') continue
|
||||
next[context] = pickString<RenderShading>(shading, RENDER_SHADINGS, 'rendered')
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
function normalizeProjectPreferences(value: unknown): ViewerState['projectPreferences'] {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
||||
|
||||
const next: ViewerState['projectPreferences'] = {}
|
||||
for (const [projectId, preferences] of Object.entries(value)) {
|
||||
if (!preferences || typeof preferences !== 'object' || Array.isArray(preferences)) continue
|
||||
const record = preferences as Record<string, unknown>
|
||||
next[projectId] = {
|
||||
...(typeof record.showScans === 'boolean' ? { showScans: record.showScans } : {}),
|
||||
...(typeof record.showGuides === 'boolean' ? { showGuides: record.showGuides } : {}),
|
||||
...(typeof record.showGrid === 'boolean' ? { showGrid: record.showGrid } : {}),
|
||||
}
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
function normalizePersistedViewerState(value: unknown): PersistedViewerState {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
|
||||
const state = value as Record<string, unknown>
|
||||
|
||||
return {
|
||||
cameraMode: pickString<ViewerState['cameraMode']>(
|
||||
state.cameraMode,
|
||||
CAMERA_MODES,
|
||||
'perspective',
|
||||
),
|
||||
sceneTheme: pickString(state.sceneTheme, SCENE_THEME_IDS, 'studio'),
|
||||
shadingByContext: normalizeShadingByContext(state.shadingByContext),
|
||||
textures: typeof state.textures === 'boolean' ? state.textures : true,
|
||||
colorPreset: pickString<ColorPreset>(state.colorPreset, COLOR_PRESETS, 'clay'),
|
||||
edges: pickString<EdgeMode>(state.edges, EDGE_MODES, 'soft'),
|
||||
shadows: typeof state.shadows === 'boolean' ? state.shadows : true,
|
||||
unit: pickString<ViewerState['unit']>(state.unit, UNITS, 'metric'),
|
||||
levelMode: pickString<ViewerState['levelMode']>(state.levelMode, LEVEL_MODES, 'stacked'),
|
||||
wallMode: pickString<ViewerState['wallMode']>(state.wallMode, WALL_MODES, 'up'),
|
||||
projectPreferences: normalizeProjectPreferences(state.projectPreferences),
|
||||
}
|
||||
}
|
||||
|
||||
const useViewer = create<ViewerState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
@@ -267,6 +347,10 @@ const useViewer = create<ViewerState>()(
|
||||
}),
|
||||
{
|
||||
name: 'viewer-preferences',
|
||||
merge: (persistedState, currentState) => ({
|
||||
...currentState,
|
||||
...normalizePersistedViewerState(persistedState),
|
||||
}),
|
||||
partialize: (state) => ({
|
||||
cameraMode: state.cameraMode,
|
||||
sceneTheme: state.sceneTheme,
|
||||
|
||||
Reference in New Issue
Block a user