A fast, configurable linter for OpenUSD scene description files (.usda).
Catch naming violations, structural anti-patterns, composition mistakes, and property issues before they become production problems, in milliseconds, with zero Python or USD SDK dependencies.
$ usd-lint ./assets/
assets/shot_010/layout.usda
3:1 warning Prim "world" does not match PascalCase convention [naming/prim-case]
↳ Rename to "World"
8:5 warning references on prim "hero" has no explicit list-op [composition/require-list-op]
↳ Use `prepend references` for predictable composition order
42:9 warning Primvar "primvars:st" on prim "Ground" has no interpolation specified [properties/primvar-interpolation]
↳ Add `interpolation = "vertex"` (or faceVarying/constant/uniform)
────────────────────────────────────────────────────────────
⚠ 3 files checked: 0 errors, 2 warnings, 1 info
Studios adopting OpenUSD generate thousands of .usda files across departments.
Without automated validation:
- Naming conventions drift between artists and teams
- Deep nesting makes scenes slow to load and hard to debug
- Bare
references =(withoutprepend) causes subtle composition bugs - Missing
defaultPrimbreaks referencing in downstream shots - Primvars without interpolation cause silent render failures
usd-lint catches all of this at authoring time. It runs in CI, pre-commit hooks,
or your editor. It does not replace usdchecker; it enforces your studio's conventions
on top of format validity.
- Fast. Rust-native USDA parser. Lints thousands of files in seconds.
- Configurable.
.usdlint.tomlcontrols every rule. Per-rule severity overrides. - 10 built-in rules across 4 categories (naming, structure, composition, properties).
- Multiple output formats. Colored terminal, JSON (for CI), GitHub Actions annotations.
- Zero runtime dependencies. Single static binary. Drop it anywhere in your pipeline.
From source (requires Rust)
cargo install --git https://github.com/voidreamer/usd-lintDownload pre-built binaries for Linux, macOS (Intel + Apple Silicon), and Windows from the Releases page.
- name: Lint USD files
run: |
curl -L https://github.com/voidreamer/usd-lint/releases/latest/download/usd-lint-x86_64-unknown-linux-gnu -o usd-lint
chmod +x usd-lint
./usd-lint ./assets/ --format github --strict# Lint the current directory (searches recursively for .usda/.usd files)
usd-lint .
# Lint specific files
usd-lint assets/hero.usda shots/shot_010/layout.usda
# Generate a default config
usd-lint init
# List all available rules
usd-lint list-rules
# JSON output for CI/CD
usd-lint ./assets/ --format json
# GitHub Actions annotations
usd-lint ./assets/ --format github
# Fail on warnings too (useful for CI)
usd-lint ./assets/ --strictCreate a .usdlint.toml in your project root (or run usd-lint init):
[global]
default_severity = "warning"
include = ["**/*.usda", "**/*.usd"]
exclude = ["**/legacy/**"]
[naming]
prim_case = "pascal_case" # PascalCase for prim names
property_case = "camel_case" # camelCase for properties
prim_disallowed = ["tmp", "test", "delete_me", "untitled"]
require_default_prim = true
[structure]
max_depth = 12 # Flag deeply nested hierarchies
max_children = 500 # Flag prims with too many children
require_kind_on_root = true # Root prims need `kind` metadata
[composition]
require_list_op = true # Require prepend/append on arcs
max_references_per_prim = 10
[properties]
require_primvar_interpolation = true
# Per-rule overrides
[rules."naming/prim-case"]
severity = "error" # Promote to error for strict enforcement
[rules."structure/require-kind"]
enabled = false # Disable if your studio doesn't use `kind`| Rule ID | Category | Description |
|---|---|---|
naming/prim-case |
Naming | Prim names follow configured casing (default: PascalCase) |
naming/property-case |
Naming | Property names follow configured casing (default: camelCase) |
naming/disallowed-names |
Naming | Prim names don't contain disallowed substrings |
naming/default-prim |
Naming | Layer metadata includes defaultPrim |
structure/max-depth |
Structure | Prim hierarchy doesn't exceed max depth |
structure/max-children |
Structure | Prims don't have excessive child counts |
structure/require-kind |
Structure | Root def prims have kind metadata |
composition/require-list-op |
Composition | Arcs use explicit prepend/append |
composition/max-references |
Composition | Prims don't exceed max reference count |
properties/primvar-interpolation |
Properties | Primvars have interpolation specified |
src/
├── parser/ # PEG grammar (pest) + AST types for USDA
│ ├── usda.pest # The grammar
│ ├── ast.rs # Typed AST nodes (Prim, Property, CompositionArc, etc.)
│ └── parse.rs # pest -> AST conversion
├── config/ # TOML config loading + rule settings
├── rules/ # Lint rules
│ ├── builtins.rs # All 10 built-in rules
│ └── engine.rs # Orchestrates parsing + rule execution
├── reporter/ # Output formatters (text, JSON, GitHub Actions)
├── cli/ # clap-based CLI argument parsing
├── lib.rs # Library entry point (for use as a crate)
└── main.rs # Binary entry point
- USDC support: binary crate file reading (via C FFI to the USD SDK)
- Python bindings: PyO3-based,
pip install usd-lint - Custom rules: user-defined rules via TOML patterns or Lua scripts
- Auto-fix:
--fixflag to automatically correct simple violations - Editor integrations: LSP server, VS Code extension
- Reference validation: check that
@asset_path@targets actually exist on disk - Performance benchmarks: track parsing speed across releases
Contributions are welcome. Whether it is a new rule, a bug fix, or documentation improvements.
# Clone and build
git clone https://github.com/voidreamer/usd-lint.git
cd usd-lint
cargo build
# Run tests
cargo test
# Run against the test fixtures
cargo run -- tests/fixtures/
# Run with a specific config
cargo run -- tests/fixtures/ --config examples/default.usdlint.tomlDual-licensed under MIT or Apache-2.0, at your option.
Built by Alejandro Cabrera. Pipeline TD with 10+ years in VFX and feature animation.