How the supply-chain conveyor video was made

A 20-second 1080p animation: one conveyor belt runs through a supplier, an assembly plant and a distributor, and gold coins pour out of the distributor at the end. Everything in the scene is generated by Python scripts running inside Blender. Only photographic textures and a sky image were downloaded. No 3D models were downloaded and nothing was modelled by hand.

opening aerial frame
Frame 1: aerial opening shot
belt 2 with robots
Frame 330: robots on belt 2
coins pouring out of distributor
Frame 480: coins pour out of the distributor
6,092lines of Python (10 files)
0downloaded 3D models
1 + 8downloaded sky HDRI + texture sets (CC0, 180 MB)
6,305objects in the scene
163materials
1,400physics-simulated coins
1,167procedural trees
600frames, 1920×1080, 30 fps
~30 sper frame on an M5 Pro GPU (~5 h total)

Pipeline

1 · Download

fetch_assets.py

Python standard library (urllib, json) calls the Poly Haven API and saves the sky HDRI and 8 texture sets.

→ assets/ (gitignored)
→
2 · Build

main.py in Blender

Headless Blender runs the script. It generates every object, material, animation and the coin physics. Takes 42 s.

→ supply_chain.blend (29 MB)
→
3 · Verify

stills + contact sheets

Low-resolution test frames (960×540, 24 samples) at chosen frame numbers, tiled with ffmpeg and inspected after each change.

→ output/stills/
→
4 · Render

Cycles on Metal GPU

The saved .blend is rendered with gpu.py to switch on the GPU. 64 samples, denoised, with motion blur and depth of field.

→ 600 PNG frames
→
5 · Encode

ffmpeg

The PNG frames are encoded as H.264 (libx264, CRF 16, yuv420p) at 30 fps.

→ .mp4

What was downloaded and what was made from scratch

Downloaded Photographic inputs only

  • Sky: Poly Haven HDRI Kloofendal 48d Partly Cloudy (Pure Sky), 4K EXR. It provides the sky, the sunlight and the ambient light. The sky is rotated so the sun comes from the front-left.
  • 8 PBR texture sets from Poly Haven at 2K: asphalt_02, concrete_floor_02, gravel_concrete_03, red_brick_03, corrugated_iron_02, metal_plate, white_plaster_02, leafy_grass. The scene uses the color, roughness and height maps; the normal, AO and ARM maps were downloaded but are not used.
  • Fonts: none were downloaded. The 3D text uses Arial Black, Arial Bold and Arial Rounded Bold, which come with macOS.

All Poly Haven assets are CC0 (public domain).

Made from scratch in Python Everything else

  • All geometry: three buildings with interiors visible through windows, silos, chimney, gantry crane, trucks, forklifts, cars, workers, lamp posts, fences, pallets, the conveyor, gears, cable spools, battery packs, robots, coins, trees, hills and the road. Built from primitives (boxes, cylinders, tubes), with bmesh where custom meshes were needed.
  • Materials: 163 shader-node materials. Paints, metals, glass, emissive signs, hazard stripes, gold, foliage and smoke are fully procedural. Brick, concrete, asphalt, grass and cladding use the downloaded photos.
  • Animation: belt texture scroll, cargo movement, robot head turns and blinks, the robot arm, spinning rollers, the coin-door light and the camera path.
  • Physics: 1,400 rigid-body coins simulated by Blender's Bullet physics and recorded as keyframes.
  • Effects: the chimney smoke (a noise-driven volume shader) and the distant hills with haze.
belt items

Belt items

Gear, copper spool, battery pack and robot. props.py builds them from primitives, and the belt carries copies of these templates.

procedural trees

Procedural trees

A lumpy blob canopy covered with individual leaf cards by Geometry Nodes (trees.py). Six templates are reused 1,167 times.

belt 1

Belt 1 in the final render

Frame 200: parts moving from the supplier to the assembly plant.

Tools and libraries

