Files
workinf_Blender_Wasm/web/protocol/depsgraph.ts
2026-08-12 04:47:48 -04:00

337 lines
16 KiB
TypeScript

export interface DepsgraphMeshEvaluationIR {
objectId: string;
meshId: string;
sourceMeshId: string;
vertexCount: number;
triangleCount: number;
modifierCount: number;
modifiers: DepsgraphModifierEvaluationIR[];
worldMatrix: number[];
positions: number[];
indices: number[];
}
export interface DepsgraphModifierEvaluationIR {
uuid: string;
index: number;
persistentUid: number;
typeCode: number;
type: string;
name: string;
showViewport: boolean;
showRender: boolean;
showEditmode: boolean;
showOnCage: boolean;
status: "EVALUATED" | "DISABLED" | "BLOCKED";
reason?: string;
error?: string;
errorCode?: "UNSUPPORTED_MODIFIER_TYPE" | "MODIFIER_TARGET_MISSING" | "BLENDER_MODIFIER_ERROR";
suggestion?: string;
targetObjectIds?: string[];
dependsOn?: string[];
}
export interface DepsgraphObjectEvaluationIR {
objectId: string;
type: "MESH" | "CURVE" | "SURFACE" | "FONT" | "METABALL" | "CURVES" | "POINT_CLOUD" | "VOLUME" | "LATTICE" | "ARMATURE" | "GREASE_PENCIL" | "EMPTY" | "OTHER";
modifierCount: number;
modifiers: DepsgraphModifierEvaluationIR[];
}
export interface DepsgraphConstraintEvaluationIR {
name: string;
typeCode: number;
influence: number;
flag: number;
targetObjectId?: string;
}
export interface DepsgraphBoneEvaluationIR {
id: string;
name: string;
poseMatrix: number[];
constraints: DepsgraphConstraintEvaluationIR[];
}
export interface DepsgraphArmatureEvaluationIR {
id: string;
objectId: string;
bones: DepsgraphBoneEvaluationIR[];
}
export interface DepsgraphNonMeshGeometryIR {
objectId: string;
sourceDataId: string;
sourceType: "CURVE" | "SURFACE" | "FONT" | "METABALL";
meshId: string;
status: "EVALUATED" | "BLOCKED";
errorCode?: "NON_MESH_DATA_BUDGET_EXCEEDED";
vertexCount: number;
edgeCount: number;
triangleCount: number;
worldMatrix: number[];
positions?: number[];
normals?: number[];
edgeVertexIndices?: number[];
indices?: number[];
uvs?: number[];
triangleMaterialIndices?: number[];
sourceElementIndices?: number[];
materialSlotIds: string[];
sourceMappingStatus: "EVALUATED_FACE" | "EVALUATED_EDGE";
}
export interface DepsgraphEvaluationIR {
engine: "BlenderDepsgraph";
status: "EVALUATED";
scene: string;
viewLayer: string;
frame: number;
objectCount: number;
meshObjectCount: number;
objects: DepsgraphObjectEvaluationIR[];
meshes: DepsgraphMeshEvaluationIR[];
nonMeshGeometries?: DepsgraphNonMeshGeometryIR[];
armatures?: DepsgraphArmatureEvaluationIR[];
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function numberField(record: Record<string, unknown>, field: string): number {
const value = record[field];
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`Depsgraph field ${field} is invalid`);
return value;
}
function stringField(record: Record<string, unknown>, field: string): string {
const value = record[field];
if (typeof value !== "string") throw new Error(`Depsgraph field ${field} is invalid`);
return value;
}
function numberArray(record: Record<string, unknown>, field: string): number[] {
const value = record[field];
if (!Array.isArray(value) || value.some((item) => typeof item !== "number" || !Number.isFinite(item))) {
throw new Error(`Depsgraph field ${field} is invalid`);
}
return value;
}
function countField(record: Record<string, unknown>, field: string): number {
const value = numberField(record, field);
if (!Number.isSafeInteger(value) || value < 0) throw new Error(`Depsgraph field ${field} is not a non-negative count`);
return value;
}
function booleanField(record: Record<string, unknown>, field: string): boolean {
const value = record[field];
if (typeof value !== "boolean") throw new Error(`Depsgraph field ${field} is invalid`);
return value;
}
function constraintReports(record: Record<string, unknown>): DepsgraphConstraintEvaluationIR[] {
const value = record.constraints;
if (!Array.isArray(value)) throw new Error("Depsgraph bone constraints are invalid");
return value.map((candidate) => {
if (!isRecord(candidate)) throw new Error("Depsgraph constraint entry is invalid");
const targetObjectId = candidate.targetObjectId;
if (targetObjectId !== undefined && typeof targetObjectId !== "string") {
throw new Error("Depsgraph constraint targetObjectId is invalid");
}
return {
name: stringField(candidate, "name"),
typeCode: countField(candidate, "typeCode"),
influence: numberField(candidate, "influence"),
flag: countField(candidate, "flag"),
...(targetObjectId === undefined ? {} : { targetObjectId }),
};
});
}
function armatureReports(value: unknown): DepsgraphArmatureEvaluationIR[] {
if (!Array.isArray(value)) throw new Error("Depsgraph armatures are invalid");
return value.map((candidate) => {
if (!isRecord(candidate)) throw new Error("Depsgraph armature entry is invalid");
const bonesValue = candidate.bones;
if (!Array.isArray(bonesValue)) throw new Error("Depsgraph armature bones are invalid");
const bones = bonesValue.map((boneValue) => {
if (!isRecord(boneValue)) throw new Error("Depsgraph bone entry is invalid");
const poseMatrix = numberArray(boneValue, "poseMatrix");
if (poseMatrix.length !== 16) throw new Error("Depsgraph bone poseMatrix must contain 16 numbers");
return {
id: stringField(boneValue, "id"),
name: stringField(boneValue, "name"),
poseMatrix,
constraints: constraintReports(boneValue),
};
});
return { id: stringField(candidate, "id"), objectId: stringField(candidate, "objectId"), bones };
});
}
function modifierReports(record: Record<string, unknown>): DepsgraphModifierEvaluationIR[] {
const value = record.modifiers;
if (!Array.isArray(value)) throw new Error(`Depsgraph field modifiers is invalid`);
return value.map((candidate) => {
if (!isRecord(candidate)) throw new Error("Depsgraph modifier entry is invalid");
const status = stringField(candidate, "status");
if (status !== "EVALUATED" && status !== "DISABLED" && status !== "BLOCKED") {
throw new Error(`Depsgraph modifier status is invalid: ${status}`);
}
const dependsOn = candidate.dependsOn;
if (dependsOn !== undefined && (!Array.isArray(dependsOn) || dependsOn.some((item) => typeof item !== "string"))) {
throw new Error("Depsgraph modifier dependsOn is invalid");
}
const targetObjectIds = candidate.targetObjectIds;
if (targetObjectIds !== undefined && (!Array.isArray(targetObjectIds) || targetObjectIds.some((item) => typeof item !== "string"))) {
throw new Error("Depsgraph modifier targetObjectIds is invalid");
}
const reason = candidate.reason;
const error = candidate.error;
const errorCode = candidate.errorCode;
const suggestion = candidate.suggestion;
if (reason !== undefined && typeof reason !== "string") throw new Error("Depsgraph modifier reason is invalid");
if (error !== undefined && typeof error !== "string") throw new Error("Depsgraph modifier error is invalid");
if (errorCode !== undefined && !["UNSUPPORTED_MODIFIER_TYPE", "MODIFIER_TARGET_MISSING", "BLENDER_MODIFIER_ERROR"].includes(errorCode as string)) {
throw new Error("Depsgraph modifier errorCode is invalid");
}
if (suggestion !== undefined && typeof suggestion !== "string") throw new Error("Depsgraph modifier suggestion is invalid");
return {
uuid: stringField(candidate, "uuid"),
index: countField(candidate, "index"),
persistentUid: countField(candidate, "persistentUid"),
typeCode: countField(candidate, "typeCode"),
type: stringField(candidate, "type"),
name: stringField(candidate, "name"),
showViewport: booleanField(candidate, "showViewport"),
showRender: booleanField(candidate, "showRender"),
showEditmode: booleanField(candidate, "showEditmode"),
showOnCage: booleanField(candidate, "showOnCage"),
status,
...(dependsOn === undefined ? {} : { dependsOn: dependsOn as string[] }),
...(targetObjectIds === undefined ? {} : { targetObjectIds: targetObjectIds as string[] }),
...(reason === undefined ? {} : { reason }),
...(error === undefined ? {} : { error }),
...(errorCode === undefined ? {} : { errorCode: errorCode as DepsgraphModifierEvaluationIR["errorCode"] }),
...(suggestion === undefined ? {} : { suggestion }),
};
});
}
export function parseDepsgraphEvaluation(value: unknown): DepsgraphEvaluationIR {
if (!isRecord(value) || value.engine !== "BlenderDepsgraph" || value.status !== "EVALUATED") {
throw new Error("Blender Depsgraph report has an invalid status");
}
if (!Array.isArray(value.meshes)) throw new Error("Blender Depsgraph report has no mesh evaluations");
if (!Array.isArray(value.objects)) throw new Error("Blender Depsgraph report has no object evaluations");
const objects = value.objects.map((candidate) => {
if (!isRecord(candidate)) throw new Error("Blender Depsgraph object entry is invalid");
const type = stringField(candidate, "type");
if (!["MESH", "CURVE", "SURFACE", "FONT", "METABALL", "CURVES", "POINT_CLOUD", "VOLUME", "LATTICE", "ARMATURE", "GREASE_PENCIL", "EMPTY", "OTHER"].includes(type)) {
throw new Error(`Blender Depsgraph object type is invalid: ${type}`);
}
const modifiers = modifierReports(candidate);
const modifierCount = countField(candidate, "modifierCount");
if (modifiers.length !== modifierCount) throw new Error("Blender Depsgraph object modifier count is inconsistent");
return {
objectId: stringField(candidate, "objectId"),
type: type as DepsgraphObjectEvaluationIR["type"],
modifierCount,
modifiers,
};
});
const meshes = value.meshes.map((candidate) => {
if (!isRecord(candidate)) throw new Error("Blender Depsgraph mesh entry is invalid");
const vertexCount = countField(candidate, "vertexCount");
const triangleCount = countField(candidate, "triangleCount");
const modifiers = modifierReports(candidate);
const modifierCount = countField(candidate, "modifierCount");
if (modifiers.length !== modifierCount) throw new Error("Blender Depsgraph mesh modifier count is inconsistent");
const worldMatrix = numberArray(candidate, "worldMatrix");
const positions = numberArray(candidate, "positions");
const indices = numberArray(candidate, "indices");
if (worldMatrix.length !== 16 || positions.length !== vertexCount * 3 || indices.length !== triangleCount * 3) {
throw new Error(`Depsgraph mesh ${stringField(candidate, "sourceMeshId")} buffer lengths are inconsistent`);
}
if (indices.some((index) => !Number.isSafeInteger(index) || index < 0 || index >= vertexCount)) {
throw new Error(`Depsgraph mesh ${stringField(candidate, "sourceMeshId")} has an invalid index`);
}
return {
objectId: stringField(candidate, "objectId"),
meshId: stringField(candidate, "meshId"),
sourceMeshId: stringField(candidate, "sourceMeshId"),
vertexCount,
triangleCount,
modifierCount,
modifiers,
worldMatrix,
positions,
indices,
};
});
const objectCount = countField(value, "objectCount");
const meshObjectCount = countField(value, "meshObjectCount");
if (objects.length !== objectCount) throw new Error("Blender Depsgraph object count is inconsistent");
if (meshes.length !== meshObjectCount) throw new Error("Blender Depsgraph mesh object count is inconsistent");
const armatures = value.armatures === undefined ? undefined : armatureReports(value.armatures);
const nonMeshGeometries = value.nonMeshGeometries === undefined ? undefined : (() => {
if (!Array.isArray(value.nonMeshGeometries)) throw new Error("Depsgraph non-mesh geometries are invalid");
return value.nonMeshGeometries.map((candidate): DepsgraphNonMeshGeometryIR => {
if (!isRecord(candidate)) throw new Error("Depsgraph non-mesh geometry entry is invalid");
const sourceType = stringField(candidate, "sourceType");
const status = stringField(candidate, "status");
if (!["CURVE", "SURFACE", "FONT", "METABALL"].includes(sourceType) || (status !== "EVALUATED" && status !== "BLOCKED")) throw new Error("Depsgraph non-mesh geometry type or status is invalid");
const vertexCount = countField(candidate, "vertexCount");
const edgeCount = countField(candidate, "edgeCount");
const triangleCount = countField(candidate, "triangleCount");
const worldMatrix = numberArray(candidate, "worldMatrix");
if (worldMatrix.length !== 16) throw new Error("Depsgraph non-mesh world matrix is invalid");
const materialSlotIds = candidate.materialSlotIds;
if (!Array.isArray(materialSlotIds) || materialSlotIds.some((item) => typeof item !== "string")) throw new Error("Depsgraph non-mesh material slots are invalid");
const common = {
objectId: stringField(candidate, "objectId"),
sourceDataId: stringField(candidate, "sourceDataId"),
sourceType: sourceType as DepsgraphNonMeshGeometryIR["sourceType"],
meshId: stringField(candidate, "meshId"),
status: status as DepsgraphNonMeshGeometryIR["status"],
vertexCount,
edgeCount,
triangleCount,
worldMatrix,
materialSlotIds: materialSlotIds as string[],
sourceMappingStatus: stringField(candidate, "sourceMappingStatus") as DepsgraphNonMeshGeometryIR["sourceMappingStatus"],
};
if (common.sourceMappingStatus !== "EVALUATED_FACE" && common.sourceMappingStatus !== "EVALUATED_EDGE") throw new Error("Depsgraph non-mesh source mapping status is invalid");
if (status === "BLOCKED") {
if (candidate.errorCode !== "NON_MESH_DATA_BUDGET_EXCEEDED") throw new Error("Depsgraph blocked non-mesh geometry has no budget error");
return { ...common, errorCode: candidate.errorCode };
}
const positions = numberArray(candidate, "positions");
const normals = numberArray(candidate, "normals");
const edgeVertexIndices = numberArray(candidate, "edgeVertexIndices");
const indices = numberArray(candidate, "indices");
const uvs = numberArray(candidate, "uvs");
const triangleMaterialIndices = numberArray(candidate, "triangleMaterialIndices");
const sourceElementIndices = numberArray(candidate, "sourceElementIndices");
if (positions.length !== vertexCount * 3 || normals.length !== vertexCount * 3 || edgeVertexIndices.length !== edgeCount * 2 || indices.length !== triangleCount * 3 || (uvs.length !== 0 && uvs.length !== triangleCount * 3 * 2) || triangleMaterialIndices.length !== triangleCount || sourceElementIndices.length !== triangleCount) throw new Error(`Depsgraph non-mesh ${common.sourceDataId} buffer lengths are inconsistent`);
if (edgeVertexIndices.some((item) => !Number.isSafeInteger(item) || item < 0 || item >= vertexCount) || indices.some((item) => !Number.isSafeInteger(item) || item < 0 || item >= vertexCount) || triangleMaterialIndices.some((item) => !Number.isSafeInteger(item) || item < 0) || sourceElementIndices.some((item) => !Number.isSafeInteger(item) || item < 0)) throw new Error(`Depsgraph non-mesh ${common.sourceDataId} indices are invalid`);
return { ...common, positions, normals, edgeVertexIndices, indices, uvs, triangleMaterialIndices, sourceElementIndices };
});
})();
return {
engine: "BlenderDepsgraph",
status: "EVALUATED",
scene: stringField(value, "scene"),
viewLayer: stringField(value, "viewLayer"),
frame: numberField(value, "frame"),
objectCount,
meshObjectCount,
objects,
meshes,
...(nonMeshGeometries === undefined ? {} : { nonMeshGeometries }),
...(armatures === undefined ? {} : { armatures }),
};
}