Files
workinf_Blender_Wasm/tools/web/generate-pbr-library-manifest.mjs
mes123456 a078648297
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
feat(web): complete PBR shader, asset library and offscreen render chain
- generate a desktop-Blender PBR asset library and seed catalog v2 into OPFS
- add React asset browser, material preview sphere and Web PBR render panel
- extend shader graph/compiler to the finite 14-node set and native properties
- add image colour-space editing, scene-camera still rendering and asset repair
- add focused E2E/unit coverage and implementation status notes

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-11 14:59:43 +08:00

122 lines
4.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Compose the catalog-v2 manifest for the generated PBR asset library.
*
* Inputs (produced by generate-pbr-library.py + export-asset-catalog-v1.py):
* <dir>/desktop-canonical.json
* <dir>/preview-manifest.json
* <dir>/pbr-library.blend
*
* Output:
* <dir>/manifest.json
*
* Usage: node tools/web/generate-pbr-library-manifest.mjs [--dir <path>]
*/
import assert from "node:assert/strict";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
import ts from "../../web/node_modules/typescript/lib/typescript.js";
const REPO = path.resolve(import.meta.dirname, "../..");
const dirIndex = process.argv.indexOf("--dir");
const DIR = dirIndex > 0 && process.argv[dirIndex + 1]
? path.resolve(process.argv[dirIndex + 1])
: path.join(REPO, "web/app/public/assets/pbr-library");
const LIBRARY_ID = "library:pbr-essentials-v1";
const LIBRARY_NAME = "PBR Essentials";
const LIBRARY_SOURCE = "libraries/pbr-library.blend";
const readJson = (name) => JSON.parse(fs.readFileSync(path.join(DIR, name), "utf8"));
const sha256 = (data) => crypto.createHash("sha256").update(data).digest("hex");
async function loadProtocol() {
const sourcePath = path.join(REPO, "web/protocol/asset-catalog-v2.ts");
const transpiled = ts.transpileModule(fs.readFileSync(sourcePath, "utf8"), {
compilerOptions: { module: ts.ModuleKind.ES2022, target: ts.ScriptTarget.ES2022 },
fileName: sourcePath,
reportDiagnostics: true,
});
assert.deepEqual(transpiled.diagnostics, []);
const temporary = path.join(DIR, ".asset-catalog-v2.generated.mjs");
fs.writeFileSync(temporary, transpiled.outputText);
try {
return await import(`${pathToFileURL(temporary).href}?v=${Date.now()}`);
} finally {
fs.rmSync(temporary, { force: true });
}
}
const desktop = readJson("desktop-canonical.json");
const previewManifest = readJson("preview-manifest.json");
const libraryBytes = fs.readFileSync(path.join(DIR, "pbr-library.blend"));
const sourceSha256 = sha256(libraryBytes);
const previews = new Map();
for (const entry of previewManifest.previews) {
const key = `${entry.idType}:${entry.name}`;
if (previews.has(key)) throw new Error(`duplicate preview ${key}`);
if (!fs.existsSync(path.join(DIR, entry.file))) throw new Error(`missing preview file ${entry.file}`);
const bytes = fs.readFileSync(path.join(DIR, entry.file));
if (sha256(bytes) !== entry.sha256) throw new Error(`preview hash mismatch ${entry.file}`);
previews.set(key, {
sha256: entry.sha256,
mimeType: "image/png",
width: entry.width,
height: entry.height,
byteLength: entry.byteLength,
});
}
const protocol = await loadProtocol();
const base = await protocol.createAssetCatalogManifestV2FromDesktop(desktop, 1);
const assets = [];
for (const asset of base.assets) {
const key = `${asset.idType}:${asset.name}`;
const preview = previews.get(key);
if (!preview) throw new Error(`asset ${key} has no preview`);
const assetLibraryIdentifier = LIBRARY_ID;
assets.push({
...asset,
assetLibraryIdentifier,
assetId: await protocol.createAssetCatalogV2StableId({
assetLibraryIdentifier,
relativeAssetIdentifier: asset.relativeAssetIdentifier,
}),
sourcePath: LIBRARY_SOURCE,
sourceSha256,
preview,
});
}
const candidate = {
schemaVersion: 2,
revision: 1,
catalogs: base.catalogs,
assets,
libraries: [{
libraryId: LIBRARY_ID,
name: LIBRARY_NAME,
sourcePath: LIBRARY_SOURCE,
sourceSha256,
dependencyIds: [],
readOnly: true,
}],
};
const manifest = await protocol.parseAssetCatalogManifestV2(candidate);
fs.writeFileSync(path.join(DIR, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
const totalBytes = assets.reduce((sum, asset) => sum + (asset.preview?.byteLength ?? 0), 0);
console.log(JSON.stringify({
manifest: "manifest.json",
catalogs: manifest.catalogs.length,
assets: manifest.assets.length,
libraries: manifest.libraries.length,
previewBytes: totalBytes,
sourceSha256,
}, null, 2));