Advance M8-M11 parity workflows
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
GEOMETRY_NODE_FIELD_BUDGET,
|
||||
parseGeometryNodeDomainCardinality,
|
||||
type GeometryNodeDomainCardinalityIR,
|
||||
} from "./geometry-nodes";
|
||||
|
||||
export interface DepsgraphMeshEvaluationIR {
|
||||
objectId: string;
|
||||
meshId: string;
|
||||
@@ -9,6 +15,23 @@ export interface DepsgraphMeshEvaluationIR {
|
||||
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 {
|
||||
@@ -25,7 +48,8 @@ export interface DepsgraphModifierEvaluationIR {
|
||||
status: "EVALUATED" | "DISABLED" | "BLOCKED";
|
||||
reason?: string;
|
||||
error?: string;
|
||||
errorCode?: "UNSUPPORTED_MODIFIER_TYPE" | "MODIFIER_TARGET_MISSING" | "BLENDER_MODIFIER_ERROR";
|
||||
errorCode?: "UNSUPPORTED_MODIFIER_TYPE" | "MODIFIER_TARGET_MISSING" | "BLENDER_MODIFIER_ERROR" |
|
||||
"GEOMETRY_NODES_SIMULATION_UNAVAILABLE" | "GEOMETRY_NODES_EVALUATOR_UNSUPPORTED";
|
||||
suggestion?: string;
|
||||
targetObjectIds?: string[];
|
||||
dependsOn?: string[];
|
||||
@@ -125,6 +149,61 @@ function countField(record: Record<string, unknown>, field: string): number {
|
||||
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`);
|
||||
@@ -194,7 +273,13 @@ function modifierReports(record: Record<string, unknown>): DepsgraphModifierEval
|
||||
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)) {
|
||||
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");
|
||||
@@ -258,6 +343,48 @@ export function parseDepsgraphEvaluation(value: unknown): DepsgraphEvaluationIR
|
||||
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"),
|
||||
@@ -269,6 +396,9 @@ export function parseDepsgraphEvaluation(value: unknown): DepsgraphEvaluationIR
|
||||
worldMatrix,
|
||||
positions,
|
||||
indices,
|
||||
...(parsedDomainCardinality === undefined ? {} : { domainCardinality: parsedDomainCardinality }),
|
||||
...(parsedFieldMaterializations === undefined ? {} : { fieldMaterializations: parsedFieldMaterializations }),
|
||||
...(attributes === undefined ? {} : { attributes }),
|
||||
};
|
||||
});
|
||||
const objectCount = countField(value, "objectCount");
|
||||
|
||||
Reference in New Issue
Block a user