Files
workinf_Blender_Wasm/tools/web/check-asset-preview-inventory.mjs
mes123456 5a11045ca5
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 Blender 5.2 web parity through M12-03D
2026-08-17 17:30:27 -04:00

99 lines
6.6 KiB
JavaScript

import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
import ts from "../../web/node_modules/typescript/lib/typescript.js";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const inventory = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-02A/asset-preview-inventory.json"), "utf8"));
const sha256 = (file) => crypto.createHash("sha256").update(fs.readFileSync(path.join(root, file))).digest("hex");
assert.equal(inventory.task, "M12-02A");
assert.equal(inventory.enablingTask, true);
assert.equal(inventory.parityStateChange, false);
assert.equal(inventory.blenderVersion, "5.2.0");
assert.equal(inventory.nextTask, "M12-02B");
for (const item of [...inventory.sources, ...inventory.fixtures]) assert.equal(sha256(item.path), item.sha256, item.path);
const source = (role) => fs.readFileSync(path.join(root, inventory.sources.find((item) => item.role === role).path), "utf8");
const dna = source("PREVIEW_DNA");
for (const token of ["unsigned int w[2]", "unsigned int h[2]", "short flag[2]", "short changed_timestamp[2]", "unsigned int *rect[2]", "PreviewImageRuntime *runtime"]) {
assert.ok(dna.includes(token), `PreviewImage DNA token is missing: ${token}`);
}
const slots = source("PREVIEW_SLOT_ENUM");
assert.match(slots, /ICON_SIZE_ICON = 0/);
assert.match(slots, /ICON_SIZE_PREVIEW = 1/);
assert.equal(inventory.storage.slotCount, 2);
const implementation = source("PREVIEW_IMPLEMENTATION");
assert.match(implementation, /PreviewImage assumes pre-multiplied alpha/);
assert.match(implementation, /writer->write_uint32_array\(prv_copy\.w\[0\] \* prv_copy\.h\[0\]/);
assert.match(implementation, /writer->write_uint32_array\(prv_copy\.w\[1\] \* prv_copy\.h\[1\]/);
assert.match(implementation, /prv->flag\[i\] &= ~PRV_RENDERING/);
assert.equal(inventory.storage.colorSpace, null);
assert.ok(!inventory.storage.fields.some((field) => /color/i.test(field.name)));
const rnaSource = source("PREVIEW_RNA");
assert.match(rnaSource, /Image pixels, as bytes \(always 32-bit RGBA\)/);
assert.match(rnaSource, /length\[0\] = prv_img->w\[size\] \* prv_img->h\[size\] \* 4/);
assert.match(rnaSource, /values\[i\] = data\[i\] \* \(1\.0f \/ 255\.0f\)/);
assert.doesNotMatch(rnaSource.slice(rnaSource.indexOf("static void rna_def_image_preview"), rnaSource.indexOf("static void rna_def_image_user")), /color.?space/i);
const parseInterface = (file, name) => {
const absolute = path.join(root, file);
const parsed = ts.createSourceFile(absolute, fs.readFileSync(absolute, "utf8"), ts.ScriptTarget.Latest, true);
const declaration = parsed.statements.find((statement) => ts.isInterfaceDeclaration(statement) && statement.name.text === name);
assert.ok(declaration, `${name} is missing`);
return declaration.members.filter(ts.isPropertySignature).map((member) => member.name.getText(parsed));
};
assert.deepEqual(parseInterface("web/protocol/asset-library-io.ts", "AssetPreviewIR"), inventory.currentWeb.v1Fields);
assert.deepEqual(parseInterface("web/protocol/asset-catalog-v2.ts", "AssetCatalogV2PreviewIR"), inventory.currentWeb.v2Fields);
const blender = process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender");
assert.match(execFileSync(blender, ["--version"], { encoding: "utf8" }), /^Blender 5\.2\.0 LTS/m);
const rnaProbe = [
"import bpy,json",
"props=[{'identifier':p.identifier,'type':p.type,'readOnly':p.is_readonly,'arrayLength':p.array_length} for p in bpy.types.ImagePreview.bl_rna.properties if p.identifier != 'rna_type']",
"idp=bpy.types.ID.bl_rna.properties['preview']",
"print('M12_PREVIEW_RNA='+json.dumps({'idProperty':{'identifier':idp.identifier,'type':idp.type,'readOnly':idp.is_readonly,'nullWhenAbsent':True},'imagePreviewProperties':props},separators=(',',':')))",
].join(";");
const rnaOutput = execFileSync(blender, ["--factory-startup", "--background", "--python-expr", rnaProbe], { encoding: "utf8" });
const rnaLine = rnaOutput.split(/\r?\n/).find((line) => line.startsWith("M12_PREVIEW_RNA="));
assert.ok(rnaLine);
assert.deepEqual(JSON.parse(rnaLine.slice("M12_PREVIEW_RNA=".length)), {
idProperty: inventory.rna.idProperty,
imagePreviewProperties: inventory.rna.imagePreviewProperties,
});
const noPreviewScript = [
"import bpy,json",
"items=[]",
"groups=(bpy.data.objects,bpy.data.materials,bpy.data.worlds)",
"[items.append({'type':item.bl_rna.identifier,'preview':None if item.preview is None else list(item.preview.image_size)}) for group in groups for item in group if item.asset_data]",
"print('M12_NO_PREVIEW='+json.dumps(items,separators=(',',':')))",
].join(";");
const noPreviewOutput = execFileSync(blender, ["--factory-startup", "--background", path.join(root, inventory.fixtures[1].path), "--python-expr", noPreviewScript], { encoding: "utf8" });
const noPreviewLine = noPreviewOutput.split(/\r?\n/).find((line) => line.startsWith("M12_NO_PREVIEW="));
assert.ok(noPreviewLine);
const noPreview = JSON.parse(noPreviewLine.slice("M12_NO_PREVIEW=".length));
assert.deepEqual(noPreview.map((item) => item.type), inventory.runtimeStates.noPreview.assetTypes);
assert.deepEqual(noPreview.map((item) => item.preview), inventory.runtimeStates.noPreview.previewValues);
const png = path.join(root, inventory.fixtures[0].path);
const loadedScript = [
"import bpy,json,bpy.utils.previews",
"collection=bpy.utils.previews.new()",
`preview=collection.load('m12-preview',${JSON.stringify(png)},'IMAGE',True)`,
"result={'sourceSize':[8,8],'imageSize':list(preview.image_size),'iconSize':list(preview.icon_size),'imagePackedPixelCount':len(preview.image_pixels),'imageFloatComponentCount':len(preview.image_pixels_float),'iconPackedPixelCount':len(preview.icon_pixels),'imageCustom':preview.is_image_custom,'iconCustom':preview.is_icon_custom}",
"print('M12_LOADED_PREVIEW='+json.dumps(result,separators=(',',':')))",
"bpy.utils.previews.remove(collection)",
].join(";");
const loadedOutput = execFileSync(blender, ["--factory-startup", "--background", "--python-expr", loadedScript], { encoding: "utf8" });
const loadedLine = loadedOutput.split(/\r?\n/).find((line) => line.startsWith("M12_LOADED_PREVIEW="));
assert.ok(loadedLine);
assert.deepEqual(JSON.parse(loadedLine.slice("M12_LOADED_PREVIEW=".length)), inventory.runtimeStates.loadedPreview);
process.stdout.write(`asset-preview-inventory-ok slots=${inventory.storage.slotCount} rna=${inventory.rna.imagePreviewProperties.length} absent=${noPreview.length} loaded=${inventory.runtimeStates.loadedPreview.imageSize.join("x")} next=${inventory.nextTask}\n`);