Skip to content

Add USD to Simularium converter#206

Open
dillonl wants to merge 5 commits intosimularium:mainfrom
dillonl:main
Open

Add USD to Simularium converter#206
dillonl wants to merge 5 commits intosimularium:mainfrom
dillonl:main

Conversation

@dillonl
Copy link
Copy Markdown

@dillonl dillonl commented Mar 31, 2026

Time estimate or Size
Small: ~1 hour to review. New self-contained module with no changes to existing code paths.

Problem
There is no way to convert USD (Universal Scene Description) files into the .simularium format. USD is a widely used interchange format exported by tools like Blender, Maya, and Houdini, and is a natural input for biological simulation visualization workflows.

Solution
Added a UsdConverter and UsdData class following the same patterns as existing converters (e.g. CellPackConverter). The converter:

Opens a USD stage and discovers all mesh geometry prims
Deduplicates meshes by geometry hash and normalizes them relative to their local origin (preserving pivot points set in DCC tools)
Extracts per-frame local transforms using Gf.Transform, decomposing rotation into intrinsic XYZ Euler angles compatible with the THREE.js viewer
Applies np.unwrap per-axis to remove Euler discontinuities
Supports optional animation trimming (trim_to_animation=True by default) to crop to the actual animated frame range
Supports display data overrides by name
Writes mesh geometry as .obj files alongside the .simularium output

Type of change
New feature (non-breaking change which adds functionality)
This change requires updated or new tests

Change summary:
Add USD converter for importing Universal Scene Description files
Fix USD rotation convention to match THREE.js Euler('XYZ') intrinsic order
Clean up USD converter: remove stale comments, unused imports, and improve error handling

Steps to Verify:
Install with USD support: pip install ".[usd]"
Run the test suite: pytest simulariumio/tests/converters/test_usd_converter.py
Convert a USD file manually:

from simulariumio.usd import UsdConverter, UsdData
converter = UsdConverter(UsdData(usd_file_path="my_file.usd"))
converter.save("output")
Open output.simularium in the Simularium viewer at simularium.allencell.org
Verify agents appear with correct positions, rotations, and mesh shapes across frames

Keyfiles:
simulariumio/usd/usd_converter.py -> main converter logic
simulariumio/usd/usd_data.py -> input data container
simulariumio/tests/converters/test_usd_converter.py -> test suite

dillonl added 3 commits March 30, 2026 10:55
Reads .usd/.usda/.usdc files and converts them to .simularium format
with OBJ mesh geometry output. Supports animated transforms (position
and rotation), mesh deduplication via geometry hashing, material color
extraction, and centering/scaling.

Key details:
- Rotations are converted from USD degrees to radians for the viewer
- OBJ meshes are normalized to unit sphere so viewer radius scaling works
- Mesh deduplication avoids writing redundant OBJ files
- Optional dependency: pip install simulariumio[usd]
Decompose(Z, Y, X) produced intrinsic ZYX angles which, when passed to
the viewer's THREE.js Euler('XYZ'), generated incorrect rotations (up to
1.24 quaternion error). Switched to Decompose(X, Y, Z) which uses the
same intrinsic XYZ convention as the viewer, eliminating barrel-roll
artifacts on animated meshes.

Also wrap Usd.Stage.Open in try/except for a clearer error on bad paths,
remove stale comments and unused imports from iteration.
…prove error handling

Remove class-level type annotations not used by other converters, unused
Tuple/MetaData/UnitData imports, redundant rotation comment, and replace
verbose/inaccurate convention comments with concise versions. Wrap
Usd.Stage.Open in try/except for a clearer error on invalid file paths.
@dillonl dillonl requested a review from a team as a code owner March 31, 2026 23:15
@dillonl dillonl requested review from ascibisz and toloudis and removed request for a team March 31, 2026 23:15
Copy link
Copy Markdown
Contributor

@interim17 interim17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is looking great! Thank you for your efforts.

A couple questions.

  1. At first pass running with this, the USDascii test file converted into a simularium file with 340 time steps, and my conversion of USDbinary test data resulted in a single frame (looked like the last frame of the USDascii trajectory). Would you expect them to be the same? Possible user error on my end?

  2. For CI would be good to add usd to the array on line 26 of Justfile

Comment thread simulariumio/usd/usd_converter.py
Comment thread simulariumio/usd/usd_converter.py
@dillonl
Copy link
Copy Markdown
Author

dillonl commented Apr 14, 2026

Overall this is looking great! Thank you for your efforts.

A couple questions.

  1. At first pass running with this, the USDascii test file converted into a simularium file with 340 time steps, and my conversion of USDbinary test data resulted in a single frame (looked like the last frame of the USDascii trajectory). Would you expect them to be the same? Possible user error on my end?
  2. For CI would be good to add usd to the array on line 26 of Justfile
  1. Good eye, this is expected behavior, not a bug. The two test fixtures aren't equivalent: actin_USDascii.usd has 50 keyframes over a declared 1–400 frame range, while actin_USDbinary.usd was exported as a static snapshot (start/end time code both 0.0, zero time samples). So the binary file legitimately collapses to a single frame. If you'd like, I can regenerate the binary fixture from the same animated source so they round-trip to the same trajectory, just let me know.

  2. As for the Justfile, I added usd to the install extras in the Justfile

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing all these tests! Would it be possible to add a test where trim_to_animation=True? That's one of the paths through your code that I'm not sure how to test locally, since I've never actually generated a usd file myself 😅

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good thinking, I added two tests under TestUsdTrimToAnimation. The ascii fixture declares an end time code of 400 but the last keyed frame across all agents/ops is 340, so the tests verify that trim_to_animation=True produces 340 frames (vs. 400 untrimmed) and that the kept frames contain identical positions/rotations to the untrimmed run (i.e. we're only dropping the held tail, not altering animation data).

Copy link
Copy Markdown
Contributor

@ascibisz ascibisz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything looks good to me! I was able to convert the test files to simularium file format using your converter without issue and the output files looked and behaved as I expected, nicely done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants