Log inGet started

modelImport

Updated 5 September 2026

The modelImport namespace — 25 functions.

globals/modelImport/decompose

modelImport.decompose(bytes: buffer | string, format: string) -> string

Parse raw model bytes on a background thread. format is the real source extension ("fbx", "obj", "dae", "gltf", "glb", "stl", "ply", "3ds", …), forwarded to assimp as the format hint. Returns a promise handle: task.await it, then read the data with result(handle).

Parameters

  • bytes buffer | string — Raw model file bytes (from vfs.readAsync).
  • format string — The source file extension (lowercase, no dot).

Returns string — Promise handle for task.await.

local h = modelImport.decompose(bytes, "obj"); task.await(h)

globals/modelImport/decomposeFiles

modelImport.decomposeFiles(files: { ModelFile }, mainName: string) -> string

Parse a model plus its companion files on a background thread, so assimp resolves the model's external references (a .gltf's external .bin and image files, an .obj's .mtl colors/textures, MD5's .md5anim, …). files is an array of { name = basename, bytes = <bytes> } that MUST include the model file itself; mainName is that file's basename. Returns a promise handle: task.await it, then read the data with result(handle) — the same shape decompose produces.

Parameters

  • files { ModelFile } — Array of { name, bytes }: the model file plus its companions.
  • mainName string — Basename of the model file to import (one of files' names).

Returns string — Promise handle for task.await.

local h = modelImport.decomposeFiles(files, "CesiumMilkTruck.gltf"); task.await(h)

globals/modelImport/extractAnimation

modelImport.extractAnimation(sourcePath: string, clipName: string) -> string

Read a model source file and extract one animation clip to its .zanim payload, stashed for retrieval. The source extension decides the parser, so this is format-agnostic. Returns a promise handle: task.await it, then extractAnimationResult(handle) returns the bytes.

Parameters

  • sourcePath string — VFS path to the source model file.
  • clipName string — Clip name as returned by result(handle).animations[i].name.

Returns string — Promise handle for task.await.

globals/modelImport/extractAnimationResult

modelImport.extractAnimationResult(handle: string) -> string?

After awaiting an extractAnimation handle, return the extracted .zanim bytes (binary-safe), consuming them. The bytes to hand to asset.create("animation", name, { bytes }). Returns nil on failure or if already taken.

Parameters

  • handle string — Promise handle from extractAnimation.

Returns string? — The clip's .zanim payload bytes, or nil.

globals/modelImport/result

modelImport.result(handle: string) -> any?

Read the decomposed model after decompose's handle has been awaited. Every format decomposes into the same shape, so this is format-agnostic. Runs on the main thread; consumes the stored result.

Parameters

  • handle string — Promise handle from decompose.

Returns any?{ nodes, meshes, materials, textures, animations, hasSkeleton, skeletonRootNode?, skeleton? }, or nil.

globals/modelImport/retryHandle

modelImport.retryHandle(makeHandle: () -> any, retries: number?, yield: (() -> ())?) -> string?

Call makeHandle — which returns a promise-handle string, or a falsy value on a transient failure (e.g. a source read that raced a pending write during a parallel import) — up to retries + 1 times, yielding via yield between attempts so a pending write can land before the next try. Returns the handle string once one is produced, or nil when every attempt failed. Callers task.await the result only when it is non-nil, so a transient miss never reaches task.await as a non-string.

Parameters

  • makeHandle () -> any — Returns a promise-handle string, or a falsy value on failure.
  • retries number (optional) — Extra attempts after the first (default 3).
  • yield (() -> ()) (optional) — Called between attempts (default task.wait).

Returns string? — The handle string, or nil when every attempt failed.

globals/modelImport/rigFromMeshSkin

modelImport.rigFromMeshSkin(meshBytes: buffer | string) -> string?

Lift the skeleton out of a skinned .mesh (ZMSH) payload and return it as a .rig JSON document: bones (hierarchy, rest pose, inverse-bind), the auto-derived humanoid profile, and the humanoid classification. The source rig a skinned mesh's clips retarget through. Returns nil when the bytes are not a mesh or carry no skin.

Parameters

  • meshBytes buffer | string — Raw ZMSH mesh bytes carrying a skin.

Returns string?.rig JSON document, or nil.

globals/modelImport/rigFromSkeleton

modelImport.rigFromSkeleton(skeleton: AnimationSkeleton) -> string?

Build a .rig JSON document from the skeleton an animation-only file was authored on — result(handle).skeleton, the bones its clips drive with their local rest transforms. Forward kinematics over the locals resolves globals + inverse-bind; the humanoid profile + classification are derived as for rigFromMeshSkin. The source rig a standalone clip retargets through. Returns nil on malformed input.

Parameters

  • skeleton AnimationSkeleton{ names, parents, locals } (a decompose result's skeleton).

Returns string?.rig JSON document, or nil.

local rigJson = modelImport.rigFromSkeleton(data.skeleton)

modules/modelImport/README

require("@builtin/modules/api/engine/modelImport") -- modelImport (also available as global 'modelImport')

The model-import pipeline: decompose any assimp-supported 3D model (fbx, obj, dae, gltf, glb, stl, ply, 3ds, …) into meshes, materials, textures, animation clips and a node graph; extract a single clip to its .zanim payload; and derive a .rig from a skinned mesh or from an animation-only file's driven skeleton. Public Luau surface over the __model and __rig Internal FFI namespaces.

Usage: local modelImport = require("@builtin/modules/api/engine/modelImport") Also available as global: modelImport

modules/modelImport/decompose

decompose(bytes: buffer | string, format: string): string

Parse raw model bytes on a background thread. format is the real source extension ("fbx", "obj", "dae", "gltf", "glb", "stl", "ply", "3ds", …), forwarded to assimp as the format hint. Returns a promise handle: task.await it, then read the data with result(handle).

Parameters

  • bytes buffer | string — Raw model file bytes (from vfs.readAsync).
  • format string — The source file extension (lowercase, no dot).
local h = modelImport.decompose(bytes, "obj"); task.await(h)

modules/modelImport/decomposeFiles

decomposeFiles(files: { ModelFile }, mainName: string): string

Parse a model plus its companion files on a background thread, so assimp resolves the model's external references (a .gltf's external .bin and image files, an .obj's .mtl colors/textures, MD5's .md5anim, …). files is an array of { name = basename, bytes = <bytes> } that MUST include the model file itself; mainName is that file's basename. Returns a promise handle: task.await it, then read the data with result(handle) — the same shape decompose produces.

Parameters

  • files { ModelFile } — Array of { name, bytes }: the model file plus its companions.
  • mainName string — Basename of the model file to import (one of files' names).
local h = modelImport.decomposeFiles(files, "CesiumMilkTruck.gltf"); task.await(h)

modules/modelImport/extractAnimation

extractAnimation(sourcePath: string, clipName: string): string

Read a model source file and extract one animation clip to its .zanim payload, stashed for retrieval. The source extension decides the parser, so this is format-agnostic. Returns a promise handle: task.await it, then extractAnimationResult(handle) returns the bytes.

Parameters

  • sourcePath string — VFS path to the source model file.
  • clipName string — Clip name as returned by result(handle).animations[i].name.

modules/modelImport/extractAnimationResult

extractAnimationResult(handle: string): string?

After awaiting an extractAnimation handle, return the extracted .zanim bytes (binary-safe), consuming them. The bytes to hand to asset.create("animation", name, { bytes }). Returns nil on failure or if already taken.

Parameters

  • handle string — Promise handle from extractAnimation.

modules/modelImport/result

result(handle: string): any?

Read the decomposed model after decompose's handle has been awaited. Every format decomposes into the same shape, so this is format-agnostic. Runs on the main thread; consumes the stored result.

Parameters

  • handle string — Promise handle from decompose.

modules/modelImport/retryHandle

retryHandle(makeHandle: () -> any, retries: number?, yield: (() -> ())?): string?

Call makeHandle — which returns a promise-handle string, or a falsy value on a transient failure (e.g. a source read that raced a pending write during a parallel import) — up to retries + 1 times, yielding via yield between attempts so a pending write can land before the next try. Returns the handle string once one is produced, or nil when every attempt failed. Callers task.await the result only when it is non-nil, so a transient miss never reaches task.await as a non-string.

Parameters

  • makeHandle () -> any — Returns a promise-handle string, or a falsy value on failure.
  • retries number? (optional) — Extra attempts after the first (default 3).
  • yield (() -> ())? (optional) — Called between attempts (default task.wait).

modules/modelImport/rigFromMeshSkin

rigFromMeshSkin(meshBytes: buffer | string): string?

Lift the skeleton out of a skinned .mesh (ZMSH) payload and return it as a .rig JSON document: bones (hierarchy, rest pose, inverse-bind), the auto-derived humanoid profile, and the humanoid classification. The source rig a skinned mesh's clips retarget through. Returns nil when the bytes are not a mesh or carry no skin.

Parameters

  • meshBytes buffer | string — Raw ZMSH mesh bytes carrying a skin.

modules/modelImport/rigFromSkeleton

rigFromSkeleton(skeleton: AnimationSkeleton): string?

Build a .rig JSON document from the skeleton an animation-only file was authored on — result(handle).skeleton, the bones its clips drive with their local rest transforms. Forward kinematics over the locals resolves globals + inverse-bind; the humanoid profile + classification are derived as for rigFromMeshSkin. The source rig a standalone clip retargets through. Returns nil on malformed input.

Parameters

  • skeleton AnimationSkeleton{ names, parents, locals } (a decompose result's skeleton).
local rigJson = modelImport.rigFromSkeleton(data.skeleton)

typed/builtin//modules/api/engine/modelImport/modelImport/decompose

modelImport.decompose(bytes: buffer | string, format: string) -> string

Parse raw model bytes on a background thread. format is the real source extension ("fbx", "obj", "dae", "gltf", "glb", "stl", "ply", "3ds", …), forwarded to assimp as the format hint. Returns a promise handle: task.await it, then read the data with result(handle).

Parameters

  • bytes buffer | string — Raw model file bytes (from vfs.readAsync).
  • format string — The source file extension (lowercase, no dot).

Returns string — Promise handle for task.await.

local h = modelImport.decompose(bytes, "obj"); task.await(h)

typed/builtin//modules/api/engine/modelImport/modelImport/decomposeFiles

modelImport.decomposeFiles(files: { ModelFile }, mainName: string) -> string

Parse a model plus its companion files on a background thread, so assimp resolves the model's external references (a .gltf's external .bin and image files, an .obj's .mtl colors/textures, MD5's .md5anim, …). files is an array of { name = basename, bytes = <bytes> } that MUST include the model file itself; mainName is that file's basename. Returns a promise handle: task.await it, then read the data with result(handle) — the same shape decompose produces.

Parameters

  • files { ModelFile } — Array of { name, bytes }: the model file plus its companions.
  • mainName string — Basename of the model file to import (one of files' names).

Returns string — Promise handle for task.await.

local h = modelImport.decomposeFiles(files, "CesiumMilkTruck.gltf"); task.await(h)

typed/builtin//modules/api/engine/modelImport/modelImport/extractAnimation

modelImport.extractAnimation(sourcePath: string, clipName: string) -> string

Read a model source file and extract one animation clip to its .zanim payload, stashed for retrieval. The source extension decides the parser, so this is format-agnostic. Returns a promise handle: task.await it, then extractAnimationResult(handle) returns the bytes.

Parameters

  • sourcePath string — VFS path to the source model file.
  • clipName string — Clip name as returned by result(handle).animations[i].name.

Returns string — Promise handle for task.await.

typed/builtin//modules/api/engine/modelImport/modelImport/extractAnimationResult

modelImport.extractAnimationResult(handle: string) -> string?

After awaiting an extractAnimation handle, return the extracted .zanim bytes (binary-safe), consuming them. The bytes to hand to asset.create("animation", name, { bytes }). Returns nil on failure or if already taken.

Parameters

  • handle string — Promise handle from extractAnimation.

Returns string? — The clip's .zanim payload bytes, or nil.

typed/builtin//modules/api/engine/modelImport/modelImport/result

modelImport.result(handle: string) -> any?

Read the decomposed model after decompose's handle has been awaited. Every format decomposes into the same shape, so this is format-agnostic. Runs on the main thread; consumes the stored result.

Parameters

  • handle string — Promise handle from decompose.

Returns any?{ nodes, meshes, materials, textures, animations, hasSkeleton, skeletonRootNode?, skeleton? }, or nil.

typed/builtin//modules/api/engine/modelImport/modelImport/retryHandle

modelImport.retryHandle(makeHandle: () -> any, retries: number?, yield: (() -> ())?) -> string?

Call makeHandle — which returns a promise-handle string, or a falsy value on a transient failure (e.g. a source read that raced a pending write during a parallel import) — up to retries + 1 times, yielding via yield between attempts so a pending write can land before the next try. Returns the handle string once one is produced, or nil when every attempt failed. Callers task.await the result only when it is non-nil, so a transient miss never reaches task.await as a non-string.

Parameters

  • makeHandle () -> any — Returns a promise-handle string, or a falsy value on failure.
  • retries number (optional) — Extra attempts after the first (default 3).
  • yield (() -> ()) (optional) — Called between attempts (default task.wait).

Returns string? — The handle string, or nil when every attempt failed.

typed/builtin//modules/api/engine/modelImport/modelImport/rigFromMeshSkin

modelImport.rigFromMeshSkin(meshBytes: buffer | string) -> string?

Lift the skeleton out of a skinned .mesh (ZMSH) payload and return it as a .rig JSON document: bones (hierarchy, rest pose, inverse-bind), the auto-derived humanoid profile, and the humanoid classification. The source rig a skinned mesh's clips retarget through. Returns nil when the bytes are not a mesh or carry no skin.

Parameters

  • meshBytes buffer | string — Raw ZMSH mesh bytes carrying a skin.

Returns string?.rig JSON document, or nil.

typed/builtin//modules/api/engine/modelImport/modelImport/rigFromSkeleton

modelImport.rigFromSkeleton(skeleton: AnimationSkeleton) -> string?

Build a .rig JSON document from the skeleton an animation-only file was authored on — result(handle).skeleton, the bones its clips drive with their local rest transforms. Forward kinematics over the locals resolves globals + inverse-bind; the humanoid profile + classification are derived as for rigFromMeshSkin. The source rig a standalone clip retargets through. Returns nil on malformed input.

Parameters

  • skeleton AnimationSkeleton{ names, parents, locals } (a decompose result's skeleton).

Returns string?.rig JSON document, or nil.

local rigJson = modelImport.rigFromSkeleton(data.skeleton)
  • api
  • reference