Files
workinf_Blender_Wasm/tools/web/generate-pbr-library-manifest.mjs
mes123456 d5dd647881
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
feat(web): finish RE 5000PBR lesson video and fix non-ASCII GLB import
- Add 11:07 Chinese-narrated lesson video, VTT, poster and 13 chapter screenshots.
- Regenerate 20-render WebGL2 matrix and 15 operation captures on RTX 4060.
- Fix WASM staging for non-ASCII import filenames (猴头.glb); rebuild web_engine.
- Harden lesson video recording pipeline; fix stale lesson-link E2E selector.
- Add RE PBR derived libraries, lesson page, evidence and status report.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-12 21:49:24 +08:00

125 lines
4.3 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>]
* [--library-id <id>] [--library-name <name>] [--source <virtual path>] [--blend <file>]
*/
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 flag = (name, fallback) => {
const index = process.argv.indexOf(`--${name}`);
return index > 0 && process.argv[index + 1] ? process.argv[index + 1] : fallback;
};
const DIR = path.resolve(flag("dir", path.join(REPO, "web/app/public/assets/pbr-library")));
const LIBRARY_ID = flag("library-id", "library:pbr-essentials-v1");
const LIBRARY_NAME = flag("library-name", "PBR Essentials");
const LIBRARY_SOURCE = flag("source", "libraries/pbr-library.blend");
const BLEND_FILE = flag("blend", "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, BLEND_FILE));
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));