Converting scenes¶
Turn a captured 3D scan, an Esri Site Scan .obj export or a 3D-Tiles Gaussian splat
export, into a catalog scene: a visual world the First Person
View (FPV) vehicles fly in. The output is always one self-contained .usdz, finalized onto the
scene-asset conventions in scripts/assets/scene_root.py. That means one root Xform, the
defaultPrim and the target the runtime places start at, holding everything, including the
authored sky of dome plus sun. There is no in-code sky. The scene file, a
Universal Scene Description (USD), owns its lighting. See
what every scene file carries.
Keep the scans under NEXUS_DATA, default ~/data. The Kit container mounts that folder at
the same path it has on the host. So a path you type in step 1 is the path step 3 flies, with no
per-command -v.
1. Convert¶
Both converters need a booted Kit app. Run them from the host with plain Python: each starts the
Kit image with its own file and arguments, boots Kit there, and does its work. On a machine with no
image it pulls the image first, see RTX cameras and lidar. Run
them from any folder: the container mounts the working folder and NEXUS_DATA at their host
paths. Progress traces on stderr. Kit swallows stdout.
# photogrammetry OBJ (a dir with .obj + .mtl + textures) -> one .usdz
uv run python scripts/assets/obj_to_usd.py \
"$NEXUS_DATA/scans/obj/My Site" --out "$NEXUS_DATA/scans/my_site_obj.usdz"
# 3D-Tiles Gaussian splat (dir with tileset.json) -> one .usdz
uv run python scripts/assets/site_scan_splat.py \
"$NEXUS_DATA/scans/gsp/My Site" --out "$NEXUS_DATA/scans/my_site_splat.usdz" \
--max-geometric-error 2.5
--max-geometric-error picks the splat Level Of Detail (LOD), where 0 is the finest level, the
leaves. The splat converter traces the latitude, longitude, and altitude of the scene origin.
Keep it for step 2. Conversion normalizes the scene, to Z-up meters with the XY origin at the
center of the scan's bounding box, but bakes in no start point.
2. Pick the start on the host¶
uv run python scripts/assets/spawn_site.py "$NEXUS_DATA/scans/my_site_splat.usdz" \
--origin <lat>,<lon>,<alt> # the converter's "scene origin" trace
Prints the catalog start:, a flat open-terrain spot near the scene center, and, with --origin,
the scene's geodetic origin measured at that start, as the command that authors it. Both are
advisory: eyeball, tweak numbers, no reconversion. A mesh carries no geodesy, so reuse the sibling
splat's origin trace.
Then author the origin on the scene's PhysicsScene, with the values step 2 printed. A run fails
on a scene that authors none:
uv run python scripts/assets/author_scene_origin.py "$NEXUS_DATA/scans/my_site_splat.usdz" \
"$NEXUS_DATA/scans/my_site.usdz" --origin <lat>,<lon>,<alt>
3. Preview-fly it unregistered¶
uv run nexus run --vehicle astro_max_fpv \
--scene "$NEXUS_DATA/scans/my_site.usdz" --log
A local --scene path flies the file in place, from the scene's own origin: the Kit container reads
it at its host path. Inspect the
.rrd in Rerun, and check fpvcam.
4. Publish¶
uv run python scripts/assets/prepare_asset_upload.py --scene "$NEXUS_DATA/scans/my_site.usdz"
Prints the content-addressed upload key, public/assets/usd/scenes/<name>-<sha256>.usdz, and the
catalog.yaml snippet. Upload it by hand, then add the scene row with start from step 2.
Done: --scene my_site flies it anywhere.