Tool / libraryKindUsed for
Blender 5.2.2 LTSappThe whole 3D scene. Runs headless (blender -b) with no UI; nothing was built by clicking.
bpyBlender Python APICreating objects, materials, node trees, modifiers, keyframes, lights, the camera and render settings.
bmeshBlender Python APICustom meshes: tree canopies, leaf cards, fence panels and other shapes that simple primitives don't cover.
mathutils, bpy_extras.anim_utilsBlender Python APIVectors and matrices; writing the recorded coin motion straight into F-curves.
Geometry NodesBlender featureScattering leaves over tree canopies, then merging them into a single mesh.
Shader nodes (Principled BSDF, Volume, Noise, Attribute)Blender featureAll materials, the smoke plume, the hill haze and the scrolling belt chevrons.
Rigid Body (Bullet)Blender featureCoin physics: they collide with the floor, the back wall and each other.
Cycles + MetalrendererPath-traced rendering on the Apple M5 Pro 20-core GPU.
OpenImageDenoisebundled with BlenderRemoves noise, so 64 samples per pixel are enough.
AgX (Medium High Contrast)color managementMaps bright sunlight and shiny gold to screen colors without clipping.
Python stdlib: math, random, urllib, json, importlibstandard librarySeeded randomness (reproducible placement), asset download, loading the building modules.
Poly Haven APIweb sourceSource of the sky HDRI and textures (api.polyhaven.com).
ffmpeg 9.0.1 (libx264)CLITiling test stills into contact sheets; encoding the final MP4.

No Blender add-ons, no pip packages, no game engine and no external 3D tools were used.

Code map

FileLinesWhat it doesWritten by
scene/common.py672Shared toolkit: scene reset, primitive builders (box, cyl, tube, text3d, railing, ladder), texture materials (tex), procedural materials (pbr), a shared material library, render and sky setup.main agent
scene/props.py362Belt items (gear, spool, battery, robot), the coin, and the rooftop title sign.main agent
scene/trees.py189Round and conifer trees with Geometry Nodes leaves.main agent
scene/b_supplier.py1,182IRONVALE METALWORKS: steel foundry.subagent 1
scene/b_assembly.py1,243APEX ROBOTICS: assembly plant.subagent 2
scene/b_distributor.py1,296SWIFTLINE DISTRIBUTION: warehouse and coin door.subagent 3
scene/main.py1,037Builds the whole scene: ground, road, trees, hills, conveyor, cargo, titles, smoke, robot arm, coins and physics, camera. Also the command-line options for test renders.main agent
scene/gpu.py10Switches Cycles to the Metal GPU when rendering the saved .blend.main agent
tools/fetch_assets.py32Downloads the HDRI and textures.main agent
tools/preview_building.py69Renders one building alone from 8–10 fixed viewpoints for checking.main agent

Scene build order

A single run of blender -b -P scene/main.py executes these steps in order. On an empty scene the whole build takes 42 seconds.

  1. Reset and render settings. Cycles, 1920×1080, 64 samples, adaptive sampling, OIDN, AgX, motion blur (shutter 0.35), persistent data.
  2. Sky. Loads the HDRI as world lighting and rotates it so the sun sits front-left of the buildings. There is no separate sun lamp.
  3. Buildings. Imports the three modules. Each builds around its own origin, then moves to x = −40, 0 and +40 m. about 5 s
  4. Ground. 1.6 km of grass with large-scale color variation, concrete site pads, belt paths with yellow lines, a road with lane marks, curbs, sidewalk and street lamps, distant hills with haze, and 1,167 trees.
  5. Conveyor. A 67 m belt with blue frame channels, guide rails, pull cords, spinning return rollers, legs, braces, motors, gearboxes and control posts with e-stops and stack lights.
  6. Cargo. Copies of the belt items with keyframed movement at 1.5 m/s. Parts on belt 1, robots on belt 2. Each item is hidden outside its visible run.
  7. Titles. Rooftop signs "1 SUPPLIER", "2 ASSEMBLY PLANT" and "3 DISTRIBUTOR": 3D text on steel frames with lamps, colored red, blue and orange.
  8. Smoke. A 22×22×26 m volume box above the supplier chimney. Its shader shapes a drifting plume from scrolling 4D noise.
  9. Robot arm. The showroom arm in the assembly plant loops through 5 poses every 42 frames.
  10. Camera. 9 keyframes along a single flight path, aimed at a moving target, with depth of field on the target.
  11. Coins. 1,400 rigid-body coins plus invisible floor and back-wall colliders. The simulation is stepped frame by frame and written to keyframes, then physics is switched off. about 30 s
  12. Save. Writes supply_chain.blend, or renders test stills when asked.

