This guide is for consumers of a published usd_optimize package on Linux — for example, a drop named like:
usd_optimize_usd_<usd_ver>_py_<py_ver>@<version>.<platform>.release
where <platform> is linux-x86_64 or linux-aarch64. The two arches share the same layout and the same prerequisites; only the contents of the .so files differ.
If you are building Usd Optimize from source, see the top-level README and use repo.sh build / repo.sh test instead. The published package does not include repo.sh, source code, or test fixtures — only headers, prebuilt libraries, and Python bindings.
| Directory | Purpose |
|---|---|
include/ |
C++ public headers (usd_optimize/core/) |
lib/ |
Prebuilt shared libraries (libusd_optimize.core.so, plugin .so files, operation_mapping.json — deprecated-name aliases for mapConfig(), not the list of operations) |
bin/ |
The usdOptimize command-line tool |
python/ |
Python bindings (usd_optimize.core) |
usdpy/ |
OpenUSD Python runtime modules (pxr.*) — the package brings its own USD |
extraLibs/ |
Third-party runtime libraries (MaterialX, TBB; USD 25.x drops additionally carry Alembic and OpenSubdiv) and the matching CPython runtime (libpython3.X.so.1.0) |
config_presets/ |
Ready-made operation stacks for usdOptimize -c |
docs/ |
This guide and the rest of the documentation set |
.agents/ |
Task-specific skill files (.agents/skills/<name>/SKILL.md) |
Two notable differences from the Windows drop:
- There is no
pythoninterpreter in the package — you must supply your own that matches the package's Python ABI. - The Linux drop does bundle
libpython3.X.so.1.0, inextraLibs/. The bundledpxrmodules link against it dynamically and resolve it from there onceextraLibsis onLD_LIBRARY_PATH(step 3), so you do not have to install a sharedlibpythonyourself.
The Python version is encoded in the package directory name (py_3.12 in the example above). The bundled USD .so modules are compiled against the CPython 3.12 ABI, so you must supply a matching 3.12 interpreter.
You do not need to supply the shared libpython. The drop bundles libpython3.12.so.1.0 in extraLibs/, and the bundled pxr modules resolve it from there once extraLibs is on LD_LIBRARY_PATH (see step 3). A plain interpreter install is enough.
If python3.12 --version already works, you have everything you need — skip ahead to Installing.
If it does not, you need to obtain one. On Ubuntu/Debian the deadsnakes PPA is the easiest source for a Python version your distro does not ship:
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install python3.12 python3.12-venvImporting
pxrdoes not catch a mismatched interpreter. Because the drop supplies an exact-matchlibpython3.12.so.1.0, the dynamic linker has nothing to object to, so importingpxrunder 3.10 or 3.11 can appear to succeed rather than failing with a clear ABI error. You may instead get anundefined symbol: PyXxx_...ImportError (see Troubleshooting). Which of the two you hit depends on the interpreter and on which symbols a module touches, so neither outcome is guaranteed and a cleanpxrimport does not mean the interpreter is right. Importingusd_optimize.coredoes check, and raises anImportErrornaming both ABIs (see Troubleshooting). Running a mismatched interpreter is unsupported and untested; step 2 shows how to confirm the one you use.
If you prefer a distro-neutral install, pyenv works on any Linux: pyenv install 3.12. You do not need to add PYTHON_CONFIGURE_OPTS="--enable-shared": current pyenv passes --enable-shared itself unless you ask for --disable-shared. A deliberately static build works too, because the drop supplies its own libpython3.12.so.1.0.
With
extraLibsonLD_LIBRARY_PATH, the drop'slibpythontakes precedence.LD_LIBRARY_PATHoutranks an interpreter's ownDT_RUNPATH, so a shared interpreter loads the drop'slibpython3.12.so.1.0rather than the one it shipped with. Across 3.12 micro versions that is harmless — they are ABI-compatible — but it does meansys.versioncan report the drop's build rather than the one you installed.
You only need a host C++ toolchain on the target machine if you are linking your own C++ application against libusd_optimize.core.so. Pure-Python consumers can skip this.
The Linux x86_64 build uses the C++11 ABI (premake.linux_x86_64_cxx_abi in repo.toml), so applications linking against the package must be built with the same ABI. A reasonably recent libstdc++.so.6 is also required at runtime — symptoms of an old one surface as version 'GLIBCXX_X.X.XX' not found from the dynamic linker.
Place the unpacked directory anywhere — for the rest of this guide we assume it lives at:
PACKAGE_ROOT=/path/to/usd_optimize_usd_25.11_py_3.12@<version>.linux-x86_64.releaseA venv keeps Usd Optimize's PYTHONPATH tweaks isolated from any other Python project on the machine:
python3.12 -m venv "$PACKAGE_ROOT/.venv"
source "$PACKAGE_ROOT/.venv/bin/activate"Adjust the interpreter name to wherever your matching Python lives (which python3.12 will print it).
Confirm the venv runs the matching interpreter (3.12.x for a py_3.12 package). The smoke test uses this same interpreter:
"$PACKAGE_ROOT/.venv/bin/python" -c "import sys; print(sys.version)" # expect 3.12.xIf it prints anything else, recreate the venv with the matching Python.
Two paths must be exported every session:
| Variable | Why |
|---|---|
PYTHONPATH += python:usdpy |
Lets the interpreter find both usd_optimize.* and pxr.* |
LD_LIBRARY_PATH += lib:extraLibs |
Lets the dynamic linker resolve transitive shared-object dependencies (USD, TBB, plugin .sos) |
bash/zsh:
export PACKAGE_ROOT=/path/to/usd_optimize_usd_25.11_py_3.12@<version>.linux-x86_64.release
export PYTHONPATH="$PACKAGE_ROOT/python:$PACKAGE_ROOT/usdpy${PYTHONPATH:+:$PYTHONPATH}"
export LD_LIBRARY_PATH="$PACKAGE_ROOT/lib:$PACKAGE_ROOT/extraLibs${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"The ${VAR:+:$VAR} form avoids appending a trailing colon when the variable is unset, which the dynamic linker would otherwise interpret as the current working directory.
To make the settings durable, append the exports to the venv's bin/activate script, or to your ~/.bashrc / ~/.zshrc if Usd Optimize is the only Python package you use in that shell.
A two-step smoke test confirms the bindings load and a real operation executes against an in-memory USD stage. Save the script as smoke_check.py and run it with the matching Python.
# smoke_check.py
from usd_optimize.core import ExecutionContext, UsdOptimizeCore
from pxr import Usd, UsdGeom
# 1. Bindings + USD load
ctx = ExecutionContext()
assert ctx.usdStageId == -1
stage = Usd.Stage.CreateInMemory()
assert ctx.set_stage(stage) and ctx.usdStageId != -1
ctx.remove_stage()
print("[1/3] bindings + USD: OK")
# 2. Op registry populated
core = UsdOptimizeCore.getInstance()
ops = core.getOperations()
assert len(ops) > 0
print(f"[2/3] op registry: {len(ops)} operations registered")
# 3. End-to-end through the public UsdOptimizeCore API
stage = Usd.Stage.CreateInMemory()
UsdGeom.Xform.Define(stage, "/World")
UsdGeom.Cube.Define(stage, "/World/c1")
UsdGeom.Cube.Define(stage, "/World/c2")
ctx.set_stage(stage)
results = core.executeConfig(ctx, [
{"operation": "deletePrims", "primPaths": ["/World/c1"]},
])
assert all(success for success, _error, _output in results)
assert sum(1 for _ in stage.TraverseAll()) == 2 # one prim removed
print("[3/3] UsdOptimizeCore.executeConfig: OK")
print("\nALL SMOKE CHECKS PASSED")Run it:
"$PACKAGE_ROOT/.venv/bin/python" smoke_check.pyExpected output:
[1/3] bindings + USD: OK
[2/3] op registry: <N> operations registered
[3/3] UsdOptimizeCore.executeConfig: OK
ALL SMOKE CHECKS PASSED
The exact value of <N> varies by build — any positive number confirms the plugins loaded.
The public Python entry point is the UsdOptimizeCore singleton in usd_optimize.core. Bind a Usd.Stage to an ExecutionContext, then apply a list of operation descriptors with executeConfig. It takes a parsed Python list (json.loads/json.load for JSON input, not raw text or a file path) and returns one (success, error, output) tuple per operation:
import json
from usd_optimize.core import ExecutionContext, UsdOptimizeCore
from pxr import Usd
stage = Usd.Stage.Open("scene.usd")
context = ExecutionContext()
context.set_stage(stage)
ops = """[
{"operation": "merge"},
{"operation": "optimizeMaterials"}
]"""
results = UsdOptimizeCore.getInstance().executeConfig(context, json.loads(ops))
if not all(ok for ok, _err, _out in results):
raise RuntimeError("optimization failed -- check Usd Optimize log")
stage.Save()Valid operation strings are whatever the loaded plugins register — enumerate them at runtime with UsdOptimizeCore.getInstance().getOperations() (the exact count varies by build). The bundled config_presets/*.json show descriptor JSON for many operations. lib/operation_mapping.json is not that catalog: it only lists deprecated operation keys and a few legacy argument renames for UsdOptimizeCore.getInstance().mapConfig(), so keys such as merge, deletePrims, or decimateMeshes will not appear there. The full per-operation argument reference is in the Usd Optimize user manual.
The Python test suite is not part of a published drop. It and its fixtures are routed into a separate, unpublished archive, so there is no run_discover.py to run. Use the smoke-check above to confirm an install is healthy.
ImportError: libpython3.12.so.1.0: cannot open shared object file: No such file or directory
The drop bundles this library in extraLibs/, so this almost always means extraLibs is missing from LD_LIBRARY_PATH — or was exported after the interpreter started. Re-check step 3 and restart the interpreter.
ls "$PACKAGE_ROOT/extraLibs" | grep libpython # should list libpython3.12.so.1.0
echo "$LD_LIBRARY_PATH" # must contain both lib and extraLibsYou do not need to install a system libpython3.12, run sudo ldconfig, or rebuild Python with --enable-shared — those steps were required by earlier revisions of this guide and are not needed with a bundled runtime.
ImportError: _usd_optimize_impl_core built for .cpython-312-x86_64-linux-gnu.so, but this interpreter expects .cpython-3XX-x86_64-linux-gnu.so
usd_optimize.core checks the interpreter on import, and yours does not match the package's py_<version> token. Recreate the venv with the matching Python (step 2).
ImportError: <something>.so: undefined symbol: PyXxx_...
Your interpreter does not match the package's py_<version> token. Install the matching Python version. Note the converse does not hold: importing only pxr under a mismatched interpreter often produces no error at all — see the Python prerequisite.
ImportError: <pxr/something>.so: cannot open shared object file: No such file or directory
LD_LIBRARY_PATH is missing lib or extraLibs. Both must be on LD_LIBRARY_PATH before the Python process starts so the dynamic linker can resolve transitive .sos. Setting them after import pxr has already run will not help — restart the interpreter.
ImportError: /usr/lib/x86_64-linux-gnu/libstdc++.so.6: version 'GLIBCXX_X.X.XX' not found
The libstdc++.so.6 on the target is older than what the package was built against. Update libstdc++ (e.g. via gcc-13/libstdc++6 on Ubuntu), or run on a newer base image.
ModuleNotFoundError: No module named 'usd_optimize' or 'pxr'
PYTHONPATH is missing python or usdpy. Both directories must be on PYTHONPATH.
ModuleNotFoundError: No module named 'usd_validation_nvidia' (importing usd_optimize.validators)
The drop ships the validator rules but not the framework they build on. Install it from PyPI: pip install usd-validation-nvidia. Only the validators need it — the core bindings and the smoke check work without it.
UsdOptimizeCore.getInstance().getOperations() returns an empty list
The plugin .so files in lib/ did not load. Confirm the directory is on LD_LIBRARY_PATH and that the package matches your platform (linux-x86_64 vs linux-aarch64). Setting LD_DEBUG=libs before the Python process will print the linker's search trace and usually pinpoints the missing dependency.