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 { return typeof value === "object" && value !== null && !Array.isArray(value); } function numberField(record: Record, 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, 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, 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, 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, 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): 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): 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 }), }; }