chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,7 @@ import { parseCompositorGraph, type CompositorGraphIR } from "./compositor";
|
||||
import { parseSequencerTimeline, type SequencerTimelineIR } from "./sequencer";
|
||||
import { parseTrackingMaskProject, type TrackingMaskProjectIR } from "./tracking-mask";
|
||||
import { normalizeProjectAssetPath } from "./asset-path";
|
||||
import { parseEditorWorkflow, type EditorWorkflowIR } from "./editor-workflow";
|
||||
import { parseEditorWorkflow, type EditorWorkflowIR, type GraphInterpolation } from "./editor-workflow";
|
||||
import { parseScriptSourceInventory, type ScriptSourceInventoryIR } from "./scripting-platform";
|
||||
import { parsePhysicsSimulationManifest, type PhysicsSimulationManifestIR } from "./physics-simulation";
|
||||
import { GEOMETRY_NODE_GRAPH_BUDGET, parseGeometryNodeGraph, type GeometryNodeGraphIR } from "./geometry-nodes";
|
||||
@@ -31,6 +31,8 @@ export type SceneNodeType =
|
||||
export interface SceneTransformIR {
|
||||
translation: [number, number, number];
|
||||
rotationEuler: [number, number, number];
|
||||
rotationQuaternion?: [number, number, number, number];
|
||||
rotationAxisAngle?: [number, number, number, number];
|
||||
scale: [number, number, number];
|
||||
rotationMode: number;
|
||||
}
|
||||
@@ -38,6 +40,8 @@ export interface SceneTransformIR {
|
||||
export interface SceneNodeIR {
|
||||
id: string;
|
||||
name: string;
|
||||
/** Native Library ID name within this Main; null is local, absent is legacy. */
|
||||
sourceLibrary?: string | null;
|
||||
type: SceneNodeType;
|
||||
parentId: string | null;
|
||||
dataId: string | null;
|
||||
@@ -107,6 +111,11 @@ export interface DriverIR {
|
||||
}
|
||||
|
||||
export interface ModifierIR {
|
||||
editableParameters?: Array<{ key: string; kind: "boolean" | "number" | "enum" | "enum_set" | "object" | "texture" | "cachefile" | "curve_mapping" | "string" | "profile"; value: unknown;
|
||||
suggestionsOnly?: boolean; maxLength?: number; unit?: 'ANGLE';
|
||||
label?: string; description?: string; editable?: boolean; nullable?: boolean; visible?: boolean; enabled?: boolean;
|
||||
integer: boolean; length: number; minimum?: number; maximum?: number;
|
||||
items?: Array<{ id: string; label: string }> }>;
|
||||
uuid: string;
|
||||
type: string;
|
||||
name: string;
|
||||
@@ -183,6 +192,7 @@ export interface UVLayerIR {
|
||||
export interface MeshSummaryIR {
|
||||
id: string;
|
||||
name: string;
|
||||
sourceLibrary?: string | null;
|
||||
vertexCount: number;
|
||||
edgeCount: number;
|
||||
faceCount: number;
|
||||
@@ -223,6 +233,8 @@ export interface MaterialNodeIR {
|
||||
id: string;
|
||||
type: "RGB" | "VALUE" | "MATH" | "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}>;
|
||||
imageId?: string | null;
|
||||
defaultValue?: number[];
|
||||
properties?: { operation?: "ADD" | "SUBTRACT" | "MULTIPLY" | "DIVIDE" | "MINIMUM" | "MAXIMUM" };
|
||||
@@ -238,6 +250,7 @@ export interface MaterialLinkIR {
|
||||
export interface MaterialIR {
|
||||
id: string;
|
||||
name: string;
|
||||
sourceLibrary?: string | null;
|
||||
baseColor: [number, number, number, number];
|
||||
roughness: number;
|
||||
metallic: number;
|
||||
@@ -261,6 +274,8 @@ export interface MaterialIR {
|
||||
export interface CameraIR {
|
||||
id: string;
|
||||
name: string;
|
||||
/** Saved Library source; linked camera data remains read-only. */
|
||||
sourceLibrary?: string | null;
|
||||
projection: "PERSPECTIVE" | "ORTHOGRAPHIC" | "PANORAMIC" | "CUSTOM";
|
||||
lensMm: number;
|
||||
sensorWidthMm: number;
|
||||
@@ -323,7 +338,7 @@ export interface WorldIR {
|
||||
color: [number, number, number];
|
||||
exposure: number;
|
||||
environmentImageId?: string | null;
|
||||
environmentStrength?: number;
|
||||
environmentStrength?: number | null;
|
||||
environmentRotation?: number;
|
||||
ambientOcclusionDistance?: number;
|
||||
ambientOcclusionEnergy?: number;
|
||||
@@ -540,6 +555,8 @@ export interface NonMeshVolumePropertiesIR {
|
||||
export interface NonMeshDataIR {
|
||||
id: string;
|
||||
name: string;
|
||||
/** Saved Library ID of legacy Curve data, independently of its Object owner. */
|
||||
sourceLibrary?: string | null;
|
||||
type: NonMeshDataType;
|
||||
geometryStatus: "summary-only" | "available" | "binary" | "blocked";
|
||||
pointCount: number;
|
||||
@@ -577,18 +594,23 @@ export interface NonMeshDataIR {
|
||||
|
||||
export interface AnimationIR {
|
||||
id: string;
|
||||
sourceActionId?: string;
|
||||
name: string;
|
||||
targetId: string;
|
||||
frameStart: number;
|
||||
frameEnd: number;
|
||||
slots?: Array<{ identifier: string; idtype: number; handle: number; flags: number }>;
|
||||
strips?: Array<{ name: string; type: "KEYFRAME"; frameStart: number; frameEnd: number; dataIndex: number }>;
|
||||
channels: Array<{ path: string; editable?: boolean; enabled?: boolean; selected?: boolean; group?: string; expanded?: boolean; keyframeSource?: "BEZIER" | "SAMPLED"; modifiers?: Array<{ name: string; type: string; typeCode: number; flags: number; expanded: boolean; influence: number; frameStart: number; frameEnd: number; blendIn: number; blendOut: number }>; interpolation?: "CONSTANT" | "LINEAR" | "BEZIER" | "MIXED"; keyframes: Array<{ frame: number; value: number[]; handleLeft?: [number, number]; handleRight?: [number, number]; interpolation?: "CONSTANT" | "LINEAR" | "BEZIER" }> }>;
|
||||
channels: Array<{ path: string; editable?: boolean; enabled?: boolean; selected?: boolean; group?: string; expanded?: boolean; keyframeSource?: "BEZIER" | "SAMPLED"; modifiers?: Array<{ name: string; type: string; typeCode: number; flags: number; expanded: boolean; influence: number; frameStart: number; frameEnd: number; blendIn: number; blendOut: number }>; interpolation?: GraphInterpolation | "MIXED"; keyframes: Array<{ frame: number; selected?: boolean; value: number[]; handleLeft?: [number, number]; handleRight?: [number, number]; interpolation?: GraphInterpolation }> }>;
|
||||
}
|
||||
|
||||
export interface CollectionIR {
|
||||
id: string;
|
||||
name: string;
|
||||
sourceLibrary?: string | null;
|
||||
isMaster?: boolean;
|
||||
viewportVisible?: boolean;
|
||||
renderVisible?: boolean;
|
||||
objectIds: string[];
|
||||
childCollectionIds: string[];
|
||||
}
|
||||
@@ -651,7 +673,7 @@ export interface SceneIR {
|
||||
saveOutput: boolean;
|
||||
alphaPremultiplied: boolean;
|
||||
};
|
||||
viewLayers?: Array<{ name: string; useRender: boolean; useFreestyle: boolean; samples: number; passMask?: number; cryptomatteMask?: number; cryptomatteLevels?: number; alphaThreshold?: number; aovs?: Array<{ name: string; type: number }>; lightgroups?: string[] }>;
|
||||
viewLayers?: Array<{ name: string; useRender: boolean; useFreestyle: boolean; samples: number; passMask?: number; cryptomatteMask?: number; cryptomatteLevels?: number; alphaThreshold?: number; aovs?: Array<{ name: string; type: 0 | 1 }>; lightgroups?: string[] }>;
|
||||
borderSettings?: { enabled: boolean; minX: number; maxX: number; minY: number; maxY: number };
|
||||
simplifySettings?: { subsurfViewport: number; subsurfRender: number; particlesViewport: number; particlesRender: number; volumes: number };
|
||||
imageFormatSettings?: { fileType: string; colorMode: number; depth: number; quality: number; compression: number; exrCodec: number };
|
||||
@@ -870,6 +892,8 @@ export interface SceneSnapshotIR {
|
||||
collections: CollectionIR[];
|
||||
scenes: SceneIR[];
|
||||
activeObjectId: string | null;
|
||||
/** Native ViewLayer Base selection, including when no editor workspace is available. */
|
||||
selectedObjectIds?: string[];
|
||||
frame: { current: number; start: number; end: number };
|
||||
}
|
||||
|
||||
@@ -1221,6 +1245,9 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
if (!isRecord(node)) throw new Error(`SceneIR.nodes[${index}] must be an object`);
|
||||
requireString(node.id, `nodes[${index}].id`);
|
||||
requireString(node.name, `nodes[${index}].name`);
|
||||
if (node.sourceLibrary !== undefined && node.sourceLibrary !== null) {
|
||||
requireString(node.sourceLibrary, `nodes[${index}].sourceLibrary`);
|
||||
}
|
||||
requireBoolean(node.visible, `nodes[${index}].visible`);
|
||||
requireBoolean(node.selectable, `nodes[${index}].selectable`);
|
||||
if (!Array.isArray(node.localMatrix) || node.localMatrix.length !== 16) {
|
||||
@@ -1231,6 +1258,8 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
if (!isRecord(node.transform)) throw new Error(`SceneIR.nodes[${index}].transform must be an object`);
|
||||
requireTuple(node.transform.translation, 3, `nodes[${index}].transform.translation`);
|
||||
requireTuple(node.transform.rotationEuler, 3, `nodes[${index}].transform.rotationEuler`);
|
||||
if (node.transform.rotationQuaternion !== undefined) requireTuple(node.transform.rotationQuaternion, 4, `nodes[${index}].transform.rotationQuaternion`);
|
||||
if (node.transform.rotationAxisAngle !== undefined) requireTuple(node.transform.rotationAxisAngle, 4, `nodes[${index}].transform.rotationAxisAngle`);
|
||||
requireTuple(node.transform.scale, 3, `nodes[${index}].transform.scale`);
|
||||
requireNumber(node.transform.rotationMode, `nodes[${index}].transform.rotationMode`);
|
||||
if (node.drivers !== undefined) {
|
||||
@@ -1458,6 +1487,9 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
if (nonMeshIds.has(dataId)) throw new Error(`SceneIR.nonMeshData contains duplicate id: ${dataId}`);
|
||||
nonMeshIds.add(dataId);
|
||||
requireString(data.name, `nonMeshData[${index}].name`);
|
||||
if (data.sourceLibrary !== undefined && data.sourceLibrary !== null) {
|
||||
requireString(data.sourceLibrary, "nonMeshData[" + index + "].sourceLibrary");
|
||||
}
|
||||
if (!["CURVE", "SURFACE", "FONT", "METABALL", "POINT_CLOUD", "CURVES", "VOLUME", "HAIR"].includes(data.type as string)) throw new Error(`SceneIR.nonMeshData[${index}].type is invalid`);
|
||||
if (!["summary-only", "available", "binary", "blocked"].includes(data.geometryStatus as string)) throw new Error(`SceneIR.nonMeshData[${index}].geometryStatus is invalid`);
|
||||
const pointCount = requireNumber(data.pointCount, `nonMeshData[${index}].pointCount`);
|
||||
@@ -1701,7 +1733,7 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
requireNumber(world.exposure, `worlds[${index}].exposure`);
|
||||
if (world.lightgroup !== undefined && world.lightgroup !== null) requireString(world.lightgroup, `worlds[${index}].lightgroup`);
|
||||
if (world.environmentImageId !== undefined && world.environmentImageId !== null) requireString(world.environmentImageId, `worlds[${index}].environmentImageId`);
|
||||
if (world.environmentStrength !== undefined) requireNumber(world.environmentStrength, `worlds[${index}].environmentStrength`);
|
||||
if (world.environmentStrength !== undefined && world.environmentStrength !== null) requireNumber(world.environmentStrength, `worlds[${index}].environmentStrength`);
|
||||
if (world.environmentRotation !== undefined) requireNumber(world.environmentRotation, `worlds[${index}].environmentRotation`);
|
||||
for (const field of ["ambientOcclusionDistance", "ambientOcclusionEnergy", "sunThreshold", "sunAngle", "sunShadowMaximumResolution", "sunShadowJitterOverblur", "sunShadowFilterRadius", "probeResolution"] as const) if (world[field] !== undefined) requireNumber(world[field], `worlds[${index}].${field}`);
|
||||
for (const field of ["useSunShadow", "useSunShadowJitter"] as const) if (world[field] !== undefined) requireBoolean(world[field], `worlds[${index}].${field}`);
|
||||
@@ -1769,7 +1801,7 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
for (const field of ["passMask", "cryptomatteMask", "cryptomatteLevels", "alphaThreshold"] as const) if (layer[field] !== undefined) requireNumber(layer[field], `scenes[${index}].viewLayers[${layerIndex}].${field}`);
|
||||
if (layer.aovs !== undefined) {
|
||||
if (!Array.isArray(layer.aovs)) throw new Error(`SceneIR.scenes[${index}].viewLayers[${layerIndex}].aovs must be an array`);
|
||||
for (const [aovIndex, aov] of layer.aovs.entries()) { if (!isRecord(aov)) throw new Error(`SceneIR.scenes[${index}].viewLayers[${layerIndex}].aovs[${aovIndex}] must be an object`); requireString(aov.name, `scenes[${index}].viewLayers[${layerIndex}].aovs[${aovIndex}].name`); requireNumber(aov.type, `scenes[${index}].viewLayers[${layerIndex}].aovs[${aovIndex}].type`); }
|
||||
for (const [aovIndex, aov] of layer.aovs.entries()) { if (!isRecord(aov)) throw new Error(`SceneIR.scenes[${index}].viewLayers[${layerIndex}].aovs[${aovIndex}] must be an object`); requireString(aov.name, `scenes[${index}].viewLayers[${layerIndex}].aovs[${aovIndex}].name`); if (aov.type !== 0 && aov.type !== 1) throw new Error(`SceneIR.scenes[${index}].viewLayers[${layerIndex}].aovs[${aovIndex}].type must be Value (0) or Color (1)`); }
|
||||
}
|
||||
if (layer.lightgroups !== undefined) { if (!Array.isArray(layer.lightgroups)) throw new Error(`SceneIR.scenes[${index}].viewLayers[${layerIndex}].lightgroups must be an array`); for (const [groupIndex, group] of layer.lightgroups.entries()) requireString(group, `scenes[${index}].viewLayers[${layerIndex}].lightgroups[${groupIndex}]`); }
|
||||
}
|
||||
@@ -1845,6 +1877,7 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
for (const [index, animation] of (value.animations as unknown[]).entries()) {
|
||||
if (!isRecord(animation)) throw new Error(`SceneIR.animations[${index}] must be an object`);
|
||||
requireString(animation.id, `animations[${index}].id`);
|
||||
if (animation.sourceActionId !== undefined) requireString(animation.sourceActionId, `animations[${index}].sourceActionId`);
|
||||
requireString(animation.name, `animations[${index}].name`);
|
||||
requireString(animation.targetId, `animations[${index}].targetId`);
|
||||
requireNumber(animation.frameStart, `animations[${index}].frameStart`);
|
||||
@@ -1892,6 +1925,7 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
for (const [keyframeIndex, keyframe] of keyframes.entries()) {
|
||||
if (!isRecord(keyframe)) throw new Error(`SceneIR.animations[${index}].channels[${channelIndex}].keyframes[${keyframeIndex}] must be an object`);
|
||||
requireNumber(keyframe.frame, `animations[${index}].channels[${channelIndex}].keyframes[${keyframeIndex}].frame`);
|
||||
if (keyframe.selected !== undefined && typeof keyframe.selected !== 'boolean') throw new Error(`animations[${index}].channels[${channelIndex}].keyframes[${keyframeIndex}].selected must be boolean`);
|
||||
const values = requireArray(keyframe.value, `animations[${index}].channels[${channelIndex}].keyframes[${keyframeIndex}].value`);
|
||||
if (values.some((item) => typeof item !== "number" || !Number.isFinite(item))) {
|
||||
throw new Error(`SceneIR.animations[${index}].channels[${channelIndex}].keyframes[${keyframeIndex}].value must contain finite numbers`);
|
||||
@@ -1960,6 +1994,17 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
};
|
||||
byId.forEach((_dependencies, id) => visit(id));
|
||||
}
|
||||
if (value.selectedObjectIds !== undefined) {
|
||||
const selection = requireArray(value.selectedObjectIds, "selectedObjectIds");
|
||||
const nodeIds = new Set(nodes.map((node) => isRecord(node) ? node.id : undefined));
|
||||
const selected = new Set<string>();
|
||||
for (const id of selection) {
|
||||
if (typeof id !== "string" || !nodeIds.has(id) || selected.has(id)) {
|
||||
throw new Error("SceneIR.selectedObjectIds must contain unique existing node IDs");
|
||||
}
|
||||
selected.add(id);
|
||||
}
|
||||
}
|
||||
if (value.editorWorkflowStatus !== undefined && !["AVAILABLE", "BLOCKED"].includes(value.editorWorkflowStatus as string)) {
|
||||
throw new Error("SceneIR.editorWorkflowStatus is invalid");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user