Add set box brush name command and update scene document migration

This commit is contained in:
2026-03-31 04:22:46 +02:00
parent 0040bcde35
commit 07999f8038
4 changed files with 76 additions and 1 deletions

View File

@@ -0,0 +1,46 @@
import { createOpaqueId } from "../core/ids";
import { normalizeBrushName } from "../document/brushes";
import { getBoxBrushOrThrow, replaceBrush } from "./brush-command-helpers";
import type { EditorCommand } from "./command";
interface SetBoxBrushNameCommandOptions {
brushId: string;
name: string | null;
}
export function createSetBoxBrushNameCommand(options: SetBoxBrushNameCommandOptions): EditorCommand {
const normalizedName = normalizeBrushName(options.name);
let previousName: string | undefined;
return {
id: createOpaqueId("command"),
label: normalizedName === undefined ? "Clear box brush name" : `Rename box brush to ${normalizedName}`,
execute(context) {
const currentDocument = context.getDocument();
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
if (previousName === undefined) {
previousName = brush.name;
}
context.setDocument(
replaceBrush(currentDocument, {
...brush,
name: normalizedName
})
);
},
undo(context) {
const currentDocument = context.getDocument();
const brush = getBoxBrushOrThrow(currentDocument, options.brushId);
context.setDocument(
replaceBrush(currentDocument, {
...brush,
name: previousName
})
);
}
};
}

View File

@@ -5,6 +5,7 @@ import {
createDefaultFaceUvState, createDefaultFaceUvState,
isBoxFaceId, isBoxFaceId,
isFaceUvRotationQuarterTurns, isFaceUvRotationQuarterTurns,
normalizeBrushName,
type BoxBrushFaces, type BoxBrushFaces,
type BrushFace, type BrushFace,
type FaceUvState type FaceUvState
@@ -13,6 +14,7 @@ import {
BOX_BRUSH_SCENE_DOCUMENT_VERSION, BOX_BRUSH_SCENE_DOCUMENT_VERSION,
FACE_MATERIALS_SCENE_DOCUMENT_VERSION, FACE_MATERIALS_SCENE_DOCUMENT_VERSION,
FOUNDATION_SCENE_DOCUMENT_VERSION, FOUNDATION_SCENE_DOCUMENT_VERSION,
RUNNER_V1_SCENE_DOCUMENT_VERSION,
SCENE_DOCUMENT_VERSION, SCENE_DOCUMENT_VERSION,
type SceneDocument, type SceneDocument,
type WorldSettings type WorldSettings
@@ -80,6 +82,10 @@ function expectOptionalString(value: unknown, label: string): string | undefined
return expectString(value, label); return expectString(value, label);
} }
function readOptionalBrushName(value: unknown, label: string): string | undefined {
return normalizeBrushName(expectOptionalString(value, label));
}
function expectEmptyCollection(value: unknown, label: string): Record<string, never> { function expectEmptyCollection(value: unknown, label: string): Record<string, never> {
if (!isRecord(value)) { if (!isRecord(value)) {
throw new Error(`${label} must be a record.`); throw new Error(`${label} must be a record.`);
@@ -264,6 +270,7 @@ function readBrushes(
brushes[brushId] = createBoxBrush({ brushes[brushId] = createBoxBrush({
id: expectString(brushValue.id, `brushes.${brushId}.id`), id: expectString(brushValue.id, `brushes.${brushId}.id`),
name: readOptionalBrushName(brushValue.name, `brushes.${brushId}.name`),
center, center,
size, size,
faces: readBoxBrushFaces(brushValue.faces, `brushes.${brushId}.faces`, materials, allowMissingUvState), faces: readBoxBrushFaces(brushValue.faces, `brushes.${brushId}.faces`, materials, allowMissingUvState),
@@ -428,6 +435,23 @@ export function migrateSceneDocument(source: unknown): SceneDocument {
}; };
} }
if (source.version === RUNNER_V1_SCENE_DOCUMENT_VERSION) {
const materials = readMaterialRegistry(source.materials, "materials");
return {
version: SCENE_DOCUMENT_VERSION,
name: expectString(source.name, "name"),
world: readWorldSettings(source.world),
materials,
textures: expectEmptyCollection(source.textures, "textures"),
assets: expectEmptyCollection(source.assets, "assets"),
brushes: readBrushes(source.brushes, materials, false),
modelInstances: expectEmptyCollection(source.modelInstances, "modelInstances"),
entities: readEntities(source.entities),
interactionLinks: expectEmptyCollection(source.interactionLinks, "interactionLinks")
};
}
if (source.version !== SCENE_DOCUMENT_VERSION) { if (source.version !== SCENE_DOCUMENT_VERSION) {
throw new Error(`Unsupported scene document version: ${String(source.version)}.`); throw new Error(`Unsupported scene document version: ${String(source.version)}.`);
} }

View File

@@ -109,6 +109,10 @@ export function validateSceneDocument(document: SceneDocument): SceneDocumentVal
registerAuthoredId(brush.id, path, seenIds, diagnostics); registerAuthoredId(brush.id, path, seenIds, diagnostics);
if (brush.name !== undefined && brush.name.trim().length === 0) {
diagnostics.push(createDiagnostic("error", "invalid-box-name", "Box brush names must be non-empty when authored.", `${path}.name`));
}
if (!isFiniteVec3(brush.size) || !hasPositiveBoxSize(brush.size)) { if (!isFiniteVec3(brush.size) || !hasPositiveBoxSize(brush.size)) {
diagnostics.push( diagnostics.push(
createDiagnostic("error", "invalid-box-size", "Box brush sizes must remain finite and positive on every axis.", `${path}.size`) createDiagnostic("error", "invalid-box-size", "Box brush sizes must remain finite and positive on every axis.", `${path}.size`)

View File

@@ -3,10 +3,11 @@ import type { Brush } from "./brushes";
import type { EntityInstance } from "../entities/entity-instances"; import type { EntityInstance } from "../entities/entity-instances";
import { cloneMaterialRegistry, createStarterMaterialRegistry, type MaterialDef } from "../materials/starter-material-library"; import { cloneMaterialRegistry, createStarterMaterialRegistry, type MaterialDef } from "../materials/starter-material-library";
export const SCENE_DOCUMENT_VERSION = 4 as const; export const SCENE_DOCUMENT_VERSION = 5 as const;
export const FOUNDATION_SCENE_DOCUMENT_VERSION = 1 as const; export const FOUNDATION_SCENE_DOCUMENT_VERSION = 1 as const;
export const BOX_BRUSH_SCENE_DOCUMENT_VERSION = 2 as const; export const BOX_BRUSH_SCENE_DOCUMENT_VERSION = 2 as const;
export const FACE_MATERIALS_SCENE_DOCUMENT_VERSION = 3 as const; export const FACE_MATERIALS_SCENE_DOCUMENT_VERSION = 3 as const;
export const RUNNER_V1_SCENE_DOCUMENT_VERSION = 4 as const;
export interface WorldBackgroundSettings { export interface WorldBackgroundSettings {
mode: "solid"; mode: "solid";