The three buildings

Each building is a Python module that follows the same contract (written in README.md): a fixed footprint, belt openings at the same height, a space kept free for the rooftop title, and a list of anchor points it returns (roof height, chimney top, coin door, robot-arm parts). The main script uses those anchors to connect the buildings to the belt, the smoke, the titles and the coins.

supplier

1 · Supplier: IRONVALE METALWORKS

  • Steel foundry hall: brick base, corrugated cladding, sawtooth roof with skylights
  • 32 m red and white chimney with platform and ladder; the smoke rises from it
  • Three silos with a catwalk and piping
  • Raw-material yard: gantry crane on rails, steel coils, I-beams, ore bunker, scrap skips, drums, forklift
  • Office annex with sign, canopy, flags, workers in hi-vis vests
assembly plant

2 · Assembly plant: APEX ROBOTICS

  • Clean white factory with blue and orange brand stripes
  • Glass office with furnished, lit floors visible inside
  • Showroom window with an animated robot arm
  • Plaza with flagpoles, planter trees, a bench and a bike rack
  • Parking lot with cars and EV chargers; loading bay
distributor

3 · Distributor: SWIFTLINE DISTRIBUTION

  • Blue-grey warehouse with orange trim and a reception entrance
  • SHIPPING door with coin emblems, a hazard-striped apron and a warm light that switches on as the coins start
  • Loading docks with shelters, branded trucks and trailers
  • Truck yard with forklift, pallets, boxes, fences and gate

Key techniques

Textures without UV unwrapping

The photo textures use box projection in object space. Any generated box, cylinder or wall gets correctly scaled brick or concrete without UV mapping. The same material works on every object of any size.

Sky placed so the sun lights the fronts

The HDRI's sun direction was measured once (azimuth 145.8°). setup_world rotates the sky so that direction ends up front-left of the buildings. The facades facing the camera get sunlight, and the shadows fall back and to the right, away from the camera.

1,167 trees at render speed

The six tree templates are placed as collection instances. Their leaves are merged into one mesh inside each template, because Cycles would otherwise expand every leaf into a separate object (seconds per frame vs 250+ s). The leaf colors vary through a stored random attribute.

Moving belt without moving the belt

The belt surface is static. A keyframed value shifts its chevron bump pattern at 1.5 m/s, the same speed as the cargo, so the belt and the items move together.

Coins: simulate once, then keyframe

Each coin starts from a random point in the doorway, rejection-sampled so no two coins overlap. It moves under animation for 3 frames, then physics takes over and keeps its speed. After the run, every coin's position is stored as linear keyframes and physics is switched off. The result is deterministic and doesn't need a physics cache during rendering.

Smoke without a fluid simulation

The smoke is a single box with a volume shader. Density comes from distance to a drifting plume centerline, a radius that grows with height, and scrolling 4D noise. The plume moves over time without any simulation or cache.

Camera

A camera and a target empty are keyframed separately. A Track To constraint keeps the camera aimed at the target, and depth of field (f/5.6) focuses on it. Smooth handles keep the speed continuous between keys. Motion blur uses a 0.35 shutter.

Reproducible randomness

Every random choice (tree placement, clutter, coin launch) uses a fixed seed. Rebuilding the scene gives the same image, so a test still and the final frame always match.

