Skip to content

Latest commit

 

History

History
260 lines (193 loc) · 10.7 KB

File metadata and controls

260 lines (193 loc) · 10.7 KB

Using The Exporter

UntoldEngine ships two user-facing exporter commands in the scripts/ folder at the repo root:

  • export-untold
  • export-untold-tiles

These wrappers launch Blender in background mode and run the Python exporters for you. Users should run the shell wrappers, not the raw Blender commands.

Prerequisites

Blender must be installed.

The wrappers resolve Blender in this order:

  1. --blender /path/to/Blender
  2. BLENDER_BIN=/path/to/Blender
  3. /Applications/Blender.app/Contents/MacOS/Blender
  4. blender on PATH

If Blender cannot be found, the wrapper prints an install message and exits.

Export A Single Asset

Use export-untold (found in scripts/) to convert one USD or USDZ asset into one .untold runtime file.

Basic usage:

./scripts/export-untold \
  --input /path/model.usdz \
  --output /path/model.untold

Common options:

  • --input <path>: required source .usd, .usda, .usdc, or .usdz
  • --output <path>: required destination .untold
  • --file-type <tile|lod|hlod|shared|animation>: optional, defaults to tile
  • --mesh-name <name>: optional, export only one mesh from a multi-mesh asset
  • --ConvertOrientation: optional, convert the export into engine space
  • --source-orientation <blender-native|engine-oriented>: optional, defaults to blender-native
  • --validate: optional, also writes <name>.validation.json
  • --compress-geometry: optional, LZ4-compress vertex and index chunks (requires pip install lz4)
  • --animation: optional, export animation clips only — no mesh geometry is written
  • --blender <path>: optional wrapper-level Blender override

Example:

./scripts/export-untold \
  --input GameData/Models/robot/robot.usdz \
  --output GameData/Models/robot/robot.untold \
  --ConvertOrientation \
  --source-orientation blender-native \
  --validate

Expected output:

  • robot.untold
  • Textures/... beside the .untold file if the asset uses textures
  • robot.validation.json only when --validate is passed

Export A Scene Into Tiles

Use export-untold-tiles (found in scripts/) to partition a USD or USDZ scene into tile payloads and generate a manifest JSON file.

Basic usage:

./scripts/export-untold-tiles \
  --input /path/scene.usdz \
  --output-dir /path/tile_exports \
  --tile-size-x 25 \
  --tile-size-y 10000 \
  --tile-size-z 25

Common options:

  • --input <path>: required source .usd, .usda, .usdc, or .usdz
  • --output-dir <path>: required destination directory for tile payloads
  • --tile-size-x <number>: optional tile width in world units
  • --tile-size-y <number>: optional tile height in world units, defaults to 10000
  • --tile-size-z <number>: optional tile depth in world units
  • --auto-tile-size: optional automatic tile sizing
  • --generate-hlod: optional HLOD generation
  • --generate-lod: optional per-tile LOD generation
  • --dry-run: optional planning pass without writing payload files
  • --write-manifest-in-dry-run: optional manifest write during dry run
  • --visible-only: optional export only visible meshes
  • --all-meshes: optional include hidden meshes
  • --debug-aabb-only: optional emit debug AABB payloads instead of geometry
  • --quadtree: optional partition tiles using a quadtree instead of a uniform grid
  • --kdtree: optional partition tiles using a KD-tree instead of a quadtree (inline annotation only). Splits each floor's XY plane on the longer axis at the median object center, producing better-balanced tiles in scenes where geometry is unevenly distributed. Produces partitioning_mode: "kdtree_floor" in the manifest. Ignored if the input is pre-annotated (quadtree metadata takes precedence)
  • --scene-profile <auto|indoor|outdoor>: optional streaming radius profile, defaults to auto. Radii are always proportional to scene size — no fixed distances to hand-tune. Use outdoor for cities, terrain, and large exterior scenes if auto-detection misses.
  • --tier-radius <Tier=stream,unload[,priority]>: optional quadtree semantic-tier radius override in world units. May be repeated.
  • --floor-count <number>: optional number of vertical floors to split each tile into (for quadtree mode)
  • --floor-band-height <number>: optional per-floor height in world units (overrides auto-detection from scene Z extent)
  • --parallel-workers <number>: optional number of parallel Blender worker processes (0 = auto-detect CPU count, 1 = sequential)
  • --compress-geometry: optional LZ4-compress vertex and index chunks in every exported tile payload (requires pip install lz4)
  • --blender <path>: optional wrapper-level Blender override

Example:

./scripts/export-untold-tiles \
  --input GameData/Models/dungeon/dungeon.usdz \
  --output-dir GameData/Models/dungeon/tile_exports \
  --tile-size-x 25 \
  --tile-size-y 10000 \
  --tile-size-z 25 \
  --generate-hlod \
  --generate-lod

Dry-run example:

./scripts/export-untold-tiles \
  --input GameData/Models/dungeon/dungeon.usdz \
  --output-dir GameData/Models/dungeon/tile_exports \
  --tile-size-x 25 \
  --tile-size-y 10000 \
  --tile-size-z 25 \
  --dry-run \
  --write-manifest-in-dry-run

