@canetoad/usdc-converter
v0.1.5
Published
Browser USDA/USDZ to binary USDC conversion for Canetoad AR exports.
Maintainers
Readme
@canetoad/usdc-converter
Browser-first USDA/USDZ to binary USDC conversion for Canetoad AR exports.
The package wraps the native Rust openusd crate in WebAssembly. It avoids the
heavy OpenUSD C++/Emscripten path while still using a real USDC crate writer.
Why this path
three/examples/jsm/exporters/USDZExporterwrites a USDZ package whose root layer is ASCII USDA.- OpenUSD C++ can write USDC, but a browser build means shipping a large custom wasm bundle and dealing with Emscripten integration.
- TinyUSDZ has useful WASM work, but its USDC writer is still documented as experimental.
openusd0.5.0 is pure Rust and currently advertises read/write support for.usda,.usdc, and.usdz, which makes a compact wasm wrapper feasible.
Install
bun add @canetoad/usdc-converterThe base converter entrypoint has no runtime dependency on Three.js. If you use
the @canetoad/usdc-converter/three helper, install Three.js in the consuming
app as well:
bun add threeUsage
import { exportThreeSceneToUsdzBlob } from "@canetoad/usdc-converter/three";
const blob = await exportThreeSceneToUsdzBlob(threeScene);
const url = URL.createObjectURL(blob);On iPhone/iPad, use the blob URL as an AR Quick Look link:
<a rel="ar" href="blob:...">Open in AR</a>For lower-level control, convert the output of Three's USDZExporter directly:
import { convertUsdzToBinaryUsdz } from "@canetoad/usdc-converter";
const asciiUsdz = await exporter.parseAsync(threeScene);
const binaryUsdz = await convertUsdzToBinaryUsdz(asciiUsdz);convertUsdzToBinaryUsdz() preserves the first/root USD layer as text and
converts non-root ASCII USD layers from .usda or ASCII .usd to .usdc. It
rewrites root-layer asset references that point at converted layers and copies
non-USD assets unchanged. Keeping the root/material layer as USDA avoids a
RealityKit material-resolution failure observed with Rust-written binary root
layers, while still shrinking the heavy geometry layers.
Packages that contain existing binary non-root USD layers plus ASCII non-root USD layers that need conversion are rejected for now, because references inside existing binary layers cannot be rewritten safely without a broader authoring pass.
If your bundler does not serve package wasm assets from import.meta.url, pass
the wasm bytes or URL explicitly:
await initToadUsdcConverter(wasmBytes);API
initToadUsdcConverter(input?): initializes the wasm module. By default it loadswasm/toad_usdc_converter_wasm_bg.wasmrelative to the package module. Pass wasm bytes or a wasm URL when your bundler cannot serve that package asset fromimport.meta.url.convertUsdaToUsdc(source): converts a USDA string,Blob, orResponseto a raw.usdcUint8Array. Accepts{ wasm }as an optional second argument.convertUsdzToBinaryUsdz(source): converts non-root ASCII USD layers in a USDZ archive to binary USDC and returns a compact USDZUint8Array. AcceptsArrayBuffer,ArrayBufferView,Blob,Response, orUint8Array, plus{ wasm }as an optional second argument.inspectUsdz(source): returns root-layer metadata for quick verification. Accepts the same byte inputs and optional{ wasm }argument asconvertUsdzToBinaryUsdz.isUsdc(source): checks for thePXR-USDCmagic bytes. Accepts the same byte inputs and optional{ wasm }argument asconvertUsdzToBinaryUsdz.
The @canetoad/usdc-converter/three entrypoint adds:
exportThreeSceneToBinaryUsdz(scene, options?): runs Three'sUSDZExporterand returns compact USDZ bytes with a USDA root and binary USDC geometry layers. Options are ThreeUSDZExporterOptionsplus optionalwasm,exporter, andtextureUtils.exportThreeSceneToUsdzBlob(scene, options?): same conversion, returned as amodel/vnd.usdz+zipBlobfor browser links/downloads.
Build
bun install
bun run build
bun run typecheck
bun run test
npm pack --dry-runThe build requires Rust with the wasm32-unknown-unknown target:
rustup target add wasm32-unknown-unknownbun run typecheck builds the generated WASM TypeScript bindings first. After
wasm/ already exists locally, bun run typecheck:ts runs the faster
TypeScript-only check.
Validation
The Rust openusd crate is vendored because crates.io 0.5.0 writes SDF
child-list token vectors in a way Apple USD tools ignore. That produced USDC
layers with valid metadata but no visible prim hierarchy. The vendored patch
uses TokenVector for child-list fields such as primChildren and
propertyChildren, while leaving authored token[] attributes such as
xformOpOrder as normal token arrays.
On macOS, bun run test uses /usr/bin/usdchecker --arkit and /usr/bin/usdcat
to verify that generated USDZ files are ARKit-valid and that prims survive the
conversion.
The converter also normalizes a Three USDZExporter material quirk before
writing USDC: texture shader inputs that Three authors as token are rewritten
to the UsdShade types RealityKit follows (string inputs:varname and
float2 inputs:in.connect). Apple's usdchecker --arkit can still report
success when those fields are left as token, but Quick Look renders the
material without its texture after binary conversion.
Runtime And Publishing
@canetoad/usdc-converter is ESM-only and targets modern browsers and
bundlers that can load WebAssembly package assets. Releases are published
publicly to npm with provenance enabled through GitHub trusted publishing.
License
PolyForm Noncommercial License 1.0.0. See LICENSE for license terms and
required notices. The vendored openusd crate keeps its own license under
rust/vendor/openusd/LICENSE.