Camera path

FrameShot
0–110High aerial of the whole site, descending toward the supplier
110–240Down to belt level alongside belt 1 (parts), then pulling back to the assembly plant front
240–355Assembly plant with the "2 ASSEMBLY PLANT" title and showroom arm, then down to belt 2 (robots)
355–480Past the distributor entrance to the SHIPPING door as the coins start (frame 385)
480–600Pull-back and rise to show the coin pile and the "3 DISTRIBUTOR" title

How the work was run

Parallel building work

  • The main agent wrote common.py, the shared material library and the building contract first.
  • Three subagents each wrote one building module in parallel. Each rendered its building alone with preview_building.py from 8–10 viewpoints and fixed problems before committing.
  • The main agent wrote main.py (everything between and around the buildings) and joined it all together.

Verification loop

  • Every change was checked with rendered stills, not by reading code. Selected frames were rendered at 960×540 and tiled into contact sheets.
  • The sheets were inspected for camera collisions, foreground obstructions, lighting and material errors, and physics artifacts.
  • Before the full render, one frame was timed at final quality. That check caught the CPU fallback: 150 s vs 32 s per frame.
  • The finished video is checked again by sampling frames from it. That check found the road patch, and only the frames where it was visible were re-rendered.

Problems found and how they were fixed

ProblemCauseFix
250–450 s per frameCycles expands instanced leaves inside instanced trees into millions of objectsMerge leaves into one mesh inside each tree template; store leaf color as an attribute
Render froze at 0% CPUGeometry sync deadlock with motion blur on the unmerged treesFixed by the same leaf merge
Scene build took 270 sAdding 1,400 rigid bodies with the standard operator gets slower with every coinLink coins directly into the rigid-body collection
Physics bake failedBlender's bake-to-keyframes operator does not work without a UICustom recorder: step frames, read positions, write F-curves
Coins shot 10+ m awayCoins spawned overlapping and pushed each other apartRejection-sample spawn points against recent coins
Coins appeared behind the doorSpawn volume reached past the back colliderMoved the back wall and narrowed the spawn depth
Gold looked pale, then copperBase and specular color balanceTuned base color, specular tint and roughness range
Hills rendered whiteEmission in the haze mixRemoved the emission; blend toward a darker haze color by distance
Trees, lamps and planters blocked shotsScattered objects on the camera pathKeep-clear zones for trees, moved lamp posts and a tree, re-keyed the camera
Dark rectangle in the road in the finaleSite driveways were 12 m long and reached 3 m into the road, drawing a second asphalt surface thereDriveways end under the sidewalk; the 225 frames that showed the road were re-rendered
Final render 5× slower than testsReopened .blend files render on the CPU (the GPU is a user preference)gpu.py enables Metal before rendering

Commands

# 1. download sky + textures (once)
python3 tools/fetch_assets.py asphalt_02 concrete_floor_02 gravel_concrete_03 red_brick_03 \
  corrugated_iron_02 metal_plate white_plaster_02 leafy_grass

# 2. check one building alone
blender -b -P tools/preview_building.py -- b_supplier --samples 48 --res 1280x720

# 3. quick test stills of the full scene
blender -b -P scene/main.py -- --samples 24 --res 960x540 --stills 200,320,480

# 4. build and save the final scene, then render all 600 frames on the GPU
blender -b -P scene/main.py -- --samples 64 --res 1920x1080 --save output/supply_chain.blend
blender -b output/supply_chain.blend -P scene/gpu.py \
  --python-expr "import bpy; r=bpy.context.scene.render; r.use_overwrite=False; r.use_placeholder=True" \
  -o "$PWD/output/frames/f####" -F PNG -s 1 -e 600 -a

# 5. encode
ffmpeg -framerate 30 -i output/frames/f%04d.png -c:v libx264 -preset slow -crf 16 \
  -pix_fmt yuv420p -movflags +faststart output/supply_chain_conveyor.mp4