Quadtree Tier Radius Overrides

Quadtree exports assign each tile group to a semantic tier:

  • ExteriorShell
  • StructuralInterior
  • RoomContents
  • FineProps

--scene-profile chooses default stream/unload bands for these tiers. Use --tier-radius when a scene needs tighter or wider bands than the selected profile.

Syntax:

--tier-radius TierName=streaming_radius,unload_radius[,priority]

Example:

./scripts/export-untold-tiles \
  --input GameData/Models/building/building.usdz \
  --output-dir GameData/Models/building/tile_exports \
  --quadtree \
  --scene-profile indoor \
  --tier-radius ExteriorShell=55,80,15 \
  --tier-radius StructuralInterior=10,18,12 \
  --tier-radius RoomContents=4,7,8 \
  --tier-radius FineProps=1.5,3,5

ExteriorShell=55,80,15 means:

  • 55: streaming_radius in world units. The tile becomes eligible to load when the camera enters this distance band.
  • 80: unload_radius in world units. Once loaded, the tile stays resident until the camera moves beyond this distance.
  • 15: optional load priority. Higher values are considered more important when multiple tile candidates compete for load slots.

unload_radius must be greater than streaming_radius. The gap is the hysteresis band that prevents rapid load/unload oscillation near the boundary.

Expected output layout:

  • dungeon.json beside the tile payload directory
  • tile_exports/tile_*.untold
  • optional HLOD and LOD .untold files in tile_exports/
  • tile_exports/Textures/... for staged textures

The manifest stores relative runtime paths so it remains portable across machines, repos, and app bundles.

KD-tree Partitioning

Use --kdtree instead of --quadtree when geometry is unevenly distributed across the scene floor — for example, when most objects cluster in corridors or specific rooms while other areas are sparse. The KD-tree splits each floor on the longer axis at the median object center, producing tiles that reflect actual geometry density rather than equal-area subdivisions.

./scripts/export-untold-tiles \
  --input GameData/Models/building/building.usdz \
  --output-dir GameData/Models/building/tile_exports \
  --kdtree \
  --scene-profile indoor \
  --floor-count 10

The --tier-radius and --scene-profile flags work identically for --kdtree and --quadtree. The manifest will contain "partitioning_mode": "kdtree_floor" and tile node IDs use the F{nn}_K_... naming convention (e.g. "F02_K_0_1_0").

When to choose KD-tree vs. quadtree:

Quadtree KD-tree
Geometry distribution Uniform across floor Clustered in sub-regions
Tile balance Equal-area (can produce empty tiles) Object-count balanced
Hierarchy culling Yes Yes
Pre-annotated input (phase12) Yes No (inline annotation only)

Selective Merging With The NM_ Prefix

When MERGE_BY_MATERIAL is enabled (the default), objects that share the same material within a tile are joined into a single mesh entity before export. This reduces draw calls significantly, but means multiple original objects collapse into one exported entity — losing their individual names.

If you need certain objects to remain as separate identifiable entities (for example, to support tap-to-select workflows or per-object JSON lookups at runtime), prefix their name in Blender with NM_.

Objects whose name starts with NM_ are excluded from the merge step and exported individually, preserving their original name in the .untold file. All other objects are still merged normally.

Example naming in Blender:

  • NM_Pipe_001 — exported as its own entity, name survives into .untold
  • NM_LightFixture_A — exported as its own entity
  • Wall_North — merged with other same-material walls, one entity for the group
  • Door_Main — merged with same-material doors

This lets you keep background geometry (walls, floors, ceilings) optimized while still being able to identify and interact with specific objects at runtime:

To change the prefix or disable selective merging, edit NO_MERGE_PREFIX at the top of scripts/tilestreamingpartition.py. Set it to "" to merge all objects regardless of name.

At runtime, NM_ objects default to .selectableGeometry and .preserveIdentity scene channels. Regular render/streaming geometry defaults to .contextGeometry. This lets an app hide context geometry with setSceneChannelVisible(.contextGeometry, false) while keeping NM_ objects visible and selectable. See Scene Channels.

Optimization Workflows

After exporting assets, use Optimizations for optional workflows such as ASTC texture compression and LZ4 geometry compression.

Loading The Result In The Engine

Single asset:

setEntityMeshAsync(
    entityId: entityId,
    filename: "robot",
    withExtension: "untold"
)

Tiled scene:

let sceneRoot = createEntity()
setEntityName(entityId: sceneRoot, name: "dungeon")
setEntityStreamScene(entityId: sceneRoot, manifest: "dungeon", withExtension: "json")

The manifest should live next to the tile payload directory. Tile, HLOD, LOD, and shared-bucket payloads are resolved relative to the manifest file.

Notes

  • .untold tile payloads participate in the current tiled streaming architecture, including tile-level load/unload, remote download + cache, per-tile LOD/HLOD, and large-tile OCC sub-mesh streaming when the runtime classifies a tile into the OOC path.
  • The Python files in scripts/ are implementation details. The recommended user entry points are the shell wrappers in the same folder.