Files
mes123456 0fe8d2bb56
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

467 lines
22 KiB
TypeScript

import {
GEOMETRY_NODE_FIELD_BUDGET,
parseGeometryNodeDomainCardinality,
type GeometryNodeDomainCardinalityIR,
} from "./geometry-nodes";
export interface DepsgraphMeshEvaluationIR {
objectId: string;
meshId: string;
sourceMeshId: string;
vertexCount: number;
triangleCount: number;
modifierCount: number;
modifiers: DepsgraphModifierEvaluationIR[];
worldMatrix: number[];
positions: number[];
indices: number[];
domainCardinality?: GeometryNodeDomainCardinalityIR;
fieldMaterializations?: Array<{
schemaVersion: 1;
fieldId: string;
domain: "POINT";
dataType: "FLOAT";
elementCount: number;
scalarValueCount: number;
materializedByteLength: number;
transport: "JSON" | "BINARY_REQUIRED";
errorCode?: "GN_FIELD_JSON_BUDGET_EXCEEDED";
}>;
attributes?: Record<string, {
domain: "POINT";
dataType: "FLOAT";
values: 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" |
"GEOMETRY_NODES_SIMULATION_UNAVAILABLE" | "GEOMETRY_NODES_EVALUATOR_UNSUPPORTED";
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 domainCardinality(value: unknown): GeometryNodeDomainCardinalityIR {
return parseGeometryNodeDomainCardinality(value, "meshes[].domainCardinality");
}
function fieldMaterializations(
value: unknown,
cardinality: GeometryNodeDomainCardinalityIR,
): NonNullable<DepsgraphMeshEvaluationIR["fieldMaterializations"]> {
if (!Array.isArray(value) || value.length > 64) throw new Error("Depsgraph mesh fieldMaterializations are invalid");
const fieldIds = new Set<string>();
return value.map((candidate) => {
if (!isRecord(candidate) ||
Object.keys(candidate).some((key) => ![
"schemaVersion", "fieldId", "domain", "dataType", "elementCount",
"scalarValueCount", "materializedByteLength", "transport", "errorCode",
].includes(key)) ||
candidate.schemaVersion !== 1 || candidate.domain !== "POINT" || candidate.dataType !== "FLOAT" ||
(candidate.transport !== "JSON" && candidate.transport !== "BINARY_REQUIRED")) {
throw new Error("Depsgraph mesh field materialization is invalid");
}
const fieldId = stringField(candidate, "fieldId");
if (new TextEncoder().encode(fieldId).byteLength > GEOMETRY_NODE_FIELD_BUDGET.maxIdentifierBytes ||
fieldIds.has(fieldId)) {
throw new Error("Depsgraph mesh field materialization identity is invalid");
}
fieldIds.add(fieldId);
const elementCount = countField(candidate, "elementCount");
const scalarValueCount = countField(candidate, "scalarValueCount");
const materializedByteLength = countField(candidate, "materializedByteLength");
if (elementCount !== cardinality.POINT || scalarValueCount !== elementCount ||
materializedByteLength !== scalarValueCount * Float32Array.BYTES_PER_ELEMENT) {
throw new Error("Depsgraph mesh field materialization counts are inconsistent");
}
const errorCode = candidate.errorCode;
if ((candidate.transport === "JSON" && errorCode !== undefined) ||
(candidate.transport === "JSON" && scalarValueCount > GEOMETRY_NODE_FIELD_BUDGET.maxJsonScalarValuesPerField) ||
(candidate.transport === "BINARY_REQUIRED" &&
(errorCode !== "GN_FIELD_JSON_BUDGET_EXCEEDED" ||
scalarValueCount <= GEOMETRY_NODE_FIELD_BUDGET.maxJsonScalarValuesPerField))) {
throw new Error("Depsgraph mesh field materialization error is inconsistent");
}
return {
schemaVersion: 1 as const,
fieldId,
domain: "POINT" as const,
dataType: "FLOAT" as const,
elementCount,
scalarValueCount,
materializedByteLength,
transport: candidate.transport,
...(errorCode === undefined ? {} : { errorCode: errorCode as "GN_FIELD_JSON_BUDGET_EXCEEDED" }),
};
});
}
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",
"GEOMETRY_NODES_SIMULATION_UNAVAILABLE",
"GEOMETRY_NODES_EVALUATOR_UNSUPPORTED",
].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`);
}
const attributesValue = candidate.attributes;
const domainCardinalityValue = candidate.domainCardinality;
const parsedDomainCardinality = domainCardinalityValue === undefined ? undefined : domainCardinality(domainCardinalityValue);
const fieldMaterializationsValue = candidate.fieldMaterializations;
if (parsedDomainCardinality !== undefined && parsedDomainCardinality.POINT !== vertexCount) {
throw new Error("Depsgraph mesh POINT cardinality is inconsistent");
}
const parsedFieldMaterializations = fieldMaterializationsValue === undefined ? undefined : (() => {
if (parsedDomainCardinality === undefined) {
throw new Error("Depsgraph mesh field materializations require domain cardinality");
}
return fieldMaterializations(fieldMaterializationsValue, parsedDomainCardinality);
})();
let attributes: DepsgraphMeshEvaluationIR["attributes"];
if (attributesValue !== undefined) {
if (!isRecord(attributesValue) || Object.keys(attributesValue).length > 64) {
throw new Error("Depsgraph mesh attributes are invalid");
}
attributes = {};
for (const [name, attributeValue] of Object.entries(attributesValue)) {
if (name.length === 0 || name.length > 64 || !isRecord(attributeValue) ||
Object.keys(attributeValue).some((key) => !["domain", "dataType", "values"].includes(key)) ||
attributeValue.domain !== "POINT" || attributeValue.dataType !== "FLOAT") {
throw new Error(`Depsgraph mesh attribute ${name} is invalid`);
}
const values = numberArray(attributeValue, "values");
if (values.length !== vertexCount || parsedDomainCardinality === undefined ||
parsedDomainCardinality.POINT !== values.length) {
throw new Error(`Depsgraph mesh attribute ${name} length is inconsistent`);
}
const receipt = parsedFieldMaterializations?.find((entry) => entry.fieldId === `attribute:${name}`);
if (receipt === undefined || receipt.transport !== "JSON" ||
receipt.materializedByteLength !== values.length * Float32Array.BYTES_PER_ELEMENT) {
throw new Error(`Depsgraph mesh attribute ${name} has no matching field materialization`);
}
attributes[name] = { domain: "POINT", dataType: "FLOAT", values };
}
}
if (parsedFieldMaterializations?.some((entry) => entry.transport === "JSON" &&
(attributes === undefined || !Object.hasOwn(attributes, entry.fieldId.replace(/^attribute:/, ""))))) {
throw new Error("Depsgraph mesh JSON field materialization has no matching attribute payload");
}
return {
objectId: stringField(candidate, "objectId"),
meshId: stringField(candidate, "meshId"),
sourceMeshId: stringField(candidate, "sourceMeshId"),
vertexCount,
triangleCount,
modifierCount,
modifiers,
worldMatrix,
positions,
indices,
...(parsedDomainCardinality === undefined ? {} : { domainCardinality: parsedDomainCardinality }),
...(parsedFieldMaterializations === undefined ? {} : { fieldMaterializations: parsedFieldMaterializations }),
...(attributes === undefined ? {} : { attributes }),
};
});
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 }),
};
}