Skip to main content

farmhand

Distributed Blender rendering on Modal. farmhand uploads a .blend file, splits its frame range across GPU containers, streams the finished frames back to your machine, and can stitch them into a video on the server. It was built for rendering Molecular Nodes animations, which need nothing installed on the server beyond bpy because the node groups and materials travel inside the .blend.

Requirements

  • Python 3.11 or newer.
  • A Modal account. Modal bills per second of GPU time; an L40S is about $2 per hour.
  • ffmpeg on your machine, only if you want to combine already-downloaded frames locally.

Install

uv tool install farmhand-bpy    # or: pip install farmhand-bpy

The distribution is farmhand-bpy; the command and the import name are farmhand.

Quickstart

farmhand setup                                   # log in to Modal (opens a browser)
farmhand deploy                                  # build the render image and deploy the app (minutes, first time)
farmhand render scene.blend --video scene.mp4    # render every frame, stitch the video on the server

Frames land in render/<job_id>/frame_00001.png and so on, and the video in render/<job_id>/scene.mp4. The job ID is printed in the table at the start of the render and by farmhand jobs.

Two things to know before the first real render:

  • The .blend must be self-contained. Only that one file is uploaded. Pack external data first (File > External Data > Pack Resources) or linked libraries and textures will be missing.
  • bpy_version must match the Blender that saved the file. The default is 5.1.0. Set it in your config to the version you use, then farmhand deploy. A file saved by a newer Blender can lose node groups or materials when opened by an older bpy without any error, giving blank frames after paying for GPU time.

Commands

farmhand config                                  # resolved settings, which file they came from, Modal auth status
farmhand setup                                   # Modal login; creates the configured environment if it is missing
farmhand deploy                                  # deploy; rerun after changing gpu, bpy_version, timeout, max_containers or volume

farmhand render scene.blend                      # all frames from the file's range, into render/<job_id>/
farmhand render scene.blend --frame-start 1 --frame-end 48 --samples 64 --resolution-percentage 50
farmhand render scene.blend --video out.mp4      # also stitch an mp4 server-side, no local ffmpeg needed
farmhand render scene.blend --ephemeral          # no deploy needed; image built on first use and cached

farmhand jobs                                    # jobs still on the Modal volume
farmhand download <job_id>                       # fetch a job's frames again, into render/<job_id>/
farmhand combine render/<job_id> -o out.mp4      # local frames -> video with your ffmpeg
farmhand combine <job_id> -o out.mp4             # frames still on the volume -> video, server-side
farmhand cleanup <job_id> [--yes]                # delete a job's blend file and frames from the volume

Every job's input file and frames stay on the Modal volume, and count towards its storage, until you run cleanup.

The global options --config, --environment and --app-name go before the subcommand:

farmhand --environment prod render scene.blend

Configuration

farmhand reads one config file: the nearest farmhand.yml, or pyproject.toml with a [tool.farmhand] table, searching upward from the current directory. Within a directory farmhand.yml wins. Files are not merged. Command-line flags override the file, and anything unset uses the built-in default. farmhand config shows the result.

# pyproject.toml, in the project you render from
[tool.farmhand]
environment = "render"      # Modal environment; omit for your profile's default
bpy_version = "5.2.2"       # the Blender version that saved your .blend files
gpu = "L40S"                # or a fallback list: ["RTX-PRO-6000", "L40S"]
frames_per_container = 4
Key Default When it is read
profile active Modal profile every command
environment profile default every command
app_name farmhand every command
volume <app_name>-data deploy
gpu L40S deploy
max_containers 50 deploy, capped by your Modal plan
timeout 7200 deploy, seconds per container
bpy_version 5.1.0 deploy
output_dir render render, download
frames_per_container 1 render
fps 30 render, combine
codec libx265 render, combine
crf 20 render, combine

Keys read at deploy are baked into the deployed app; change them and run farmhand deploy again. The rest are per-run defaults that the matching flags override. Modal credentials come from farmhand setup, or from the MODAL_TOKEN_ID and MODAL_TOKEN_SECRET environment variables in CI.

Each container opens the file once and renders frames_per_container frames in sequence, so a higher value amortises the container start-up and scene evaluation over more frames. Keep frames_per_container times the per-frame render time under timeout.

Choosing a GPU

See docs/gpus.md. Short version: Cycles wants RT cores and FP32 throughput, so the visualisation cards (RTX-PRO-6000, L40S, L4) are both faster and several times cheaper per frame than the AI cards (A100, H100, H200, B200). The default L40S is a good balance.

Python API

from farmhand import RenderJob, combine_frames, config, submit_render

cfg = config.load()  # same lookup as the CLI; or Config(environment="render", bpy_version="5.2.2")
config.apply_env(cfg)  # exports cfg.profile for Modal; call before importing farmhand.render

job = RenderJob("scene.blend", frame_end=48, samples=64, video="out.mp4")
result = submit_render(job, cfg)  # blocks until done
result.frames  # list[Path] under result.output_dir, which is <output_dir>/<job_id>
result.video  # Path or None

combine_frames("render/abc123def456", "out.mp4", fps=30)  # local ffmpeg

RenderJob fields left as None take the config default, or, for the frame range, the values saved in the .blend.

Example

examples/6n2y-spin builds a Molecular Nodes scene of PDB 6n2y turning on the spot and renders it with farmhand.

License

MIT. See LICENSE.

Metadata

Release files for farmhand-bpy 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for farmhand-bpy 0.1.0
File Size Uploaded
farmhand_bpy-0.1.0.tar.gz 15.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for farmhand-bpy 0.1.0
File Interpreter ABI Platform
farmhand_bpy-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 35.4 kB

Release files / farmhand_bpy-0.1.0.tar.gz

Download URL farmhand_bpy-0.1.0.tar.gz
Size 15.9 kB
Tags Source
SHA-256 checksum
How to use checksums
050299283b8f3df562e780772e071cd7309cf22922ca4a5675ff1accc410e95f
BLAKE2b-256 checksum
How to use checksums
5f0b9644035f0b4296d5d33108e8741e8f52febb9b490c31c70e3e80df3c56c6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / farmhand_bpy-0.1.0-py3-none-any.whl

Download URL farmhand_bpy-0.1.0-py3-none-any.whl
Size 19.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6e91ade355fcacef1b6f844caca449c023880d0c9af4f446e8dd65438a675d40
BLAKE2b-256 checksum
How to use checksums
0e0c7a3aefb2af364c114ec9e9d732e3c3eb97e762b20150354dcd0a123bb14f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page