Add scene editor preferences migration and validation

This commit is contained in:
2026-04-11 14:25:27 +02:00
parent e664f1e316
commit ed694d2bc7

View File

@@ -76,6 +76,7 @@ import {
ANIMATION_PLAYBACK_SCENE_DOCUMENT_VERSION,
DEFAULT_PROJECT_NAME,
DEFAULT_PROJECT_SCENE_ID,
SCENE_EDITOR_PREFERENCES_SCENE_DOCUMENT_VERSION,
ENTITY_NAMES_SCENE_DOCUMENT_VERSION,
ENTITY_SYSTEM_FOUNDATION_SCENE_DOCUMENT_VERSION,
FACE_MATERIALS_SCENE_DOCUMENT_VERSION,
@@ -101,9 +102,13 @@ import {
WHITEBOX_FLOAT_TRANSFORM_SCENE_DOCUMENT_VERSION,
WHITEBOX_GEOMETRY_SCENE_DOCUMENT_VERSION,
WORLD_ENVIRONMENT_SCENE_DOCUMENT_VERSION,
cloneSceneEditorPreferences,
createDefaultSceneEditorPreferences,
createDefaultSceneLoadingScreenSettings,
createProjectDocumentFromSceneDocument,
type ProjectDocument,
type SceneEditorPanelPreferences,
type SceneEditorPreferences,
type ProjectScene,
type SceneLoadingScreenSettings,
type SceneDocument
@@ -196,6 +201,149 @@ function readSceneLoadingScreen(
};
}
function readSceneEditorPanelPreferences(
value: unknown,
label: string
): SceneEditorPanelPreferences {
if (!isRecord(value)) {
throw new Error(`${label} must be an object.`);
}
const viewMode = expectString(value.viewMode, `${label}.viewMode`);
const displayMode = expectString(value.displayMode, `${label}.displayMode`);
if (
viewMode !== "perspective" &&
viewMode !== "top" &&
viewMode !== "front" &&
viewMode !== "side"
) {
throw new Error(`${label}.viewMode must be a supported viewport view mode.`);
}
if (
displayMode !== "normal" &&
displayMode !== "authoring" &&
displayMode !== "wireframe"
) {
throw new Error(
`${label}.displayMode must be a supported viewport display mode.`
);
}
return {
viewMode,
displayMode
};
}
function readSceneEditorPreferences(
value: unknown,
label: string,
options: { allowMissing: boolean }
): SceneEditorPreferences {
if (value === undefined && options.allowMissing) {
return createDefaultSceneEditorPreferences();
}
if (!isRecord(value)) {
throw new Error(`${label} must be an object.`);
}
const whiteboxSelectionMode = expectString(
value.whiteboxSelectionMode,
`${label}.whiteboxSelectionMode`
);
const viewportLayoutMode = expectString(
value.viewportLayoutMode,
`${label}.viewportLayoutMode`
);
const activeViewportPanelId = expectString(
value.activeViewportPanelId,
`${label}.activeViewportPanelId`
);
if (
whiteboxSelectionMode !== "object" &&
whiteboxSelectionMode !== "face" &&
whiteboxSelectionMode !== "edge" &&
whiteboxSelectionMode !== "vertex"
) {
throw new Error(
`${label}.whiteboxSelectionMode must be a supported whitebox selection mode.`
);
}
if (viewportLayoutMode !== "single" && viewportLayoutMode !== "quad") {
throw new Error(
`${label}.viewportLayoutMode must be a supported viewport layout mode.`
);
}
if (
activeViewportPanelId !== "topLeft" &&
activeViewportPanelId !== "topRight" &&
activeViewportPanelId !== "bottomLeft" &&
activeViewportPanelId !== "bottomRight"
) {
throw new Error(
`${label}.activeViewportPanelId must be a supported viewport panel id.`
);
}
const defaultPreferences = createDefaultSceneEditorPreferences();
return cloneSceneEditorPreferences({
whiteboxSelectionMode,
whiteboxSnapEnabled: expectBoolean(
value.whiteboxSnapEnabled,
`${label}.whiteboxSnapEnabled`
),
whiteboxSnapStep: expectPositiveFiniteNumber(
value.whiteboxSnapStep,
`${label}.whiteboxSnapStep`
),
viewportGridVisible: expectBoolean(
value.viewportGridVisible,
`${label}.viewportGridVisible`
),
viewportLayoutMode,
activeViewportPanelId,
viewportQuadSplit: isRecord(value.viewportQuadSplit)
? {
x: expectFiniteNumber(
value.viewportQuadSplit.x,
`${label}.viewportQuadSplit.x`
),
y: expectFiniteNumber(
value.viewportQuadSplit.y,
`${label}.viewportQuadSplit.y`
)
}
: { ...defaultPreferences.viewportQuadSplit },
viewportPanels: isRecord(value.viewportPanels)
? {
topLeft: readSceneEditorPanelPreferences(
value.viewportPanels.topLeft,
`${label}.viewportPanels.topLeft`
),
topRight: readSceneEditorPanelPreferences(
value.viewportPanels.topRight,
`${label}.viewportPanels.topRight`
),
bottomLeft: readSceneEditorPanelPreferences(
value.viewportPanels.bottomLeft,
`${label}.viewportPanels.bottomLeft`
),
bottomRight: readSceneEditorPanelPreferences(
value.viewportPanels.bottomRight,
`${label}.viewportPanels.bottomRight`
)
}
: defaultPreferences.viewportPanels
});
}
function expectBoolean(value: unknown, label: string): boolean {
if (typeof value !== "boolean") {
throw new Error(`${label} must be a boolean.`);
@@ -2526,7 +2674,10 @@ function readProjectScene(
label: string,
materials: Record<string, MaterialDef>,
assets: Record<string, ProjectAssetRecord>,
options: { allowMissingLoadingScreen: boolean }
options: {
allowMissingLoadingScreen: boolean;
allowMissingEditorPreferences: boolean;
}
): ProjectScene {
if (!isRecord(value)) {
throw new Error(`${label} must be an object.`);
@@ -2542,6 +2693,13 @@ function readProjectScene(
allowMissing: options.allowMissingLoadingScreen
}
),
editorPreferences: readSceneEditorPreferences(
value.editorPreferences,
`${label}.editorPreferences`,
{
allowMissing: options.allowMissingEditorPreferences
}
),
world: readWorldSettings(value.world),
brushes: readBrushes(value.brushes, materials, false),
modelInstances: readModelInstances(value.modelInstances, assets),
@@ -2588,6 +2746,8 @@ export function migrateProjectDocument(source: unknown): ProjectDocument {
source.version === MULTI_SCENE_FOUNDATION_SCENE_DOCUMENT_VERSION;
const allowMissingProjectName =
source.version < PROJECT_NAME_SCENE_DOCUMENT_VERSION;
const allowMissingEditorPreferences =
source.version < SCENE_EDITOR_PREFERENCES_SCENE_DOCUMENT_VERSION;
for (const [sceneKey, sceneValue] of Object.entries(source.scenes)) {
scenes[sceneKey] = readProjectScene(
@@ -2596,7 +2756,8 @@ export function migrateProjectDocument(source: unknown): ProjectDocument {
materials,
assets,
{
allowMissingLoadingScreen
allowMissingLoadingScreen,
allowMissingEditorPreferences
}
);
}