chore: checkpoint web Blender feature-gap work, docs and tests
- update native web_engine sources and rebuilt wasm artifacts - add task cards, status notes and tooling scripts - add web e2e specs, tutorial UI sources and tutorial assets - sync golden task manifests and audit records Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,18 @@ export const coreSceneCommands = Object.freeze({
|
||||
createCollection(name: string, parentCollectionId?: string | null): CoreCommand<"createCollection"> {
|
||||
return { type: "createCollection", name, ...(parentCollectionId === undefined ? {} : { parentCollectionId }) };
|
||||
},
|
||||
moveCollection(collectionId: string, parentCollectionId: string | null = null, anchorCollectionId: string | null = null, after = false): CoreCommand<"moveCollection"> {
|
||||
return { type: "moveCollection", collectionId, parentCollectionId, ...(anchorCollectionId ? { anchorCollectionId } : {}), ...(after ? { after: true } : {}) };
|
||||
},
|
||||
deleteCollection(collectionId: string, hierarchy = false): CoreCommand<"deleteCollection"> {
|
||||
return { type: "deleteCollection", collectionId, ...(hierarchy ? { hierarchy: true } : {}) };
|
||||
},
|
||||
setScene(sceneId: string): CoreCommand<"setScene"> {
|
||||
return { type: "setScene", sceneId };
|
||||
},
|
||||
setActiveCollection(collectionId: string): CoreCommand<"setActiveCollection"> {
|
||||
return { type: "setActiveCollection", collectionId };
|
||||
},
|
||||
moveToCollection(objectId: string, collectionId: string): CoreCommand<"moveObjectToCollection"> {
|
||||
return { type: "moveObjectToCollection", objectId, collectionId };
|
||||
},
|
||||
|
||||
@@ -10,6 +10,7 @@ export interface NativeWorkflowContext {
|
||||
selectedObjects?: string[];
|
||||
mode?: EditorMode;
|
||||
meshSelection?: { domain: 'VERT' | 'EDGE' | 'FACE'; indices: number[] };
|
||||
nonMeshSelection?: { controlPoints: number[] };
|
||||
nodeTreeType?: 'ShaderNodeTree' | 'GeometryNodeTree' | 'CompositorNodeTree';
|
||||
nodeTreeName?: string;
|
||||
areaType?: 'VIEW_3D' | 'IMAGE_EDITOR' | 'NODE_EDITOR' | 'GRAPH_EDITOR' | 'DOPESHEET_EDITOR' | 'SEQUENCE_EDITOR' | 'TEXT_EDITOR' | 'NLA_EDITOR' | 'CLIP_EDITOR' | 'CONSOLE' | 'INFO' | 'OUTLINER' | 'PROPERTIES' | 'FILE_BROWSER' | 'SPREADSHEET' | 'PREFERENCES';
|
||||
|
||||
@@ -9,7 +9,7 @@ export const MAX_GPU_TEXTURE_DIMENSION = 16_384;
|
||||
export const MAX_GPU_TEXTURE_ASSETS = 256;
|
||||
export const MAX_UDIM_TILES = 64;
|
||||
|
||||
export type GPUTextureUsage = "BASE_COLOR" | "NORMAL" | "EMISSIVE" | "DATA" | "ENVIRONMENT" | "UDIM_TILE";
|
||||
export type GPUTextureUsage = "BASE_COLOR" | "NORMAL" | "METALLIC" | "ROUGHNESS" | "EMISSIVE" | "ALPHA" | "BUMP" | "DATA" | "ENVIRONMENT" | "UDIM_TILE";
|
||||
export type GPUTextureColorSpace = "SRGB" | "NON_COLOR" | "LINEAR";
|
||||
|
||||
export interface GPUTextureAssetRequest {
|
||||
@@ -109,7 +109,10 @@ function materialUsages(materials: MaterialIR[]): Map<string, Set<GPUTextureUsag
|
||||
};
|
||||
for (const material of materials) {
|
||||
if (material.normalImageId) add(material.normalImageId, "NORMAL");
|
||||
for (const imageId of material.imageIds ?? []) add(imageId, imageId === material.normalImageId ? "NORMAL" : "BASE_COLOR");
|
||||
for (const binding of material.textureBindings ?? []) add(binding.imageId, binding.usage === "EMISSION" ? "EMISSIVE" : binding.usage);
|
||||
for (const imageId of material.imageIds ?? []) {
|
||||
if (!material.textureBindings?.some((binding) => binding.imageId === imageId)) add(imageId, imageId === material.normalImageId ? "NORMAL" : "BASE_COLOR");
|
||||
}
|
||||
for (const node of material.nodes ?? []) {
|
||||
if (node.type === "IMAGE_TEXTURE" && node.imageId) add(node.imageId, node.imageId === material.normalImageId ? "NORMAL" : "BASE_COLOR");
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ export interface SceneNodeIR {
|
||||
type: SceneNodeType;
|
||||
parentId: string | null;
|
||||
dataId: string | null;
|
||||
/** Collection instantiated by this empty object, if any. */
|
||||
instanceCollectionId?: string;
|
||||
visible: boolean;
|
||||
selectable: boolean;
|
||||
localMatrix: number[];
|
||||
@@ -231,13 +233,25 @@ export interface MeshSummaryIR {
|
||||
|
||||
export interface MaterialNodeIR {
|
||||
id: string;
|
||||
type: "RGB" | "VALUE" | "MATH" | "PRINCIPLED" | "IMAGE_TEXTURE" | "NORMAL_MAP" | "OUTPUT" | "UNSUPPORTED";
|
||||
type: "RGB" | "VALUE" | "MATH" | "MIX" | "MAPPING" | "TEX_COORD" | "UV_MAP" | "BUMP" | "SEPARATE_COLOR" | "OBJECT_INFO" | "PRINCIPLED" | "IMAGE_TEXTURE" | "NORMAL_MAP" | "OUTPUT" | "UNSUPPORTED";
|
||||
name: string;
|
||||
location?: [number, number];
|
||||
sockets?: Array<{id:string;name:string;direction:'INPUT'|'OUTPUT';dataType:'VALUE'|'VECTOR'|'COLOR'|'BOOLEAN'|'SHADER'|'UNSUPPORTED';value?:number|boolean|number[];linked:boolean;integer?:boolean}>;
|
||||
sockets?: Array<{id:string;name:string;direction:'INPUT'|'OUTPUT';dataType:'VALUE'|'VECTOR'|'COLOR'|'BOOLEAN'|'SHADER'|'UNSUPPORTED';value?:number|boolean|number[];linked:boolean;integer?:boolean;available?:boolean;hidden?:boolean}>;
|
||||
imageId?: string | null;
|
||||
defaultValue?: number[];
|
||||
properties?: { operation?: "ADD" | "SUBTRACT" | "MULTIPLY" | "DIVIDE" | "MINIMUM" | "MAXIMUM" };
|
||||
properties?: {
|
||||
operation?: "ADD" | "SUBTRACT" | "MULTIPLY" | "DIVIDE" | "MINIMUM" | "MAXIMUM";
|
||||
dataType?: "FLOAT" | "VECTOR" | "RGBA";
|
||||
blendType?: string;
|
||||
vectorType?: "POINT" | "TEXTURE" | "VECTOR" | "NORMAL";
|
||||
space?: "TANGENT" | "OBJECT" | "WORLD";
|
||||
invert?: boolean;
|
||||
projection?: number;
|
||||
interpolation?: number;
|
||||
extension?: number;
|
||||
uvMap?: string;
|
||||
mode?: "RGB" | "HSV" | "HSL";
|
||||
};
|
||||
}
|
||||
|
||||
export interface MaterialLinkIR {
|
||||
@@ -247,6 +261,15 @@ export interface MaterialLinkIR {
|
||||
toSocket: string;
|
||||
}
|
||||
|
||||
export interface MaterialTextureBindingIR {
|
||||
imageId: string;
|
||||
usage: "BASE_COLOR" | "METALLIC" | "ROUGHNESS" | "EMISSION" | "ALPHA" | "NORMAL" | "BUMP";
|
||||
socket?: string;
|
||||
evaluated?: boolean;
|
||||
strength?: number;
|
||||
uvMap?: string;
|
||||
}
|
||||
|
||||
export interface MaterialIR {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -264,6 +287,7 @@ export interface MaterialIR {
|
||||
emissionStrength?: number;
|
||||
normalImageId?: string | null;
|
||||
imageIds?: string[];
|
||||
textureBindings?: MaterialTextureBindingIR[];
|
||||
warnings?: string[];
|
||||
/** SHA-256 of the serialized bounded shader graph when a node tree was read. */
|
||||
shaderGraphHash?: string;
|
||||
@@ -1248,6 +1272,12 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
if (node.sourceLibrary !== undefined && node.sourceLibrary !== null) {
|
||||
requireString(node.sourceLibrary, `nodes[${index}].sourceLibrary`);
|
||||
}
|
||||
if (node.instanceCollectionId !== undefined) {
|
||||
const collectionId = requireString(node.instanceCollectionId, `nodes[${index}].instanceCollectionId`);
|
||||
if (!collectionId.startsWith("collection:")) {
|
||||
throw new Error(`SceneIR.nodes[${index}].instanceCollectionId is invalid`);
|
||||
}
|
||||
}
|
||||
requireBoolean(node.visible, `nodes[${index}].visible`);
|
||||
requireBoolean(node.selectable, `nodes[${index}].selectable`);
|
||||
if (!Array.isArray(node.localMatrix) || node.localMatrix.length !== 16) {
|
||||
@@ -1685,6 +1715,18 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
if (material.imageIds !== undefined && (!Array.isArray(material.imageIds) || material.imageIds.some((item) => typeof item !== "string"))) {
|
||||
throw new Error(`SceneIR.materials[${index}].imageIds must contain strings`);
|
||||
}
|
||||
if (material.textureBindings !== undefined) {
|
||||
if (!Array.isArray(material.textureBindings)) throw new Error(`SceneIR.materials[${index}].textureBindings must be an array`);
|
||||
for (const [bindingIndex, binding] of material.textureBindings.entries()) {
|
||||
if (!isRecord(binding)) throw new Error(`SceneIR.materials[${index}].textureBindings[${bindingIndex}] must be an object`);
|
||||
requireString(binding.imageId, `materials[${index}].textureBindings[${bindingIndex}].imageId`);
|
||||
if (!["BASE_COLOR", "METALLIC", "ROUGHNESS", "EMISSION", "ALPHA", "NORMAL", "BUMP"].includes(binding.usage as string)) throw new Error(`SceneIR.materials[${index}].textureBindings[${bindingIndex}].usage is invalid`);
|
||||
if (binding.socket !== undefined) requireString(binding.socket, `materials[${index}].textureBindings[${bindingIndex}].socket`);
|
||||
if (binding.uvMap !== undefined) requireString(binding.uvMap, `materials[${index}].textureBindings[${bindingIndex}].uvMap`);
|
||||
if (binding.evaluated !== undefined && typeof binding.evaluated !== "boolean") throw new Error(`SceneIR.materials[${index}].textureBindings[${bindingIndex}].evaluated is invalid`);
|
||||
if (binding.strength !== undefined) requireNumber(binding.strength, `materials[${index}].textureBindings[${bindingIndex}].strength`);
|
||||
}
|
||||
}
|
||||
if (material.warnings !== undefined && (!Array.isArray(material.warnings) || material.warnings.some((item) => typeof item !== "string"))) {
|
||||
throw new Error(`SceneIR.materials[${index}].warnings must contain strings`);
|
||||
}
|
||||
@@ -1694,12 +1736,24 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
if (!isRecord(node)) throw new Error(`SceneIR.materials[${index}].nodes[${nodeIndex}] must be an object`);
|
||||
requireString(node.id, `SceneIR.materials[${index}].nodes[${nodeIndex}].id`);
|
||||
requireString(node.name, `SceneIR.materials[${index}].nodes[${nodeIndex}].name`);
|
||||
if (!["RGB", "VALUE", "MATH", "PRINCIPLED", "IMAGE_TEXTURE", "NORMAL_MAP", "OUTPUT", "UNSUPPORTED"].includes(node.type as string)) throw new Error(`SceneIR.materials[${index}].nodes[${nodeIndex}].type is invalid`);
|
||||
if (!["RGB", "VALUE", "MATH", "MIX", "MAPPING", "TEX_COORD", "UV_MAP", "BUMP", "SEPARATE_COLOR", "OBJECT_INFO", "PRINCIPLED", "IMAGE_TEXTURE", "NORMAL_MAP", "OUTPUT", "UNSUPPORTED"].includes(node.type as string)) throw new Error(`SceneIR.materials[${index}].nodes[${nodeIndex}].type is invalid`);
|
||||
if (node.imageId !== undefined && node.imageId !== null) requireString(node.imageId, `SceneIR.materials[${index}].nodes[${nodeIndex}].imageId`);
|
||||
if (node.defaultValue !== undefined && (!Array.isArray(node.defaultValue) || node.defaultValue.length === 0 || node.defaultValue.length > 4 || node.defaultValue.some((value) => typeof value !== "number" || !Number.isFinite(value)))) throw new Error(`SceneIR.materials[${index}].nodes[${nodeIndex}].defaultValue is invalid`);
|
||||
if (node.properties !== undefined) {
|
||||
if (!isRecord(node.properties) || Object.keys(node.properties).some((key) => key !== "operation") ||
|
||||
(node.properties.operation !== undefined && !["ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MINIMUM", "MAXIMUM"].includes(node.properties.operation as string))) {
|
||||
const properties = node.properties as Record<string, unknown>;
|
||||
const allowed = new Set(["operation", "dataType", "blendType", "vectorType", "space", "invert", "projection", "interpolation", "extension", "uvMap", "mode"]);
|
||||
const enumCheck = (key: string, values: readonly string[]) => properties[key] === undefined || (typeof properties[key] === "string" && values.includes(properties[key] as string));
|
||||
const numberCheck = (key: string) => properties[key] === undefined || (Number.isSafeInteger(properties[key]) && (properties[key] as number) >= 0 && (properties[key] as number) <= 32);
|
||||
if (!isRecord(node.properties) || Object.keys(properties).some((key) => !allowed.has(key)) ||
|
||||
!enumCheck("operation", ["ADD", "SUBTRACT", "MULTIPLY", "DIVIDE", "MINIMUM", "MAXIMUM"]) ||
|
||||
!enumCheck("dataType", ["FLOAT", "VECTOR", "RGBA"]) ||
|
||||
!enumCheck("vectorType", ["POINT", "TEXTURE", "VECTOR", "NORMAL"]) ||
|
||||
!enumCheck("space", ["TANGENT", "OBJECT", "WORLD"]) ||
|
||||
!enumCheck("mode", ["RGB", "HSV", "HSL"]) ||
|
||||
(properties.blendType !== undefined && (typeof properties.blendType !== "string" || properties.blendType.length > 32)) ||
|
||||
(properties.uvMap !== undefined && (typeof properties.uvMap !== "string" || properties.uvMap.length > 64)) ||
|
||||
(properties.invert !== undefined && typeof properties.invert !== "boolean") ||
|
||||
!numberCheck("projection") || !numberCheck("interpolation") || !numberCheck("extension")) {
|
||||
throw new Error(`SceneIR.materials[${index}].nodes[${nodeIndex}].properties is invalid`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,13 @@ const nodeSockets = (type: ShaderNodeType): ShaderSocketIR[] => {
|
||||
if (type === "PRINCIPLED") return [input("Base Color", "Base Color", "COLOR", [0.8, 0.8, 0.8, 1]), input("Metallic", "Metallic", "VALUE", 0), input("Roughness", "Roughness", "VALUE", 0.4), output("BSDF", "BSDF", "SHADER")];
|
||||
if (type === "MATERIAL_OUTPUT") return [input("Surface", "Surface", "SHADER")];
|
||||
if (type === "IMAGE_TEXTURE") return [input("Vector", "Vector", "VECTOR", [0, 0, 0]), output("Color", "Color", "COLOR")];
|
||||
if (type === "MAPPING") return [input("Vector", "Vector", "VECTOR", [0, 0, 0]), input("Location", "Location", "VECTOR", [0, 0, 0]), input("Rotation", "Rotation", "VECTOR", [0, 0, 0]), input("Scale", "Scale", "VECTOR", [1, 1, 1]), output("Vector", "Vector", "VECTOR")];
|
||||
if (type === "TEX_COORD") return ["Generated", "Normal", "UV", "Object", "Camera", "Window", "Reflection"].map((name) => output(name, name, "VECTOR"));
|
||||
if (type === "UV_MAP") return [output("UV", "UV", "VECTOR")];
|
||||
if (type === "BUMP") return [input("Strength", "Strength", "VALUE", 1), input("Distance", "Distance", "VALUE", 1), input("Height", "Height", "VALUE", 1), input("Normal", "Normal", "VECTOR", [0, 0, 0]), output("Normal", "Normal", "VECTOR")];
|
||||
if (type === "SEPARATE_COLOR") return [input("Color", "Color", "COLOR", [0, 0, 0, 1]), output("Red", "Red", "VALUE"), output("Green", "Green", "VALUE"), output("Blue", "Blue", "VALUE")];
|
||||
if (type === "OBJECT_INFO") return [output("Location", "Location", "VECTOR"), output("Color", "Color", "COLOR"), output("Alpha", "Alpha", "VALUE"), output("Object Index", "Object Index", "VALUE"), output("Material Index", "Material Index", "VALUE"), output("Random", "Random", "VALUE")];
|
||||
if (type === "MIX") return [input("Factor_Float", "Factor", "VALUE", 0.5), input("A_Float", "A", "VALUE", 0), input("B_Float", "B", "VALUE", 0), output("Result_Float", "Result", "VALUE")];
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
@@ -3,14 +3,16 @@ import { blockedGate, capabilityIssue, readyGate, type CapabilityGateResult } fr
|
||||
|
||||
export const SHADER_GRAPH_SCHEMA = 1 as const;
|
||||
export type ShaderSocketType = "VALUE" | "VECTOR" | "COLOR" | "BOOLEAN" | "SHADER" | "CLOSURE";
|
||||
export type ShaderNodeType = "RGB" | "VALUE" | "MIX" | "MATH" | "MAPPING" | "TEX_COORD" | "IMAGE_TEXTURE" | "NORMAL_MAP" | "BUMP" | "PRINCIPLED" | "MATERIAL_OUTPUT";
|
||||
export type ShaderNodeType = "RGB" | "VALUE" | "MIX" | "MATH" | "MAPPING" | "TEX_COORD" | "UV_MAP" | "SEPARATE_COLOR" | "OBJECT_INFO" | "IMAGE_TEXTURE" | "NORMAL_MAP" | "BUMP" | "PRINCIPLED" | "MATERIAL_OUTPUT";
|
||||
export type ShaderSocketValue = boolean | number | string | number[];
|
||||
export type ShaderNodeEdit =
|
||||
| {action:'ADD';nodeType:'RGB'|'VALUE'|'MATH'|'MIX'|'MAPPING'|'TEX_COORD'|'IMAGE_TEXTURE'|'NORMAL_MAP'|'BUMP'|'PRINCIPLED'|'OUTPUT';location:[number,number]}
|
||||
| {action:'ADD';nodeType:'RGB'|'VALUE'|'MATH'|'MIX'|'MAPPING'|'TEX_COORD'|'UV_MAP'|'SEPARATE_COLOR'|'OBJECT_INFO'|'IMAGE_TEXTURE'|'NORMAL_MAP'|'BUMP'|'PRINCIPLED'|'OUTPUT';location:[number,number]}
|
||||
| {action:'MOVE';nodeId:string;location:[number,number]}
|
||||
| {action:'RENAME';nodeId:string;name:string}
|
||||
| {action:'REMOVE';nodeId:string}
|
||||
| {action:'SET_SOCKET';nodeId:string;socketId:string;direction:'INPUT'|'OUTPUT';value:number|boolean|number[]}
|
||||
| {action:'SET_PROPERTY';nodeId:string;property:string;value:string|number|boolean}
|
||||
| {action:'SET_IMAGE';nodeId:string;imageId:string}
|
||||
| {action:'LINK'|'UNLINK';nodeId:string;socketId:string;fromNodeId:string;fromSocketId:string};
|
||||
|
||||
export interface ShaderSocketIR {
|
||||
|
||||
@@ -49,6 +49,8 @@ export const WEB_ENGINE_COMMAND_CAPABILITIES = {
|
||||
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" },
|
||||
assignMaterialSlot: { family: "MATERIAL", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
packImage: { family: "IO", 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" },
|
||||
@@ -83,6 +85,9 @@ export const WEB_ENGINE_COMMAND_CAPABILITIES = {
|
||||
createCollection: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
moveObjectToCollection: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
moveCollection: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
deleteCollection: { family: "OBJECT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setScene: { family: "SCENE", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setActiveCollection: { 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" },
|
||||
|
||||
@@ -156,11 +156,13 @@ export type WebEngineEditCommand =
|
||||
| { type: "setActiveUVMap"; meshId: string; name: string }
|
||||
| { type: "unwrapUV"; meshId: string; faceIndices: number[]; method: "PLANAR" | "CUBE" }
|
||||
| { type: "addMaterialSlot"; objectId: string; name?: string }
|
||||
| { type: "assignMaterialSlot"; objectId: string; materialId: string; slotIndex?: number }
|
||||
| { type: "removeMaterialSlot"; objectId: string; slotIndex: number }
|
||||
| { type: "assignMaterialFaces"; meshId: string; faceIndices: number[]; slotIndex: number }
|
||||
| { type: "setMaterialPrincipled"; materialId: string; baseColor: [number, number, number, number]; roughness: number; metallic: number; emissionColor?: [number, number, number, number]; alpha?: number; ior?: number; specularIORLevel?: number; transmissionWeight?: number; coatWeight?: number; coatRoughness?: number; emissionStrength?: number }
|
||||
| { type: "importImage"; name: string; mimeType: "image/png" | "image/jpeg"; width: number; height: number; base64: string }
|
||||
| { type: "setMaterialImageNode"; materialId: string; imageId: string; usage: "BASE_COLOR" | "NORMAL"; uvMap?: string }
|
||||
| { type: "packImage"; imageId: string; mimeType: "image/png" | "image/jpeg"; width: number; height: number; base64: string }
|
||||
| { type: "setMaterialImageNode"; materialId: string; imageId: string; usage: "BASE_COLOR" | "METALLIC" | "ROUGHNESS" | "EMISSION" | "ALPHA" | "NORMAL"; uvMap?: string }
|
||||
| { type: "insertObjectKeyframe"; objectId: string; frame: number; property: "LOCATION" | "ROTATION_EULER" | "SCALE"; interpolation?: FCurveInterpolation }
|
||||
| { type: "deleteObjectKeyframe"; objectId: string; frame: number; property?: "LOCATION" | "ROTATION_EULER" | "SCALE" }
|
||||
| { type: "createAction"; name: string; objectId?: string; useFakeUser?: boolean }
|
||||
@@ -180,7 +182,10 @@ export type WebEngineEditCommand =
|
||||
| { type: "setParents"; objectIds: string[]; parentId: string | null; keepTransform?: boolean }
|
||||
| { type: "createCollection"; name: string; parentCollectionId?: string | null }
|
||||
| { type: "moveObjectToCollection"; objectId: string; collectionId: string; keepExistingLinks?: boolean }
|
||||
| { type: "moveCollection"; collectionId: string; parentCollectionId?: string | null }
|
||||
| { type: "moveCollection"; collectionId: string; parentCollectionId?: string | null; anchorCollectionId?: string | null; after?: boolean }
|
||||
| { type: "deleteCollection"; collectionId: string; hierarchy?: boolean }
|
||||
| { type: "setScene"; sceneId: string }
|
||||
| { type: "setActiveCollection"; collectionId: string }
|
||||
| { type: "renameId"; id: string; name: string }
|
||||
| { type: "joinObjects"; activeObjectId: string; objectIds: string[] }
|
||||
| { type: "separateMeshFaces"; objectId: string; faceIndices: number[]; name?: string }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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"],
|
||||
OBJECT: ["createPrimitive", "duplicateObject", "deleteObject", "setObjectTransform", "setParent", "createCollection", "moveObjectToCollection", "renameId", "applyObjectTransform", "setObjectOrigin", "assignMaterialSlot"],
|
||||
MESH: ["meshEdit", "translateMeshVertices", "createUVMap", "setActiveUVMap", "unwrapUV", "setVertexColors", "setVertexWeights"],
|
||||
ANIMATION: ["setFrame", "insertObjectKeyframe", "deleteObjectKeyframe", "setFCurveInterpolation", "setConstraint", "setNLAStack", "setNLAProperty"],
|
||||
RENDER: ["setCameraProperties", "setLightProperties", "setWorldProperties", "setSceneProperties", "setSceneRenderSettings", "setSceneAudioSettings", "setSceneUnitSettings", "setSceneTimelineSettings", "setScenePipelineSettings", "setSceneBindings", "setSceneViewLayerSettings", "setSceneCollectionSettings", "setSceneBorderSettings", "setSceneSimplifySettings", "setSceneColorManagement", "setSceneImageFormat", "setSceneMotionBlur", "setSceneHairSettings", "setSceneCompositorSettings", "setScenePerformanceSettings", "setSceneSamplingSettings", "setSceneMultiviewSettings", "setSceneStampSettings", "setSceneLineSettings", "setSceneViewLayerPassSettings", "setSceneViewLayerAOVs", "setSceneViewLayerLightgroups", "setObjectLightgroup", "setWorldLightgroup"],
|
||||
IO: ["importImage", "importVFont", "setMaterialImageNode"],
|
||||
IO: ["importImage", "packImage", "importVFont", "setMaterialImageNode"],
|
||||
} as const satisfies Record<string, readonly WebEngineCommandType[]>);
|
||||
|
||||
export function isWorkbenchCommandAvailable(type: unknown): type is WebEngineCommandType {
|
||||
|
||||
Reference in New Issue
Block a user