From 7dee0af25b5606b3f44cd9c773b3ea3cfc7649e5 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 22 Sep 2026 15:27:24 -0700 Subject: [PATCH 1/2] feat: add USD input block with packaged importer Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .gitignore | 1 + eslint.config.mjs | 2 +- package.json | 4 +- packages/core/build/codecBuildPlugin.ts | 37 ++++++++++++- packages/core/build/prepareUsdRuntime.ts | 63 ++++++++++++++++++++++ packages/core/docs/blocks.md | 5 ++ packages/core/package.json | 11 ++-- packages/core/src/blocks/usdInputBlock.ts | 47 ++++++++++++++++ packages/core/src/index.ts | 1 + packages/core/src/types/assets.d.ts | 5 ++ pnpm-lock.yaml | 54 +++++++++---------- tests/bundle/browserConsumerBundle.test.ts | 47 ++++++++++++++++ vitest.config.ts | 2 +- 13 files changed, 242 insertions(+), 37 deletions(-) create mode 100644 packages/core/build/prepareUsdRuntime.ts create mode 100644 packages/core/src/blocks/usdInputBlock.ts diff --git a/.gitignore b/.gitignore index d560b2c..53bdb71 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ packages/core/docs/typedocs test-results/ *.tsbuildinfo .vite/ +packages/core/.cache/usd/ .env.local .DS_Store diff --git a/eslint.config.mjs b/eslint.config.mjs index 4a9cbca..69ae039 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -63,7 +63,7 @@ const internalNamingPlugin = { export default tseslint.config( { - ignores: ["**/dist/**", "**/node_modules/**", "test-results/**", "docs/**", "packages/core/docs/typedocs/**", "**/*.md"], + ignores: ["**/dist/**", "**/node_modules/**", "test-results/**", "docs/**", "packages/core/docs/typedocs/**", "packages/core/.cache/**", "**/*.md"], }, js.configs.recommended, diff --git a/package.json b/package.json index 2d0d889..4a8eb17 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "typedocs": "pnpm --filter @babylonjs/node-assets typedocs" }, "devDependencies": { - "@babylonjs/core": "9.21.2", - "@babylonjs/ktx2decoder": "9.21.2", + "@babylonjs/core": "9.27.1", + "@babylonjs/ktx2decoder": "9.27.1", "@babylonjs/node-assets": "workspace:*", "@eslint/js": "^10.0.1", "@gltf-transform/core": "4.5.0", diff --git a/packages/core/build/codecBuildPlugin.ts b/packages/core/build/codecBuildPlugin.ts index cc4820e..479000e 100644 --- a/packages/core/build/codecBuildPlugin.ts +++ b/packages/core/build/codecBuildPlugin.ts @@ -5,25 +5,36 @@ import { fileURLToPath } from "node:url"; import type { Plugin, ResolvedConfig } from "vite"; +import { prepareUsdRuntimeAsync, UsdRuntimeAssets, UsdRuntimeNotices } from "./prepareUsdRuntime"; + export const MscTranscoderModuleId = "virtual:node-assets-msc-transcoder"; const ResolvedMscTranscoderModuleId = `\0${MscTranscoderModuleId}`; const BasisEncoderWasmUrlModuleId = "virtual:node-assets-basis-encoder-wasm-url"; +const UsdRuntimeUrlsModuleId = "virtual:node-assets-usd-runtime-urls"; +const ResolvedUsdRuntimeUrlsModuleId = `\0${UsdRuntimeUrlsModuleId}`; const DracoNodeRuntimeDetection = /"object"==typeof process&&"object"==typeof process\.versions&&"string"==typeof process\.versions\.node/g; -export function codecBuildPlugin(): Plugin { +export function codecBuildPlugin(usdPackageDirectory = "."): Plugin { let resolvePackage: ReturnType | undefined; let isBuild = false; + let usdCacheDirectory: string; + let usdFiles: ReturnType | undefined; + const prepareUsdAsync = () => (usdFiles ??= prepareUsdRuntimeAsync(usdCacheDirectory)); return { name: "node-assets-codecs", enforce: "pre", configResolved(config) { resolvePackage = config.createResolver(); isBuild = config.command === "build"; + usdCacheDirectory = resolve(config.root, usdPackageDirectory, ".cache/usd/5"); }, async resolveId(id) { if (id === MscTranscoderModuleId) { return ResolvedMscTranscoderModuleId; } + if (id === UsdRuntimeUrlsModuleId) { + return ResolvedUsdRuntimeUrlsModuleId; + } if (id === BasisEncoderWasmUrlModuleId) { // TODO: Remove this override when the encoder keeps its relative WASM URL valid after Vite pre-bundling. const encoderEntry = await resolvePackage?.("babylonpress-ktx2-encoder", fileURLToPath(import.meta.url)); @@ -34,6 +45,17 @@ export function codecBuildPlugin(): Plugin { } }, async load(id) { + if (id === ResolvedUsdRuntimeUrlsModuleId) { + const files = await prepareUsdAsync(); + const urls = UsdRuntimeAssets.map(({ name, option }, index) => { + const path = files.get(name); + if (!path) { + throw new Error(`Missing USD runtime asset "${name}".`); + } + return { import: `import asset${index} from ${JSON.stringify(`${path}?url&no-inline`)};`, option: `${option}: asset${index}` }; + }); + return [...urls.map((url) => url.import), `export default { ${urls.map((url) => url.option).join(", ")} };`].join("\n"); + } if (id !== ResolvedMscTranscoderModuleId) { return; } @@ -62,5 +84,18 @@ export function codecBuildPlugin(): Plugin { } return { code: transformed, map: null }; }, + async generateBundle() { + if (!isBuild) { + return; + } + const files = await prepareUsdAsync(); + for (const { name } of UsdRuntimeNotices) { + const path = files.get(name); + if (!path) { + throw new Error(`Missing USD runtime notice "${name}".`); + } + this.emitFile({ type: "asset", fileName: `licenses/usd/${name}`, source: await readFile(path) }); + } + }, }; } diff --git a/packages/core/build/prepareUsdRuntime.ts b/packages/core/build/prepareUsdRuntime.ts new file mode 100644 index 0000000..d4599cc --- /dev/null +++ b/packages/core/build/prepareUsdRuntime.ts @@ -0,0 +1,63 @@ +import { createHash, randomUUID } from "node:crypto"; +import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +const BabylonRelease = "8b086dc2b313b61918b0dc3854d3a1d0aff4530e"; // 9.27.1 +const ImporterRelease = "2baa7a087bad28498378dacf50d22f46c54eacc3"; // protocol 5 +const BabylonRuntimeUrl = `https://raw.githubusercontent.com/BabylonJS/Babylon.js/${BabylonRelease}/packages/tools/babylonServer/public/babylonUsdImporter/5/`; +const ImporterUrl = `https://raw.githubusercontent.com/BabylonJS/babylon-usd-importer/${ImporterRelease}/`; + +export const UsdRuntimeAssets = [ + { name: "babylon-usd-importer.worker.js", option: "workerUrl", sha256: "caaf962049c25a16ed15745508d1dd547580e36edf36f7a9ebc0f27759de2dc5" }, + { name: "babylon-usd-importer.js", option: "glueUrl", sha256: "ccd71ba966d27b1b08aa540915cb983ece00262b440f519d94c2505e2b88c120" }, + { name: "babylon-usd-importer.wasm", option: "wasmUrl", sha256: "72f0d5a1ce2476566b9455dc16f19738d6b4c03776e2e103027e0c7a096d98d2" }, + { name: "babylon-usd-importer.data", option: "dataUrl", sha256: "ededef6a1ecbc5aa112ffa0f4e2713a13bca316ae005b3d95b91fd8dad240225" }, +] as const; + +export const UsdRuntimeNotices = [ + { name: "usd-importer.license", source: `${ImporterUrl}LICENSE`, sha256: "5c9817c129b98e7bb966bca028c43c19107102ef8e03fe799bffb4354f4ef015" }, + { name: "openusd.license", source: `${BabylonRuntimeUrl}openusd.license`, sha256: "4d6e8e3a9bd0104e10c48e3bc6af2f0976448a70a377d20cef674740f96f4452" }, + { name: "tbb.license", source: `${BabylonRuntimeUrl}tbb.license`, sha256: "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" }, + { name: "zlib.license", source: `${BabylonRuntimeUrl}zlib.license`, sha256: "e32ff4e00d9d94930537635291da39e7e612703334bf6fde8c7f1686fe8a45a2" }, +] as const; + +export async function prepareUsdRuntimeAsync(cacheDirectory: string): Promise> { + const files = await Promise.all( + [...UsdRuntimeAssets.map((asset) => ({ ...asset, source: `${BabylonRuntimeUrl}${asset.name}` })), ...UsdRuntimeNotices].map(async ({ name, source, sha256 }) => { + const path = join(cacheDirectory, name); + let bytes: Uint8Array | undefined; + try { + bytes = await readFile(path); + } catch (error) { + if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) { + throw error; + } + } + if (bytes === undefined) { + const response = await fetch(source); + if (!response.ok) { + throw new Error(`Unable to download USD runtime asset "${name}": ${response.status} ${response.statusText}`); + } + bytes = new Uint8Array(await response.arrayBuffer()); + verifyChecksum(name, bytes, sha256); + await mkdir(cacheDirectory, { recursive: true }); + const temporaryPath = `${path}.${randomUUID()}.tmp`; + try { + await writeFile(temporaryPath, bytes); + await rename(temporaryPath, path); + } finally { + await rm(temporaryPath, { force: true }); + } + } + verifyChecksum(name, bytes, sha256); + return [name, path] as const; + }) + ); + return new Map(files); +} + +function verifyChecksum(name: string, bytes: Uint8Array, expected: string): void { + if (createHash("sha256").update(bytes).digest("hex") !== expected) { + throw new Error(`USD runtime asset "${name}" does not match its pinned SHA-256 checksum.`); + } +} diff --git a/packages/core/docs/blocks.md b/packages/core/docs/blocks.md index 5939d90..e1552bd 100644 --- a/packages/core/docs/blocks.md +++ b/packages/core/docs/blocks.md @@ -23,6 +23,11 @@ - Output: `Document` - Uses: Babylon STL loader - Behavior: Uses the Babylon scene loader to load an STL using NullEngine, exports it as a GLB, then reimports the bytes as a `Document`. +- `UsdInputBlock` + - Input: `string` HTTP(S) or data URL, or a Node filesystem path/file URL, pointing to a USD, USDA, USDC, or USDZ file. + - Output: `Document` + - Uses: Babylon USD loader and the packaged OpenUSD importer runtime; requires Web Worker support. + - Behavior: Uses the Babylon scene loader to load USD using NullEngine, exports it as a GLB, then reimports the bytes as a `Document`. # Transforms diff --git a/packages/core/package.json b/packages/core/package.json index d23b4e3..a7b3d92 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,7 +26,8 @@ "asset-pipeline", "draco", "meshopt", - "ktx2" + "ktx2", + "usd" ], "repository": { "type": "git", @@ -45,10 +46,10 @@ "typedocs": "typedoc" }, "dependencies": { - "@babylonjs/core": "^9.21.2", - "@babylonjs/ktx2decoder": "9.21.2", - "@babylonjs/loaders": "^9.21.2", - "@babylonjs/serializers": "^9.21.2", + "@babylonjs/core": "^9.27.1", + "@babylonjs/ktx2decoder": "9.27.1", + "@babylonjs/loaders": "^9.27.1", + "@babylonjs/serializers": "^9.27.1", "@gltf-transform/core": "4.5.0", "@gltf-transform/extensions": "4.5.0", "@gltf-transform/functions": "4.5.0", diff --git a/packages/core/src/blocks/usdInputBlock.ts b/packages/core/src/blocks/usdInputBlock.ts new file mode 100644 index 0000000..fb196e4 --- /dev/null +++ b/packages/core/src/blocks/usdInputBlock.ts @@ -0,0 +1,47 @@ +import type { ISceneLoaderPluginFactory } from "@babylonjs/core/Loading/sceneLoader.js"; +import { USDFileLoaderMetadata } from "@babylonjs/loaders/USD/usdFileLoader.metadata.js"; + +import { GltfDocumentType } from "../connectionPoints/gltfDocument"; +import { UrlType } from "../connectionPoints/url"; +import { convertBabylonSceneToDocumentAsync } from "../helpers/convertBabylonSceneToDocument"; +import { loadSceneWithPluginAsync } from "../helpers/loadSceneWithPlugin"; +import { NullEngineResource } from "../resources/nullEngineResource"; +import { PlatformIOResource } from "../resources/platformIOResource"; +import { Block, type BlockOptions } from "./block"; +import { defineBlock } from "./blockDefinition"; + +const UsdLoaderFactory = { + ...USDFileLoaderMetadata, + createPlugin: async () => { + const [{ USDFileLoader, _RegisterUSDLoaderDependencies }, { default: runtimeUrls }] = await Promise.all([ + import("@babylonjs/loaders/USD/usdFileLoader.pure.js"), + import("virtual:node-assets-usd-runtime-urls"), + ]); + _RegisterUSDLoaderDependencies(); + return new USDFileLoader(runtimeUrls); + }, +} satisfies ISceneLoaderPluginFactory; + +const UsdInputBlockDefinition = /* @__PURE__ */ defineBlock({ + type: "input.usd", + input: UrlType, + output: GltfDocumentType, + resources: { + engine: NullEngineResource, + io: PlatformIOResource, + }, + runAsync: async (url, _config, { engine, io }) => + convertBabylonSceneToDocumentAsync( + await loadSceneWithPluginAsync(url, engine, UsdLoaderFactory, { + pluginExtension: ".usd", + }), + io + ), +}); + +/** Loads a USD, USDA, USDC, or USDZ URL or Node filesystem path; requires Web Worker support. */ +export class UsdInputBlock extends Block { + public constructor(options?: BlockOptions) { + super(UsdInputBlockDefinition, options); + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7c1257c..da471ff 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -7,6 +7,7 @@ export { GltfInputBlock } from "./blocks/gltfInputBlock"; export { GltfOutputBlock, type GltfOutputBlockOptions } from "./blocks/gltfOutputBlock"; export { ObjInputBlock } from "./blocks/objInputBlock"; export { StlInputBlock } from "./blocks/stlInputBlock"; +export { UsdInputBlock } from "./blocks/usdInputBlock"; export { ValidateBlock, type ValidateBlockOptions } from "./blocks/validateBlock"; export { NodeAsset } from "./nodeAsset"; export { NodeAssetContext } from "./nodeAssetContext"; diff --git a/packages/core/src/types/assets.d.ts b/packages/core/src/types/assets.d.ts index 4e3bc72..7954d8d 100644 --- a/packages/core/src/types/assets.d.ts +++ b/packages/core/src/types/assets.d.ts @@ -12,3 +12,8 @@ declare module "virtual:node-assets-basis-encoder-wasm-url" { const url: string; export default url; } + +declare module "virtual:node-assets-usd-runtime-urls" { + const urls: { readonly workerUrl: string; readonly glueUrl: string; readonly wasmUrl: string; readonly dataUrl: string }; + export default urls; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a3d782..bf7c165 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,11 +9,11 @@ importers: .: devDependencies: '@babylonjs/core': - specifier: 9.21.2 - version: 9.21.2 + specifier: 9.27.1 + version: 9.27.1 '@babylonjs/ktx2decoder': - specifier: 9.21.2 - version: 9.21.2(@babylonjs/core@9.21.2) + specifier: 9.27.1 + version: 9.27.1(@babylonjs/core@9.27.1) '@babylonjs/node-assets': specifier: workspace:* version: link:packages/core @@ -78,17 +78,17 @@ importers: packages/core: dependencies: '@babylonjs/core': - specifier: ^9.21.2 - version: 9.21.2 + specifier: ^9.27.1 + version: 9.27.1 '@babylonjs/ktx2decoder': - specifier: 9.21.2 - version: 9.21.2(@babylonjs/core@9.21.2) + specifier: 9.27.1 + version: 9.27.1(@babylonjs/core@9.27.1) '@babylonjs/loaders': - specifier: ^9.21.2 - version: 9.21.2(@babylonjs/core@9.21.2)(babylonjs-gltf2interface@9.21.2) + specifier: ^9.27.1 + version: 9.27.1(@babylonjs/core@9.27.1)(babylonjs-gltf2interface@9.21.2) '@babylonjs/serializers': - specifier: ^9.21.2 - version: 9.21.2(@babylonjs/core@9.21.2)(babylonjs-gltf2interface@9.21.2) + specifier: ^9.27.1 + version: 9.27.1(@babylonjs/core@9.27.1)(babylonjs-gltf2interface@9.21.2) '@gltf-transform/core': specifier: 4.5.0 version: 4.5.0 @@ -139,22 +139,22 @@ packages: resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} - '@babylonjs/core@9.21.2': - resolution: {integrity: sha512-y4brONcZ4X5rfoiLqTdd9jSZzyIeJ+zHhvN6qgDCpgVIyM2sJlCouLBSvNn+Kl7jIxmzJhIvbBvUIS/sx8jFUw==} + '@babylonjs/core@9.27.1': + resolution: {integrity: sha512-bcHeB7YNrYRiD+pEhZjEfzm8tT/N4dC8oYhlENFVa9fnP/XiHEbmt7h2U6Es/vs8x/Aju+9vgpcmq2qnLv+3Hw==} - '@babylonjs/ktx2decoder@9.21.2': - resolution: {integrity: sha512-G5DAxbkIVHQKAvDXvnDvH/PFWdi1PkjsTrDVNQux1+S6IG+EYrkyqt3EqFBLeprzyYoiV20r1g2wrOFCVslshA==} + '@babylonjs/ktx2decoder@9.27.1': + resolution: {integrity: sha512-Cf7wmGWn/IxcD6rcPZPySNpQ44FmjoTty2t8LXEFAxuk11LxUy2x1F6EJcXgz94ecZ1dw7t7NEknAjkq0D/OLA==} peerDependencies: '@babylonjs/core': ^9.0.0 - '@babylonjs/loaders@9.21.2': - resolution: {integrity: sha512-ZPfVpNBbxZhL3Xns355gyIFBfEgq4qGAbW/Rf5rwvkivTbQcKQRTmp+GTieTdPtEcPGBTSBVFnr/LioKEmJlvQ==} + '@babylonjs/loaders@9.27.1': + resolution: {integrity: sha512-BCtnH4OuCiq7XPNENsOPyNBRGOBRlVbld49YwMIalbNjjgcAMMtk+wx8kzE1/ywKF3Hfrjr17bMLT3qgmJZ42g==} peerDependencies: '@babylonjs/core': ^9.0.0 babylonjs-gltf2interface: ^9.0.0 - '@babylonjs/serializers@9.21.2': - resolution: {integrity: sha512-qbBMUQDCRrv5RBIj5/JZY1lFVGOGHDQ7xbBVcKibT1cSXBXU1bkXHAAEa/ATOa+N3m6YgPBAAxe8yQ9C/cZJ6Q==} + '@babylonjs/serializers@9.27.1': + resolution: {integrity: sha512-mMdS8TXMOPxVaTph4Wmi1dbvJo/so5V5FMs8ob3uAL6QntB2Twg4P2BO5EhORvSj09gH4NYX4lh1F1zKbk8s0A==} peerDependencies: '@babylonjs/core': ^9.0.0 babylonjs-gltf2interface: ^9.0.0 @@ -1715,20 +1715,20 @@ snapshots: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babylonjs/core@9.21.2': {} + '@babylonjs/core@9.27.1': {} - '@babylonjs/ktx2decoder@9.21.2(@babylonjs/core@9.21.2)': + '@babylonjs/ktx2decoder@9.27.1(@babylonjs/core@9.27.1)': dependencies: - '@babylonjs/core': 9.21.2 + '@babylonjs/core': 9.27.1 - '@babylonjs/loaders@9.21.2(@babylonjs/core@9.21.2)(babylonjs-gltf2interface@9.21.2)': + '@babylonjs/loaders@9.27.1(@babylonjs/core@9.27.1)(babylonjs-gltf2interface@9.21.2)': dependencies: - '@babylonjs/core': 9.21.2 + '@babylonjs/core': 9.27.1 babylonjs-gltf2interface: 9.21.2 - '@babylonjs/serializers@9.21.2(@babylonjs/core@9.21.2)(babylonjs-gltf2interface@9.21.2)': + '@babylonjs/serializers@9.27.1(@babylonjs/core@9.27.1)(babylonjs-gltf2interface@9.21.2)': dependencies: - '@babylonjs/core': 9.21.2 + '@babylonjs/core': 9.27.1 babylonjs-gltf2interface: 9.21.2 '@emnapi/runtime@1.11.3': diff --git a/tests/bundle/browserConsumerBundle.test.ts b/tests/bundle/browserConsumerBundle.test.ts index 37bd461..8e12ca7 100644 --- a/tests/bundle/browserConsumerBundle.test.ts +++ b/tests/bundle/browserConsumerBundle.test.ts @@ -91,6 +91,38 @@ describe("browser consumer bundle", () => { expect(chunks.some((chunk) => chunk.dynamicImports.some((id) => /ktx2Decoder|msc-transcoder/.test(id)))).toBe(false); }, 120_000); + it("bundles the published USD input with its own importer assets", async () => { + const result = await build({ + configFile: false, + logLevel: "silent", + plugins: [rejectNodeOnlyDependencies(), createUsdOnlyConsumerPlugin()], + build: { + assetsInlineLimit: 0, + rollupOptions: { + input: "node-assets:usd-only-browser-consumer", + }, + write: false, + }, + }); + if (Array.isArray(result) || !("output" in result)) { + throw new Error("Expected one consumer bundle"); + } + + const runtimeNames = ["babylon-usd-importer.worker.js", "babylon-usd-importer.js", "babylon-usd-importer.wasm", "babylon-usd-importer.data"]; + const fileNames = result.output.map(({ fileName }) => fileName); + const publishedFiles = await readdir(dirname(PublishedEntryPath)); + for (const name of runtimeNames) { + expect(publishedFiles).toContain(name); + const dot = name.lastIndexOf("."); + expect(fileNames.some((fileName) => fileName.includes(`${name.slice(0, dot)}-`) && fileName.endsWith(name.slice(dot)))).toBe(true); + } + const publishedNotices = await readdir(join(dirname(PublishedEntryPath), "licenses", "usd")); + expect(publishedNotices).toEqual(["openusd.license", "tbb.license", "usd-importer.license", "zlib.license"]); + + const chunks = result.output.filter((entry) => entry.type === "chunk"); + expect(chunks.some((chunk) => chunk.code.includes("babylon-usd-importer.worker-"))).toBe(true); + }, 120_000); + it("runs the published entry in Node", async () => { const url = "https://example.com/model.gltf"; vi.stubGlobal( @@ -170,6 +202,21 @@ function createEncoderOnlyConsumerPlugin(): Plugin { }; } +function createUsdOnlyConsumerPlugin(): Plugin { + const moduleId = "\0node-assets-usd-only-browser-consumer"; + return { + name: "node-assets-usd-only-browser-consumer", + resolveId: (id) => (id === "node-assets:usd-only-browser-consumer" ? moduleId : undefined), + load: (id) => + id === moduleId + ? ` + import { UsdInputBlock } from ${JSON.stringify(PublishedPackageName)}; + globalThis.UsdInputBlock = UsdInputBlock; + ` + : undefined, + }; +} + function rejectNodeOnlyDependencies(): Plugin { return { name: "node-assets-reject-node-only-dependencies", diff --git a/vitest.config.ts b/vitest.config.ts index 0a2f26a..906db27 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -8,7 +8,7 @@ import { codecBuildPlugin } from "./packages/core/build/codecBuildPlugin"; // those codecs are implemented; do not use Vitest browser mode. Babylon.js and // Babylon-Lite use this configuration. export default defineConfig({ - plugins: [codecBuildPlugin()], + plugins: [codecBuildPlugin("packages/core")], test: { environment: "node", include: ["tests/**/*.test.ts"], From 94c6a03605d36dd249b9d2a18cf4f975400c7473 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Thu, 1 Oct 2026 16:03:28 -0400 Subject: [PATCH 2/2] refactor: trim USD integration scope Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- packages/core/build/codecBuildPlugin.ts | 9 ++--- packages/core/build/prepareUsdRuntime.ts | 2 +- packages/core/package.json | 3 +- tests/bundle/browserConsumerBundle.test.ts | 47 ---------------------- vitest.config.ts | 2 +- 5 files changed, 7 insertions(+), 56 deletions(-) diff --git a/packages/core/build/codecBuildPlugin.ts b/packages/core/build/codecBuildPlugin.ts index 479000e..232e8f9 100644 --- a/packages/core/build/codecBuildPlugin.ts +++ b/packages/core/build/codecBuildPlugin.ts @@ -14,10 +14,10 @@ const UsdRuntimeUrlsModuleId = "virtual:node-assets-usd-runtime-urls"; const ResolvedUsdRuntimeUrlsModuleId = `\0${UsdRuntimeUrlsModuleId}`; const DracoNodeRuntimeDetection = /"object"==typeof process&&"object"==typeof process\.versions&&"string"==typeof process\.versions\.node/g; -export function codecBuildPlugin(usdPackageDirectory = "."): Plugin { +export function codecBuildPlugin(): Plugin { let resolvePackage: ReturnType | undefined; let isBuild = false; - let usdCacheDirectory: string; + const usdCacheDirectory = fileURLToPath(new URL("../.cache/usd/5/", import.meta.url)); let usdFiles: ReturnType | undefined; const prepareUsdAsync = () => (usdFiles ??= prepareUsdRuntimeAsync(usdCacheDirectory)); return { @@ -26,7 +26,6 @@ export function codecBuildPlugin(usdPackageDirectory = "."): Plugin { configResolved(config) { resolvePackage = config.createResolver(); isBuild = config.command === "build"; - usdCacheDirectory = resolve(config.root, usdPackageDirectory, ".cache/usd/5"); }, async resolveId(id) { if (id === MscTranscoderModuleId) { @@ -85,10 +84,10 @@ export function codecBuildPlugin(usdPackageDirectory = "."): Plugin { return { code: transformed, map: null }; }, async generateBundle() { - if (!isBuild) { + if (!usdFiles) { return; } - const files = await prepareUsdAsync(); + const files = await usdFiles; for (const { name } of UsdRuntimeNotices) { const path = files.get(name); if (!path) { diff --git a/packages/core/build/prepareUsdRuntime.ts b/packages/core/build/prepareUsdRuntime.ts index d4599cc..49c26f1 100644 --- a/packages/core/build/prepareUsdRuntime.ts +++ b/packages/core/build/prepareUsdRuntime.ts @@ -34,7 +34,7 @@ export async function prepareUsdRuntimeAsync(cacheDirectory: string): Promise { expect(chunks.some((chunk) => chunk.dynamicImports.some((id) => /ktx2Decoder|msc-transcoder/.test(id)))).toBe(false); }, 120_000); - it("bundles the published USD input with its own importer assets", async () => { - const result = await build({ - configFile: false, - logLevel: "silent", - plugins: [rejectNodeOnlyDependencies(), createUsdOnlyConsumerPlugin()], - build: { - assetsInlineLimit: 0, - rollupOptions: { - input: "node-assets:usd-only-browser-consumer", - }, - write: false, - }, - }); - if (Array.isArray(result) || !("output" in result)) { - throw new Error("Expected one consumer bundle"); - } - - const runtimeNames = ["babylon-usd-importer.worker.js", "babylon-usd-importer.js", "babylon-usd-importer.wasm", "babylon-usd-importer.data"]; - const fileNames = result.output.map(({ fileName }) => fileName); - const publishedFiles = await readdir(dirname(PublishedEntryPath)); - for (const name of runtimeNames) { - expect(publishedFiles).toContain(name); - const dot = name.lastIndexOf("."); - expect(fileNames.some((fileName) => fileName.includes(`${name.slice(0, dot)}-`) && fileName.endsWith(name.slice(dot)))).toBe(true); - } - const publishedNotices = await readdir(join(dirname(PublishedEntryPath), "licenses", "usd")); - expect(publishedNotices).toEqual(["openusd.license", "tbb.license", "usd-importer.license", "zlib.license"]); - - const chunks = result.output.filter((entry) => entry.type === "chunk"); - expect(chunks.some((chunk) => chunk.code.includes("babylon-usd-importer.worker-"))).toBe(true); - }, 120_000); - it("runs the published entry in Node", async () => { const url = "https://example.com/model.gltf"; vi.stubGlobal( @@ -202,21 +170,6 @@ function createEncoderOnlyConsumerPlugin(): Plugin { }; } -function createUsdOnlyConsumerPlugin(): Plugin { - const moduleId = "\0node-assets-usd-only-browser-consumer"; - return { - name: "node-assets-usd-only-browser-consumer", - resolveId: (id) => (id === "node-assets:usd-only-browser-consumer" ? moduleId : undefined), - load: (id) => - id === moduleId - ? ` - import { UsdInputBlock } from ${JSON.stringify(PublishedPackageName)}; - globalThis.UsdInputBlock = UsdInputBlock; - ` - : undefined, - }; -} - function rejectNodeOnlyDependencies(): Plugin { return { name: "node-assets-reject-node-only-dependencies", diff --git a/vitest.config.ts b/vitest.config.ts index 906db27..0a2f26a 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -8,7 +8,7 @@ import { codecBuildPlugin } from "./packages/core/build/codecBuildPlugin"; // those codecs are implemented; do not use Vitest browser mode. Babylon.js and // Babylon-Lite use this configuration. export default defineConfig({ - plugins: [codecBuildPlugin("packages/core")], + plugins: [codecBuildPlugin()], test: { environment: "node", include: ["tests/**/*.test.ts"],