Importing 3-D Models

Model3D loads a 3-D model file – geometry, UVs, textures, PBR materials, node hierarchy and rigid animation – and gives you an ordinary Algan Mob.

from algan import *

model = Model3D('dragon.glb', fit_to_size=2.0).scale(3).spawn()
with Seq(runtime=4, easing=easings.identity):
    model.rotate(360, UP)

Scene.save_video()

File Formats

Formats

Requirements

.glb, .gltf, .obj, .ply, .stl, .dae, .off

Loaded through trimesh, which is pure Python. Works with a standard Algan install.

.fbx

Loaded through pyassimp, which needs the native assimp library as well as the Python bindings.

glTF / glB is the format to prefer: it is the best supported, needs no extra install, and carries PBR materials and embedded textures.

To use FBX, install the extra and the native library:

pip install "algan[fbx]"

pyassimp is only a ctypes wrapper, so the native assimp library must be installed separately – conda install -c conda-forge assimp, apt install libassimp5, brew install assimp, or an assimp*.dll on PATH on Windows. Algan raises an error naming the missing piece if it cannot find it.

Model paths are resolved against the working directory and then your script’s directory, like every other Algan asset.

Scale and Position

Model files use wildly inconsistent unit scales – one file’s “1” is a metre, another’s is a centimetre. fit_to_size recentres the model and uniformly scales it so its bounding-box diagonal is that many world units, which makes an unfamiliar asset usable immediately:

# Predictable size regardless of the file's units.
model = Model3D('asset.glb', fit_to_size=3).spawn()

# Or take the file's own scale and adjust by hand.
model = Model3D('asset.glb').scale(0.01).spawn()

After that it is a normal Mob: scale(), move_to(), rotate() and scale_to_height() all behave as usual.

Materials and Textures

By default Algan applies each imported material’s PBR parameters – metalness, roughness, emissive – as a MeshStandardMaterial, so imported meshes shade with Cook-Torrance GGX and respond correctly to your lighting. Diffuse texture maps and tangent-space normal maps are loaded and applied too.

Argument

Effect

pbr_materials

Apply each material’s PBR parameters. Default True; False keeps Algan’s default lit shader.

load_textures

Load diffuse texture maps. Default True; False (or a failed load) falls back to the material’s flat base color.

normal_maps

Apply tangent-space normal maps. Default True. Requires per-vertex UVs.

smooth_normals

Use the mesh’s authored per-vertex normals. Default True; False derives flat per-face normals at render time, for a low-poly look.

Note

Batches carrying a normal map are routed automatically to the general wavefront tracer, which supports per-fragment normal perturbation. That is a performance consideration, not something you have to configure – but see Performance and Quality if an imported model renders more slowly than you expect.

Because the materials land on the meshes as ordinary Algan materials, their properties are animatable attributes like any other:

model = Model3D('robot.glb', fit_to_size=2.0).spawn()
with Seq(runtime=3):
    model.roughness = 0.1      # polish the whole model

Working With Parts

An imported model keeps its node hierarchy, so you can reach in and animate a single part:

model = Model3D('robot.glb', fit_to_size=2.0).spawn()

print(model.node_names)              # what's in the file

arm = model.get_part('LeftArm')      # one TriangleMesh, or a list of them
with Seq(runtime=2):
    arm.rotate(45, OUT)
    arm.color = RED

node_names lists the nodes that carry geometry, and get_part() returns the mesh Mob (or list of them) for a named node. A part is a normal Mob, so everything in Basic Animations applies – and because it is a child of the model, moving the model still carries it along (see Grouping Mobs).

Playing Baked Animations

If the file carries animation clips, play_animation() records one onto Algan’s timeline:

model = Model3D('walking.glb', fit_to_size=2.0).spawn()

print(model.animation_names)         # available clips

model.play_animation('Walk', runtime=4, loop=2)

Argument

Meaning

name

Which clip. Defaults to the first one.

runtime

Seconds per loop. Defaults to the clip’s authored runtime.

loop

How many times to repeat it.

fps

Sampling rate used when baking. Higher is smoother for fast rotation, because poses are interpolated linearly in between. Default 30.

easing

Easing. Defaults to easings.identity, which is what you want – a walk cycle should not ease in and out.

Because the clip is recorded on the timeline like any other animation, it composes with animation contexts – you can play a walk cycle while simultaneously moving the model across the frame:

with Sync(runtime=4):
    model.play_animation('Walk', runtime=4)
    model.move(RIGHT * 6)

Important

Rigid node animation only. Algan bakes each clip by evaluating the animated node transforms and composing them down the hierarchy, so a part that translates, rotates or scales plays back correctly. Skeletal skinning – where vertices are weighted to several bones and deform between them – is not applied. A model animated by moving rigid parts works; one animated by deforming a continuous skinned mesh will move at the node level only.

precompute_animation() exposes the same computation without touching the scene, returning the sample times and per-frame geometry, if you want to inspect or post-process the poses yourself.

Troubleshooting

Symptom

Likely cause

Nothing appears

The model’s units. Try fit_to_size=2.

Faceted where it should be smooth

The file has no authored normals; they are being derived per face.

Untextured / flat color

A texture failed to load, or the mesh has no UVs.

Black or very dark

A metallic material with nothing to reflect. Add an environment map – see Lighting and Shadows.

Very slow

Triangle count, or normal maps forcing the wavefront tracer. See Performance and Quality.

An FBX raises on load

The native assimp library is missing. The error message names what to install.

See Also