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.
Pipeline
fetch_assets.py
Python standard library (urllib, json) calls the Poly Haven API and saves the sky HDRI and 8 texture sets.
assets/ (gitignored)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)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/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.
ffmpeg
The PNG frames are encoded as H.264 (libx264, CRF 16, yuv420p) at 30 fps.
.mp4What 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
bmeshwhere 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
Gear, copper spool, battery pack and robot. props.py builds them from primitives, and the belt carries copies of these templates.
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 in the final render
Frame 200: parts moving from the supplier to the assembly plant.
Tools and libraries
| Tool / library | Kind | Used for |
|---|---|---|
| Blender 5.2.2 LTS | app | The whole 3D scene. Runs headless (blender -b) with no UI; nothing was built by clicking. |
bpy | Blender Python API | Creating objects, materials, node trees, modifiers, keyframes, lights, the camera and render settings. |
bmesh | Blender Python API | Custom meshes: tree canopies, leaf cards, fence panels and other shapes that simple primitives don't cover. |
mathutils, bpy_extras.anim_utils | Blender Python API | Vectors and matrices; writing the recorded coin motion straight into F-curves. |
| Geometry Nodes | Blender feature | Scattering leaves over tree canopies, then merging them into a single mesh. |
| Shader nodes (Principled BSDF, Volume, Noise, Attribute) | Blender feature | All materials, the smoke plume, the hill haze and the scrolling belt chevrons. |
| Rigid Body (Bullet) | Blender feature | Coin physics: they collide with the floor, the back wall and each other. |
| Cycles + Metal | renderer | Path-traced rendering on the Apple M5 Pro 20-core GPU. |
| OpenImageDenoise | bundled with Blender | Removes noise, so 64 samples per pixel are enough. |
| AgX (Medium High Contrast) | color management | Maps bright sunlight and shiny gold to screen colors without clipping. |
Python stdlib: math, random, urllib, json, importlib | standard library | Seeded randomness (reproducible placement), asset download, loading the building modules. |
| Poly Haven API | web source | Source of the sky HDRI and textures (api.polyhaven.com). |
| ffmpeg 9.0.1 (libx264) | CLI | Tiling 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
| File | Lines | What it does | Written by |
|---|---|---|---|
scene/common.py | 672 | Shared 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.py | 362 | Belt items (gear, spool, battery, robot), the coin, and the rooftop title sign. | main agent |
scene/trees.py | 189 | Round and conifer trees with Geometry Nodes leaves. | main agent |
scene/b_supplier.py | 1,182 | IRONVALE METALWORKS: steel foundry. | subagent 1 |
scene/b_assembly.py | 1,243 | APEX ROBOTICS: assembly plant. | subagent 2 |
scene/b_distributor.py | 1,296 | SWIFTLINE DISTRIBUTION: warehouse and coin door. | subagent 3 |
scene/main.py | 1,037 | Builds 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.py | 10 | Switches Cycles to the Metal GPU when rendering the saved .blend. | main agent |
tools/fetch_assets.py | 32 | Downloads the HDRI and textures. | main agent |
tools/preview_building.py | 69 | Renders 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.
- Reset and render settings. Cycles, 1920×1080, 64 samples, adaptive sampling, OIDN, AgX, motion blur (shutter 0.35), persistent data.
- 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.
- Buildings. Imports the three modules. Each builds around its own origin, then moves to x = −40, 0 and +40 m. about 5 s
- 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.
- 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.
- 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.
- Titles. Rooftop signs "1 SUPPLIER", "2 ASSEMBLY PLANT" and "3 DISTRIBUTOR": 3D text on steel frames with lamps, colored red, blue and orange.
- Smoke. A 22×22×26 m volume box above the supplier chimney. Its shader shapes a drifting plume from scrolling 4D noise.
- Robot arm. The showroom arm in the assembly plant loops through 5 poses every 42 frames.
- Camera. 9 keyframes along a single flight path, aimed at a moving target, with depth of field on the target.
- 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
- 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.
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
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
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
| Frame | Shot |
|---|---|
| 0–110 | High aerial of the whole site, descending toward the supplier |
| 110–240 | Down to belt level alongside belt 1 (parts), then pulling back to the assembly plant front |
| 240–355 | Assembly plant with the "2 ASSEMBLY PLANT" title and showroom arm, then down to belt 2 (robots) |
| 355–480 | Past the distributor entrance to the SHIPPING door as the coins start (frame 385) |
| 480–600 | Pull-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.pyfrom 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
| Problem | Cause | Fix |
|---|---|---|
| 250–450 s per frame | Cycles expands instanced leaves inside instanced trees into millions of objects | Merge leaves into one mesh inside each tree template; store leaf color as an attribute |
| Render froze at 0% CPU | Geometry sync deadlock with motion blur on the unmerged trees | Fixed by the same leaf merge |
| Scene build took 270 s | Adding 1,400 rigid bodies with the standard operator gets slower with every coin | Link coins directly into the rigid-body collection |
| Physics bake failed | Blender's bake-to-keyframes operator does not work without a UI | Custom recorder: step frames, read positions, write F-curves |
| Coins shot 10+ m away | Coins spawned overlapping and pushed each other apart | Rejection-sample spawn points against recent coins |
| Coins appeared behind the door | Spawn volume reached past the back collider | Moved the back wall and narrowed the spawn depth |
| Gold looked pale, then copper | Base and specular color balance | Tuned base color, specular tint and roughness range |
| Hills rendered white | Emission in the haze mix | Removed the emission; blend toward a darker haze color by distance |
| Trees, lamps and planters blocked shots | Scattered objects on the camera path | Keep-clear zones for trees, moved lamp posts and a tree, re-keyed the camera |
| Dark rectangle in the road in the finale | Site driveways were 12 m long and reached 3 m into the road, drawing a second asphalt surface there | Driveways end under the sidewalk; the 225 frames that showed the road were re-rendered |
| Final render 5× slower than tests | Reopened .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