Files
workinf_Blender_Wasm/web/protocol/corrective-mesh-command.ts
mes123456 a081c68c87
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Reorganize Blender Web execution around capabilities
2026-08-24 18:37:46 -04:00

73 lines
3.5 KiB
TypeScript

import { CorrectiveCommandRegistry } from "./corrective-command-registry";
import { CorrectiveCaseRunnerError, MainMutationCommand } from "./corrective-case-runner";
import { InMemoryMainStore, runAtomicMutation } from "./corrective-main-transaction";
export interface MeshVertexState {
id: string;
x: number;
y: number;
z: number;
}
export interface MeshMain {
vertices: MeshVertexState[];
}
const VERTEX_ID = /^[A-Za-z0-9._:-]{1,128}$/u;
const COORDINATE_LIMIT = 1_000_000;
function coordinate(value: unknown, label: string): number {
if (typeof value !== "number" || !Number.isFinite(value) || Math.abs(value) > COORDINATE_LIMIT) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", `${label} must be a finite coordinate within bounds`);
}
return value;
}
function meshPayload(command: MainMutationCommand): { vertexId: string; delta: { x: number; y: number; z: number } } {
const input = command.payload;
if (!input || typeof input !== "object" || Array.isArray(input) ||
typeof input.vertexId !== "string" || !VERTEX_ID.test(input.vertexId) ||
!input.delta || typeof input.delta !== "object" || Array.isArray(input.delta) ||
Object.keys(input).some((key) => !["vertexId", "delta"].includes(key))) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "mesh payload must contain only vertexId and delta");
}
const delta = input.delta as Record<string, unknown>;
if (Object.keys(delta).some((key) => !["x", "y", "z"].includes(key)) ||
!Object.prototype.hasOwnProperty.call(delta, "x") || !Object.prototype.hasOwnProperty.call(delta, "y") || !Object.prototype.hasOwnProperty.call(delta, "z")) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "mesh delta must contain only x, y, and z");
}
return { vertexId: input.vertexId, delta: { x: coordinate(delta.x, "delta.x"), y: coordinate(delta.y, "delta.y"), z: coordinate(delta.z, "delta.z") } };
}
function cloneMeshMain(value: MeshMain): MeshMain {
return { vertices: value.vertices.map((vertex) => ({ ...vertex })) };
}
export function createMeshRegistry(main: InMemoryMainStore<MeshMain>): CorrectiveCommandRegistry {
return new CorrectiveCommandRegistry().register({
commandType: "meshTranslateVertex",
targetKinds: ["MESH"],
authorizations: ["USER_EDIT", "SYSTEM"],
execute: async (command) => {
const change = meshPayload(command);
return runAtomicMutation(main, command, (draft) => {
const vertex = draft.vertices.find((candidate) => candidate.id === change.vertexId);
if (!vertex) throw new CorrectiveCaseRunnerError("COMMAND_UNSUPPORTED", `vertex ${change.vertexId} does not exist`);
const before = { x: vertex.x, y: vertex.y, z: vertex.z };
const after = { x: before.x + change.delta.x, y: before.y + change.delta.y, z: before.z + change.delta.z };
if ([after.x, after.y, after.z].some((value) => !Number.isFinite(value) || Math.abs(value) > COORDINATE_LIMIT)) {
throw new CorrectiveCaseRunnerError("COMMAND_MALFORMED", "mesh translation exceeds coordinate bounds");
}
vertex.x = after.x;
vertex.y = after.y;
vertex.z = after.z;
return { delta: { vertexId: vertex.id, before, after }, persistence: { dirty: true } };
});
},
});
}
export function createMeshMain(vertices: readonly MeshVertexState[], revision = 0, dirty = false): InMemoryMainStore<MeshMain> {
return new InMemoryMainStore({ vertices: vertices.map((vertex) => ({ ...vertex })) }, revision, dirty, cloneMeshMain);
}