feat(web): complete user-facing workflow command coverage
This commit is contained in:
@@ -20,6 +20,9 @@ export const animationRenderCommands = Object.freeze({
|
||||
setNLAStack(objectId: string, tracks: AnimationCommand<"setNLAStack">["tracks"], baseRevision?: number): AnimationCommand<"setNLAStack"> {
|
||||
return { type: "setNLAStack", objectId, tracks, ...(baseRevision === undefined ? {} : { baseRevision }) };
|
||||
},
|
||||
moveNLAStrip(objectId: string, trackId: string, stripId: string, frameStart: number, baseRevision: number): AnimationCommand<"moveNLAStrip"> {
|
||||
return { type: "moveNLAStrip", objectId, trackId, stripId, frameStart, baseRevision };
|
||||
},
|
||||
setLightProperties(dataId: string, properties: AnimationCommand<"setLightProperties">["properties"]): AnimationCommand<"setLightProperties"> {
|
||||
return { type: "setLightProperties", dataId, properties };
|
||||
},
|
||||
|
||||
@@ -2,20 +2,88 @@ import type { WebEngineEditCommand } from "./web-engine";
|
||||
|
||||
type NonMeshCommand<T extends WebEngineEditCommand["type"]> = Extract<WebEngineEditCommand, { type: T }>;
|
||||
|
||||
/** Typed constructors for curve, font, volume and Grease Pencil editing. */
|
||||
/** Typed constructors for non-mesh data, curve/surface topology, font, volume and Grease Pencil editing. */
|
||||
export const nonMeshGeometryCommands = Object.freeze({
|
||||
createCurve(name: string, controlPoints: readonly number[], cyclic = false, resolution = 12): NonMeshCommand<"createCurve"> {
|
||||
return { type: "createCurve", curveType: "CURVE", name, controlPoints: [...controlPoints], cyclic, resolution };
|
||||
},
|
||||
deleteNonMeshData(dataId: string): NonMeshCommand<"deleteNonMeshData"> {
|
||||
return { type: "deleteNonMeshData", dataId };
|
||||
},
|
||||
setCurveControlPoints(dataId: string, controlPoints: readonly number[], splineOffsets?: readonly number[], resolution?: number): NonMeshCommand<"setCurveControlPoints"> {
|
||||
return { type: "setCurveControlPoints", dataId, controlPoints: [...controlPoints], ...(splineOffsets ? { splineOffsets: [...splineOffsets] } : {}), ...(resolution === undefined ? {} : { resolution }) };
|
||||
},
|
||||
setCurveHandle(dataId: string, pointIndex: number, side: "LEFT" | "RIGHT", position: readonly [number, number, number]): NonMeshCommand<"setCurveHandle"> {
|
||||
return { type: "setCurveHandle", dataId, pointIndex, side, position: [...position] as [number, number, number] };
|
||||
},
|
||||
setCurveSplines(
|
||||
dataId: string,
|
||||
splineTypes: ReadonlyArray<NonMeshCommand<"setCurveSplines">["splineTypes"][number]>,
|
||||
splineOffsets: readonly number[],
|
||||
ordersU: readonly number[],
|
||||
controlPoints: readonly number[],
|
||||
pointWeights: readonly number[],
|
||||
cyclicU: readonly boolean[],
|
||||
handleTypes: readonly number[],
|
||||
handlePoints: readonly number[],
|
||||
): NonMeshCommand<"setCurveSplines"> {
|
||||
return {
|
||||
type: "setCurveSplines",
|
||||
dataId,
|
||||
splineTypes: [...splineTypes],
|
||||
splineOffsets: [...splineOffsets],
|
||||
ordersU: [...ordersU],
|
||||
controlPoints: [...controlPoints],
|
||||
pointWeights: [...pointWeights],
|
||||
cyclicU: [...cyclicU],
|
||||
handleTypes: [...handleTypes],
|
||||
handlePoints: [...handlePoints],
|
||||
};
|
||||
},
|
||||
setSurfaceTopology(
|
||||
dataId: string,
|
||||
splineDimensions: ReadonlyArray<NonMeshCommand<"setSurfaceTopology">["splineDimensions"][number]>,
|
||||
controlPoints: readonly number[],
|
||||
pointWeights: readonly number[],
|
||||
cyclicU: readonly boolean[],
|
||||
cyclicV: readonly boolean[],
|
||||
): NonMeshCommand<"setSurfaceTopology"> {
|
||||
return {
|
||||
type: "setSurfaceTopology",
|
||||
dataId,
|
||||
splineDimensions: splineDimensions.map((dimension) => ({ ...dimension })),
|
||||
controlPoints: [...controlPoints],
|
||||
pointWeights: [...pointWeights],
|
||||
cyclicU: [...cyclicU],
|
||||
cyclicV: [...cyclicV],
|
||||
};
|
||||
},
|
||||
setFontBody(dataId: string, body: string): NonMeshCommand<"setFontBody"> {
|
||||
return { type: "setFontBody", dataId, body };
|
||||
},
|
||||
setFontProperties(dataId: string, properties: NonMeshCommand<"setFontProperties">["properties"]): NonMeshCommand<"setFontProperties"> {
|
||||
return { type: "setFontProperties", dataId, properties: { ...properties } };
|
||||
},
|
||||
setFontAdvanced(
|
||||
dataId: string,
|
||||
characters: ReadonlyArray<NonMeshCommand<"setFontAdvanced">["characters"][number]>,
|
||||
textBoxes: ReadonlyArray<NonMeshCommand<"setFontAdvanced">["textBoxes"][number]>,
|
||||
activeTextBox: number,
|
||||
): NonMeshCommand<"setFontAdvanced"> {
|
||||
return {
|
||||
type: "setFontAdvanced",
|
||||
dataId,
|
||||
characters: characters.map((character) => ({ ...character })),
|
||||
textBoxes: textBoxes.map((textBox) => ({ ...textBox })),
|
||||
activeTextBox,
|
||||
};
|
||||
},
|
||||
importVFont(payload: Omit<NonMeshCommand<"importVFont">, "type">): NonMeshCommand<"importVFont"> {
|
||||
return { type: "importVFont", ...payload };
|
||||
},
|
||||
setFontLinks(dataId: string, links: NonMeshCommand<"setFontLinks">["links"]): NonMeshCommand<"setFontLinks"> {
|
||||
return { type: "setFontLinks", dataId, links: { ...links } };
|
||||
},
|
||||
setVolumeProperties(dataId: string, sourcePath: string, displayDensity: number, interpolation: "NEAREST" | "LINEAR", stepSize: number): NonMeshCommand<"setVolumeProperties"> {
|
||||
return { type: "setVolumeProperties", dataId, sourcePath, displayDensity, interpolation, stepSize };
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { SculptBrush, SculptMeshAttributesIR, SculptStrokeIR } from "./sculpt";
|
||||
|
||||
export const PAINT_BUDGET = {
|
||||
maxSamples: 100_000,
|
||||
maxWeightEntries: 1_000_000,
|
||||
@@ -96,6 +98,45 @@ export interface PaintBrushSpatialQueryIR {
|
||||
|
||||
export interface PaintColorPatchIR { indices: number[]; colors: number[] }
|
||||
|
||||
export function createSculptStroke(
|
||||
meshId: string,
|
||||
brush: SculptBrush,
|
||||
radius: number,
|
||||
strength: number,
|
||||
pressure: number,
|
||||
symmetry: [boolean, boolean, boolean],
|
||||
mirrorObjectSpace: boolean,
|
||||
): SculptStrokeIR {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
meshId,
|
||||
brush,
|
||||
samples: [{ position: [0, 0, 0], normal: [0, 0, 1], radius, strength, pressure, time: 0 }],
|
||||
symmetry,
|
||||
mirrorObjectSpace,
|
||||
};
|
||||
}
|
||||
|
||||
export function createSculptMeshAttributes(
|
||||
meshId: string,
|
||||
vertexCount: number,
|
||||
faceCount: number,
|
||||
maskValue: number,
|
||||
faceSet: number,
|
||||
revision: number,
|
||||
): SculptMeshAttributesIR {
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
meshId,
|
||||
vertexCount,
|
||||
faceCount,
|
||||
mask: Array.from({ length: vertexCount }, () => maskValue),
|
||||
faceSets: Array.from({ length: faceCount }, () => faceSet),
|
||||
activeFaceSet: faceSet,
|
||||
revision,
|
||||
};
|
||||
}
|
||||
|
||||
function parseBrushWeights(value: unknown, path = "brushWeights"): PaintBrushWeightIR[] {
|
||||
if (!Array.isArray(value) || value.length > PAINT_BUDGET.maxWeightEntries) fail(path, "exceeds the brush patch budget", true);
|
||||
if (value.length === 0) fail(path, "must contain at least one brush hit");
|
||||
|
||||
Reference in New Issue
Block a user