A Blender-powered synthetic data generator template for 3D video reasoning tasks. Built for VBVR (Very Big Video Reasoning).
Every generator produces deterministic, parametrically varied training samples for I2V (Image-to-Video) foundation models.
data/questions/{domain}_task/{task_id}/
├── first_frame.png # Initial state — given to the video model as input
├── final_frame.png # Optional goal/after-state PNG
├── ground_truth.mp4 # Physics-rendered collision video — the correct answer
├── prompt.txt # Natural-language task question
└── metadata.json # All randomised parameters for this sample
# 1) Install dependencies into Blender's embedded Python
BLENDER_PY="/Applications/Blender.app/Contents/Resources/5.1/python/bin/python3.13"
"$BLENDER_PY" -m pip install -r requirements.txt
# 2) Generate 10 samples (headless, no UI)
/Applications/Blender.app/Contents/MacOS/Blender -b \
-P examples/generate_blender.py -- --num-samples 10ffmpegmust be installed and available onPATH(used to assembleground_truth.mp4).- The project relies on Blender's Python runtime (
bpy), so all Python deps must be installed into Blender's embedded Python. - On macOS headless runs, the core generator forces Cycles to
CPUfor stability.
3d-template-data-generator/
│
├── core/ # ✅ Framework — DO NOT MODIFY
│ ├── base_blender_generator.py # BaseBlenderGenerator + render helpers
│ ├── schemas.py # TaskPair dataclass
│ └── output_writer.py # Saves output to standardised folders
│
├── src/ # ✏️ YOUR TASK — customise these 3 files
│ ├── generator.py # Implement generate_task_pair()
│ ├── config.py # Task hyperparameters (image size, fps, …)
│ └── __init__.py
│
├── examples/
│ └── generate_blender.py # Entry point — run this with Blender
│
└── data/questions/ # Generated output appears here
You only need to touch 3 files in src/:
from core.base_blender_generator import GenerationConfig
from pydantic import Field
class TaskConfig(GenerationConfig):
domain: str = Field(default="my_new_task")
image_size: tuple[int, int] = Field(default=(800, 500))
video_frames: int = Field(default=60) # 2 sec @ 30 fps
render_samples: int = Field(default=50)
# Add task-specific params here:
# max_objects: int = Field(default=5)from core.base_blender_generator import BaseBlenderGenerator
from core.schemas import TaskPair
class MyTaskGenerator(BaseBlenderGenerator):
def generate_task_pair(self, task_id: str) -> TaskPair:
self.clear_scene() # Always start fresh
# ── Randomise parameters ──────────────────────────────────────────
n_objects = random.randint(2, 5) # placeholder: use your task-specific ranges
# ── Build the 3D scene with bpy ───────────────────────────────────
self._sky_world() # (optional) add sky lighting
# ... add meshes, materials, camera, lights ...
# ── Render ───────────────────────────────────────────────────────
output_dir = os.path.join(str(self.config.output_dir),
f"{self.config.domain}_task", task_id)
os.makedirs(output_dir, exist_ok=True)
first_frame = os.path.join(output_dir, "first_frame.png")
video = os.path.join(output_dir, "ground_truth.mp4")
self.render_first_frame(first_frame) # renders frame 1
self.render_video(video, bake_physics=True) # renders full animation
# ── Return TaskPair ───────────────────────────────────────────────
return TaskPair(
task_id=task_id,
domain=self.config.domain,
prompt="<your task question here>",
first_image=first_frame,
ground_truth_video=video if os.path.exists(video) else None,
metadata={"n_objects": n_objects, ...}
)from .config import TaskConfig
from .generator import MyTaskGeneratorReplace CausalityGenerator with MyTaskGenerator.
| Method | Description |
|---|---|
self.clear_scene() |
Resets Blender to empty scene |
self.render_first_frame(path) |
Renders frame 1 as PNG |
self.render_video(path, bake_physics=True) |
Bakes physics + renders MP4 |
self.bpy |
Direct access to the bpy Blender API |
self.config |
Your TaskConfig instance |
| Machine | Per-frame time (50 samples) | 60-frame video | Notes |
|---|---|---|---|
| Mac M2 (CPU) | ~1–2 s | ~90 s | Good for dev/debug |
| RunPod RTX 4090 | ~0.1–0.2 s | ~8–12 s | Production batch |
On macOS headless runs, the core generator forces Cycles to CPU for stability.
On Linux/RunPod, it tries OPTIX first, then CUDA, falling back to CPU.
If you need to force a specific device/compute backend, edit _configure_cycles_device() in core/base_blender_generator.py.
| # | Domain | Task | Key 3D Property Tested |
|---|---|---|---|
| 1 | Knowledge | Material-Momentum Causality | Mass / material → collision outcome |
| 2 | Spatiality | Egocentric Navigation | 3D spatial layout → 1st-person path |
| 3 | Transformation | Object Permanence & Occlusion | Tracking hidden objects in 3D |
| 4 | Perception | Mirror / Refraction Reasoning | Inverse optical physics |
| 5 | Abstraction | 3D Topological Analogy | Topological knot reasoning |