openusd

openusd is a Pure Ruby library for reading, composing, editing, and writing OpenUSD ASCII (.usda) and package (.usdz) assets. It requires Ruby 3.2 or newer and has no runtime gem or native-library dependencies.

Version 1.0 focuses on pipeline automation, validation, conversion, and scene generation. It supports sublayers and references, but it is not a replacement for the complete C++ OpenUSD composition engine.

Installation

Add the gem to your bundle:

bundle add openusd

Or install it directly:

gem install openusd

Create and save a stage

require "openusd"

stage = OpenUSD::Stage.create("scene.usda")
stage.define_prim("/World", "Xform")
cube = stage.define_prim("/World/Cube", "Cube")
cube.create_attribute("size", "double").set(2.0)
cube.create_attribute("xformOp:translate", "double3").set([0, 1, 0])
cube.create_relationship("target").set_targets(["/World"])

stage.root_layer..merge!(
  "defaultPrim" => "World",
  "metersPerUnit" => 1.0,
  "upAxis" => "Y"
)
stage.save

Open and traverse a composed stage

stage = OpenUSD::Stage.open("scene.usda")

stage.traverse do |prim|
  puts "#{prim.path} <#{prim.type_name}>"
end

size = stage.prim_at("/World/Cube").attribute("size").get

Stage.open(path, missing_assets: :warn) warns and skips unresolved sublayers or references. The default policy, :error, raises OpenUSD::CompositionError; :ignore silently skips them.

Time samples and metadata

prim = stage.prim_at("/World/Cube")
attribute = prim.create_attribute("animatedSize", "double")
attribute.set(1.0, time: 0)
attribute.set(3.0, time: 24)

attribute.get(time: 12) # => 2.0
prim.["kind"] = "component"
attribute.["documentation"] = "Animated cube size"

Schema helpers

xform = OpenUSD::Schema::Xform.define(stage, "/World/Model")
xform.translate = [0, 2, 0]
xform.scale = [2, 2, 2]

mesh = OpenUSD::Schema::Mesh.define(stage, "/World/Model/Mesh")
mesh.points = [[0, 0, 0], [1, 0, 0], [0, 1, 0]]
mesh.face_vertex_counts = [3]
mesh.face_vertex_indices = [0, 1, 2]

USDZ

Package an existing root layer and its assets:

OpenUSD::Format::Usdz::Writer.pack(
  "scene.usdz",
  root: "scene.usda",
  assets: ["textures/albedo.png"]
)

Every entry is stored without compression and begins at a 64-byte boundary. The reader validates bounds, CRC values, alignment, encryption/compression flags, and extraction paths.

stage = OpenUSD::Stage.open("scene.usdz")
OpenUSD::Format::Usdz::Reader.unpack("scene.usdz", destination: "unpacked")

Command line

openusd cat scene.usda
openusd cat --output formatted.usda scene.usda
openusd tree scene.usdz
openusd zip scene.usdz scene.usda textures/albedo.png

The repository includes a reproducible CLI smoke-test procedure covering all three subcommands and official validation of their outputs.

Supported scope

Version 1.0 supports:

  • USDA layer metadata, prims, typed attributes, relationships, time samples, dictionaries, connections, references, and variant selections
  • deterministic USDA output and semantic round trips
  • root layer → authored sublayers → references composition strength
  • stored ZIP32 USDZ packages with internal USDA references
  • Xform, Mesh, Camera, Material, and Scope conveniences

Version 1.0 does not support USDC (Crate), payloads, inherits, specializes, full list-edit semantics, Hydra/imaging, or concurrent writes to one Stage. Separate stages and concurrent read-only access are safe as long as application code does not mutate their underlying Layers.

Unknown USDA value types and metadata are retained where the parser can represent their syntax, which allows newer authored data to survive a parse/write round trip.

Development

bundle install
bundle exec rake spec
bundle exec rake lint
bundle exec rake doc
bundle exec rake compatibility # requires usdchecker and usdcat on PATH
bundle exec rake golden:update  # intentionally refresh exact writer baselines
bundle exec rake bench

The compatibility task validates generated USDA and USDZ files with the official usdchecker, then parses every golden USDA output with usdcat. In CI, where the usd-core wheel does not install those executables, the task uses the equivalent official UsdValidation and Sdf Python APIs. The benchmark defaults to 100,000 generated prims and a 1,000,000-vertex Mesh, reports elapsed time and resident-memory growth, and enforces a five-second 100,000-Prim budget. Set PRIMS, VERTICES, or PRIM_BUDGET to adjust it.

License

The gem is available under the MIT License.