feat(web): switch to implementation-first capability wave
This commit is contained in:
26
web/protocol/animation-render-commands.ts
Normal file
26
web/protocol/animation-render-commands.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { FCurveInterpolation } from "./web-engine";
|
||||
import type { WebEngineEditCommand } from "./web-engine";
|
||||
|
||||
type AnimationCommand<T extends WebEngineEditCommand["type"]> = Extract<WebEngineEditCommand, { type: T }>;
|
||||
|
||||
/** Typed constructors for animation, constraint and render property edits. */
|
||||
export const animationRenderCommands = Object.freeze({
|
||||
insertKeyframe(objectId: string, frame: number, property: AnimationCommand<"insertObjectKeyframe">["property"], interpolation: FCurveInterpolation = "BEZIER"): AnimationCommand<"insertObjectKeyframe"> {
|
||||
return { type: "insertObjectKeyframe", objectId, frame, property, interpolation };
|
||||
},
|
||||
deleteKeyframe(objectId: string, frame: number, property?: AnimationCommand<"deleteObjectKeyframe">["property"]): AnimationCommand<"deleteObjectKeyframe"> {
|
||||
return { type: "deleteObjectKeyframe", objectId, frame, ...(property ? { property } : {}) };
|
||||
},
|
||||
setConstraint(objectId: string, constraintName: string, enabled: boolean, influence: number): AnimationCommand<"setConstraint"> {
|
||||
return { type: "setConstraint", objectId, constraintName, enabled, influence };
|
||||
},
|
||||
setLightProperties(dataId: string, properties: AnimationCommand<"setLightProperties">["properties"]): AnimationCommand<"setLightProperties"> {
|
||||
return { type: "setLightProperties", dataId, properties };
|
||||
},
|
||||
setCameraProperties(dataId: string, properties: AnimationCommand<"setCameraProperties">["properties"]): AnimationCommand<"setCameraProperties"> {
|
||||
return { type: "setCameraProperties", dataId, properties };
|
||||
},
|
||||
setWorldProperties(dataId: string, properties: AnimationCommand<"setWorldProperties">["properties"]): AnimationCommand<"setWorldProperties"> {
|
||||
return { type: "setWorldProperties", dataId, properties };
|
||||
},
|
||||
} satisfies Record<string, (...args: never[]) => WebEngineEditCommand>);
|
||||
43
web/protocol/core-scene-commands.ts
Normal file
43
web/protocol/core-scene-commands.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { WebEngineEditCommand } from "./web-engine";
|
||||
|
||||
type CoreCommand<T extends WebEngineEditCommand["type"]> = Extract<WebEngineEditCommand, { type: T }>;
|
||||
type Vector3 = [number, number, number];
|
||||
|
||||
const vector3 = (value: readonly number[]): Vector3 => [value[0] ?? 0, value[1] ?? 0, value[2] ?? 0];
|
||||
|
||||
/** Typed constructors used by the workbench for the source-backed scene API. */
|
||||
export const coreSceneCommands = Object.freeze({
|
||||
createCube(location: readonly number[] = [0, 0, 0], name?: string): CoreCommand<"createPrimitive"> {
|
||||
return { type: "createPrimitive", primitive: "CUBE", location: vector3(location), ...(name ? { name } : {}) };
|
||||
},
|
||||
duplicate(objectId: string, offset: readonly number[] = [0.25, 0.25, 0], linked = false): CoreCommand<"duplicateObject"> {
|
||||
return { type: "duplicateObject", objectId, offset: vector3(offset), ...(linked ? { linked: true } : {}) };
|
||||
},
|
||||
delete(objectId: string): CoreCommand<"deleteObject"> {
|
||||
return { type: "deleteObject", objectId };
|
||||
},
|
||||
visibility(objectId: string, visible: boolean): CoreCommand<"setObjectVisibility"> {
|
||||
return { type: "setObjectVisibility", objectId, visible };
|
||||
},
|
||||
transform(objectId: string, translation: readonly number[], rotationEuler: readonly number[], scale: readonly number[]): CoreCommand<"setObjectTransform"> {
|
||||
return { type: "setObjectTransform", objectId, translation: vector3(translation), rotationEuler: vector3(rotationEuler), scale: vector3(scale) };
|
||||
},
|
||||
parent(objectId: string, parentId: string | null, keepTransform = true): CoreCommand<"setParent"> {
|
||||
return { type: "setParent", objectId, parentId, keepTransform };
|
||||
},
|
||||
createCollection(name: string, parentCollectionId?: string | null): CoreCommand<"createCollection"> {
|
||||
return { type: "createCollection", name, ...(parentCollectionId === undefined ? {} : { parentCollectionId }) };
|
||||
},
|
||||
moveToCollection(objectId: string, collectionId: string): CoreCommand<"moveObjectToCollection"> {
|
||||
return { type: "moveObjectToCollection", objectId, collectionId };
|
||||
},
|
||||
rename(id: string, name: string): CoreCommand<"renameId"> {
|
||||
return { type: "renameId", id, name };
|
||||
},
|
||||
applyTransform(objectId: string): CoreCommand<"applyObjectTransform"> {
|
||||
return { type: "applyObjectTransform", objectId };
|
||||
},
|
||||
setOrigin(objectId: string, mode: CoreCommand<"setObjectOrigin">["mode"] = "GEOMETRY"): CoreCommand<"setObjectOrigin"> {
|
||||
return { type: "setObjectOrigin", objectId, mode };
|
||||
},
|
||||
} satisfies Record<string, (...args: never[]) => WebEngineEditCommand>);
|
||||
13
web/protocol/io-media-commands.ts
Normal file
13
web/protocol/io-media-commands.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { WebEngineEditCommand } from "./web-engine";
|
||||
|
||||
type IOCommand<T extends WebEngineEditCommand["type"]> = Extract<WebEngineEditCommand, { type: T }>;
|
||||
|
||||
/** Typed constructors for media import and persistence-facing edit commands. */
|
||||
export const ioMediaCommands = Object.freeze({
|
||||
importImage(name: string, mimeType: IOCommand<"importImage">["mimeType"], width: number, height: number, base64: string): IOCommand<"importImage"> {
|
||||
return { type: "importImage", name, mimeType, width, height, base64 };
|
||||
},
|
||||
setMaterialImageNode(materialId: string, imageId: string, usage: IOCommand<"setMaterialImageNode">["usage"], uvMap?: string): IOCommand<"setMaterialImageNode"> {
|
||||
return { type: "setMaterialImageNode", materialId, imageId, usage, ...(uvMap ? { uvMap } : {}) };
|
||||
},
|
||||
} satisfies Record<string, (...args: never[]) => WebEngineEditCommand>);
|
||||
35
web/protocol/mesh-authoring-commands.ts
Normal file
35
web/protocol/mesh-authoring-commands.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { MeshEditOperation, MeshElementMode } from "./web-engine";
|
||||
import type { SimplifyProfile } from "./simplify";
|
||||
import type { WebEngineEditCommand } from "./web-engine";
|
||||
|
||||
type MeshCommand<T extends WebEngineEditCommand["type"]> = Extract<WebEngineEditCommand, { type: T }>;
|
||||
type Vector3 = [number, number, number];
|
||||
|
||||
const vector3 = (value: readonly number[]): Vector3 => [value[0] ?? 0, value[1] ?? 0, value[2] ?? 0];
|
||||
|
||||
/** Typed constructors for mesh authoring commands sent to WebEngine Main. */
|
||||
export const meshAuthoringCommands = Object.freeze({
|
||||
edit(meshId: string, operation: MeshEditOperation, selectionMode: MeshElementMode, elementIndices: readonly number[], options: { offset?: readonly number[]; amount?: number; segments?: number } = {}): MeshCommand<"meshEdit"> {
|
||||
return {
|
||||
type: "meshEdit", meshId, operation, selectionMode, elementIndices: [...elementIndices],
|
||||
...(options.offset ? { offset: vector3(options.offset) } : {}),
|
||||
...(options.amount === undefined ? {} : { amount: options.amount }),
|
||||
...(options.segments === undefined ? {} : { segments: options.segments }),
|
||||
};
|
||||
},
|
||||
decimate(meshId: string, profile: SimplifyProfile): MeshCommand<"decimateMesh"> {
|
||||
return { type: "decimateMesh", meshId, profile };
|
||||
},
|
||||
previewDecimate(meshId: string, profile: SimplifyProfile): MeshCommand<"previewDecimateMesh"> {
|
||||
return { type: "previewDecimateMesh", meshId, profile };
|
||||
},
|
||||
translateVertices(meshId: string, vertexIndices: readonly number[], offset: readonly number[]): MeshCommand<"translateMeshVertices"> {
|
||||
return { type: "translateMeshVertices", meshId, vertexIndices: [...vertexIndices], offset: vector3(offset) };
|
||||
},
|
||||
createUVMap(meshId: string, name: string): MeshCommand<"createUVMap"> {
|
||||
return { type: "createUVMap", meshId, name };
|
||||
},
|
||||
activeUVMap(meshId: string, name: string): MeshCommand<"setActiveUVMap"> {
|
||||
return { type: "setActiveUVMap", meshId, name };
|
||||
},
|
||||
} satisfies Record<string, (...args: never[]) => WebEngineEditCommand>);
|
||||
28
web/protocol/nonmesh-geometry-commands.ts
Normal file
28
web/protocol/nonmesh-geometry-commands.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
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. */
|
||||
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 };
|
||||
},
|
||||
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 }) };
|
||||
},
|
||||
setFontBody(dataId: string, body: string): NonMeshCommand<"setFontBody"> {
|
||||
return { type: "setFontBody", dataId, body };
|
||||
},
|
||||
setVolumeProperties(dataId: string, sourcePath: string, displayDensity: number, interpolation: "NEAREST" | "LINEAR", stepSize: number): NonMeshCommand<"setVolumeProperties"> {
|
||||
return { type: "setVolumeProperties", dataId, sourcePath, displayDensity, interpolation, stepSize };
|
||||
},
|
||||
createGreasePencilLayer(dataId: string, name: string): NonMeshCommand<"createGreasePencilLayer"> {
|
||||
return { type: "createGreasePencilLayer", dataId, name };
|
||||
},
|
||||
removeGreasePencilLayer(dataId: string, layerId: string): NonMeshCommand<"removeGreasePencilLayer"> {
|
||||
return { type: "removeGreasePencilLayer", dataId, layerId };
|
||||
},
|
||||
clearGreasePencilFrame(dataId: string, layerId: string, frame: number, baseRevision?: number): NonMeshCommand<"setGreasePencilStrokes"> {
|
||||
return { type: "setGreasePencilStrokes", dataId, layerId, frame, ...(baseRevision === undefined ? {} : { baseRevision }), strokes: [] };
|
||||
},
|
||||
} satisfies Record<string, (...args: never[]) => WebEngineEditCommand>);
|
||||
14
web/protocol/render-compositor-commands.ts
Normal file
14
web/protocol/render-compositor-commands.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { RenderCapabilityRequest } from "./render-capabilities";
|
||||
|
||||
/** Canonical capability requests shared by export and future compositor UI. */
|
||||
export const renderCompositorCommands = Object.freeze({
|
||||
boundedShaderGraph(): RenderCapabilityRequest {
|
||||
return { kind: "ARBITRARY_SHADER", nodeTypes: ["PRINCIPLED", "MATERIAL_OUTPUT"] };
|
||||
},
|
||||
webGPUBackend(): RenderCapabilityRequest {
|
||||
return { kind: "WEBGPU_BACKEND" };
|
||||
},
|
||||
postProcess(passes: Extract<RenderCapabilityRequest, { kind: "POSTPROCESS" }>["passes"]): RenderCapabilityRequest {
|
||||
return { kind: "POSTPROCESS", passes: [...passes] };
|
||||
},
|
||||
} as const);
|
||||
28
web/protocol/shader-graph-commands.ts
Normal file
28
web/protocol/shader-graph-commands.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { ShaderGraphIR, ShaderSocketIR } from "./shader-graph";
|
||||
|
||||
const input = (id: string, name: string, dataType: ShaderSocketIR["dataType"], defaultValue?: ShaderSocketIR["defaultValue"]): ShaderSocketIR => ({ id, name, direction: "INPUT", dataType, ...(defaultValue === undefined ? {} : { defaultValue }) });
|
||||
const output = (id: string, name: string, dataType: ShaderSocketIR["dataType"]): ShaderSocketIR => ({ id, name, direction: "OUTPUT", dataType });
|
||||
|
||||
/** Build a valid bounded graph for the source-backed Principled writer. */
|
||||
export const shaderGraphCommands = Object.freeze({
|
||||
setPrincipledGraph(materialId: string, baseColor: readonly number[], roughness: number, metallic: number): ShaderGraphIR {
|
||||
const principled = "shader:principled";
|
||||
const materialOutput = "shader:material-output";
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
id: `shader-graph:${materialId}`,
|
||||
materialId,
|
||||
nodes: [
|
||||
{ id: principled, type: "PRINCIPLED", name: "Principled BSDF", sockets: [
|
||||
input("Base Color", "Base Color", "COLOR", [...baseColor].slice(0, 4)),
|
||||
input("Metallic", "Metallic", "VALUE", metallic),
|
||||
input("Roughness", "Roughness", "VALUE", roughness),
|
||||
output("BSDF", "BSDF", "SHADER"),
|
||||
] },
|
||||
{ id: materialOutput, type: "MATERIAL_OUTPUT", name: "Material Output", sockets: [input("Surface", "Surface", "SHADER")] },
|
||||
],
|
||||
links: [{ fromNodeId: principled, fromSocketId: "BSDF", toNodeId: materialOutput, toSocketId: "Surface" }],
|
||||
outputNodeId: materialOutput,
|
||||
};
|
||||
},
|
||||
} as const);
|
||||
98
web/protocol/web-engine-capabilities.ts
Normal file
98
web/protocol/web-engine-capabilities.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import type { WebEngineEditCommand } from "./web-engine";
|
||||
|
||||
export const WEB_ENGINE_CAPABILITY_REGISTRY_SCHEMA = 1 as const;
|
||||
export type WebEngineCommandType = WebEngineEditCommand["type"];
|
||||
export type WebEngineCommandRoute = "MAIN_AUTHORITATIVE" | "MAIN_GUARDED" | "LOCAL_HISTORY" | "PREVIEW";
|
||||
|
||||
export interface WebEngineCommandCapability {
|
||||
readonly family: string;
|
||||
readonly route: WebEngineCommandRoute;
|
||||
readonly implementation: "SOURCE_BACKED" | "SOURCE_GUARDED";
|
||||
}
|
||||
|
||||
/**
|
||||
* The protocol is intentionally the type authority; this record makes the
|
||||
* runtime dispatch contract explicit without duplicating command payloads.
|
||||
*/
|
||||
export const WEB_ENGINE_COMMAND_CAPABILITIES = {
|
||||
setFrame: { family: "TIMELINE", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setObjectVisibility: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setModifierEnabled: { family: "MODIFIER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setModifierVisibility: { family: "MODIFIER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
decimateMesh: { family: "MESH", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
previewDecimateMesh: { family: "MESH", route: "PREVIEW", implementation: "SOURCE_BACKED" },
|
||||
createPrimitive: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
duplicateObject: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
deleteObject: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setObjectTransform: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
translateMeshVertices: { family: "MESH", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
meshEdit: { family: "MESH", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
createUVMap: { family: "MESH", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setActiveUVMap: { family: "MESH", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
unwrapUV: { family: "MESH", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
addMaterialSlot: { family: "MATERIAL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
removeMaterialSlot: { family: "MATERIAL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
assignMaterialFaces: { family: "MATERIAL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setMaterialPrincipled: { family: "MATERIAL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
importImage: { family: "IO", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setMaterialImageNode: { family: "MATERIAL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
insertObjectKeyframe: { family: "ANIMATION", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
deleteObjectKeyframe: { family: "ANIMATION", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setFCurveInterpolation: { family: "ANIMATION", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setActiveAction: { family: "ANIMATION", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setConstraint: { family: "ANIMATION", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setParent: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
createCollection: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
moveObjectToCollection: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
renameId: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
joinObjects: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
separateMeshFaces: { family: "MESH", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
applyObjectTransform: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setObjectOrigin: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
createCurve: { family: "GEOMETRY", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
deleteNonMeshData: { family: "GEOMETRY", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setCurveControlPoints: { family: "GEOMETRY", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setCurveHandle: { family: "GEOMETRY", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setCurveTopology: { family: "GEOMETRY", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setCurveSplines: { family: "GEOMETRY", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setSurfaceTopology: { family: "GEOMETRY", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setFontBody: { family: "FONT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setFontProperties: { family: "FONT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setFontAdvanced: { family: "FONT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
importVFont: { family: "FONT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setFontLinks: { family: "FONT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setVolumeProperties: { family: "VOLUME", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
createGreasePencilLayer: { family: "GREASE_PENCIL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
removeGreasePencilLayer: { family: "GREASE_PENCIL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
moveGreasePencilLayer: { family: "GREASE_PENCIL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
moveGreasePencilFrame: { family: "GREASE_PENCIL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
insertGreasePencilFrame: { family: "GREASE_PENCIL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
removeGreasePencilFrame: { family: "GREASE_PENCIL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setGreasePencilStrokes: { family: "GREASE_PENCIL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setVertexColors: { family: "MESH", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setVertexWeights: { family: "MESH", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setLightProperties: { family: "RENDER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setCameraProperties: { family: "RENDER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setWorldProperties: { family: "RENDER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setMetaballElements: { family: "GEOMETRY", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
sculptStroke: { family: "SCULPT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setSculptMeshAttributes: { family: "SCULPT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setGeometryNodeGraph: { family: "GEOMETRY_NODES", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setPhysicsSimulation: { family: "SIMULATION", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setShaderGraph: { family: "SHADER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setNLAStack: { family: "ANIMATION", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
moveNLAStrip: { family: "ANIMATION", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
undo: { family: "HISTORY", route: "LOCAL_HISTORY", implementation: "SOURCE_BACKED" },
|
||||
redo: { family: "HISTORY", route: "LOCAL_HISTORY", implementation: "SOURCE_BACKED" },
|
||||
} satisfies Record<WebEngineCommandType, WebEngineCommandCapability>;
|
||||
|
||||
export function getWebEngineCommandCapability(type: unknown): WebEngineCommandCapability | null {
|
||||
if (typeof type !== "string") return null;
|
||||
return Object.prototype.hasOwnProperty.call(WEB_ENGINE_COMMAND_CAPABILITIES, type)
|
||||
? WEB_ENGINE_COMMAND_CAPABILITIES[type as WebEngineCommandType]
|
||||
: null;
|
||||
}
|
||||
|
||||
export function isKnownWebEngineCommand(type: unknown): type is WebEngineCommandType {
|
||||
return getWebEngineCommandCapability(type) !== null;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import type { LODManifest } from "./simplify";
|
||||
import type { DepsgraphEvaluationIR } from "./depsgraph";
|
||||
import type { SculptMeshAttributesIR, SculptStrokeIR } from "./sculpt";
|
||||
import type { GeometryNodeGraphIR } from "./geometry-nodes";
|
||||
import type { PhysicsSimulationManifestIR } from "./physics-simulation";
|
||||
import type { ShaderGraphIR } from "./shader-graph";
|
||||
import type { ShaderCompileReport } from "./shader-compiler";
|
||||
import type { NlaMoveStripCommand, NlaTrackIR } from "./nla";
|
||||
@@ -141,6 +142,7 @@ export type WebEngineEditCommand =
|
||||
| { type: "sculptStroke"; stroke: SculptStrokeIR }
|
||||
| { type: "setSculptMeshAttributes"; attributes: SculptMeshAttributesIR }
|
||||
| { type: "setGeometryNodeGraph"; meshId: string; graph: GeometryNodeGraphIR }
|
||||
| { type: "setPhysicsSimulation"; manifest: PhysicsSimulationManifestIR }
|
||||
| { type: "setShaderGraph"; materialId: string; graph: ShaderGraphIR }
|
||||
| { type: "setNLAStack"; objectId: string; tracks: NlaTrackIR[]; baseRevision?: number }
|
||||
| NlaMoveStripCommand
|
||||
|
||||
16
web/protocol/workbench-command-groups.ts
Normal file
16
web/protocol/workbench-command-groups.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { getWebEngineCommandCapability, type WebEngineCommandType } from "./web-engine-capabilities";
|
||||
|
||||
export const WORKBENCH_COMMAND_GROUPS = Object.freeze({
|
||||
OBJECT: ["createPrimitive", "duplicateObject", "deleteObject", "setObjectTransform", "setParent", "createCollection", "moveObjectToCollection", "renameId", "applyObjectTransform", "setObjectOrigin"],
|
||||
MESH: ["meshEdit", "translateMeshVertices", "createUVMap", "setActiveUVMap", "unwrapUV", "setVertexColors", "setVertexWeights"],
|
||||
ANIMATION: ["setFrame", "insertObjectKeyframe", "deleteObjectKeyframe", "setFCurveInterpolation", "setConstraint", "setNLAStack"],
|
||||
RENDER: ["setCameraProperties", "setLightProperties", "setWorldProperties"],
|
||||
IO: ["importImage", "importVFont", "setMaterialImageNode"],
|
||||
} as const satisfies Record<string, readonly WebEngineCommandType[]>);
|
||||
|
||||
export function isWorkbenchCommandAvailable(type: unknown): type is WebEngineCommandType {
|
||||
return getWebEngineCommandCapability(type)?.route !== "MAIN_GUARDED";
|
||||
}
|
||||
export function workbenchCommandCount(): number {
|
||||
return Object.values(WORKBENCH_COMMAND_GROUPS).reduce((count, group) => count + group.filter(isWorkbenchCommandAvailable).length, 0);
|
||||
}
|
||||
Reference in New Issue
Block a user