A multi-agent simulation add-on that runs inside Blender, built around geometry space and object elements: agents live on a real 3D mesh space where distance, visibility, collision, and heading are first-class citizens (vs. NetLogo's 2D patches, GAMA's GIS focus, or Mesa's visualization-less Python).
Features: random walk + boundary reflection + obstacle avoidance + Boids flocking + SIR spreading + trajectory baking & export + numpy vectorization + heading display + layered 2.5D (multi-surface / elevators / goal seeking / force fields). The core is numpy (N,3) (tens of thousands of agents), rendered in bulk via Geometry Nodes instancing with per-instance heading; SIR states shade per-instance live. Trajectories bake to keyframes or export to CSV/JSON, and the core runs headless for parameter sweeps and benchmarks.
Download the release zip (built into release/, see "Building a Release") and install in Blender 4.2+:
- Edit → Preferences → Add-ons → Install from Disk… → select the zip;
- Enable "Blender ABMSim".
The zip ships with blender_manifest.toml, so it installs via the Blender 4.2+ extension mechanism — no manual directory copying.
- In the 3D Viewport press
N→ sidebar ABMSim tab. - The top section holds scene basics and controls: Agent Count, Boundary Radius (ignored when a base surface is set), the buttons Setup / Reset / Start / Pause / Step Back / Step (timer-driven, decoupled from the timeline), and live status (agents, t, S/I/R counts, running state).
- Feature groups below, each in a collapsible section:
- Scene Input — base surface, spawn zone, destination zone (goal arrival radius), elevator, force fields (multiple, each with its own parameters);
- Behavior — speed, turn rate, substeps,
dt, seed; - Obstacle Avoidance — probe distance, steer gain, agent radius;
- Boids — enable, neighbor radius, separation/cohesion/alignment weights;
- SIR Spread — contact radius, infection/recovery probability, initial infected;
- Trajectory & Export — recording, bake, CSV/JSON export.
- Click Setup, then Start. To add obstacles, create a mesh named
OBST_...(e.g.OBST_Wall) and re-run Setup. - Boids / SIR: enable in their sections; agents recolor live (blue = S, red = I, green = R).
- Trajectories: with recording on, run a while, then Bake Keyframes (playback frame = bake start frame + sim time × bake FPS; re-running overwrites the keyframes) or Export CSV/JSON to Export Path.
Tip: simulation speed is
substeps × dtper timer tick (heartbeat fixed at 0.05 s).
Multiple walkable heights at the same (x, y): elevated walkways, underpasses, multi-floor buildings with elevators, ramps, staircases.
- Base Surface (
base_obj): walkable-surface mesh. Multi-floor / bridges / ramps are multiple disconnected islands in ONE object (each island fits its own height). With a base set,boundary_sizeis ignored and no auto ground is created. Each island must be a single-valued height field — overlapping floors need separate (not vertex-connected) islands, otherwise triangle interpolation picks an arbitrary layer's z. - Spawn Zone (
spawn_obj, optional): spawn polygon; unset = random inside the base. - Destination Zone (
destination_obj): OD goal area; setting it auto-enables goal seeking (no separate toggle). Cross-layer goals route via elevators (nearest entrance, greedy). Arrival Radius sits in Scene Input, below the destination. - Elevator (
lift_obj): shaft mesh — must be upright with its XY footprint reaching into each floor's walkable area, or agents can't enter the cabin. Multiple elevators = multiple islands in one object. - Force Fields (multiple): positive strength attracts, negative repels, 0 = obstacle only; "Also an Obstacle" merges it into the world obstacle set.
- Legacy
SURF_/LIFT_name prefixes still work as a fallback when no base/lift object is assigned.
Semantics: surfaces are walkable (boundary = wall, z pinned to the surface), OBST_ objects are keep-out solids. Don't model floor plates as OBST_ — the "deep penetration → nearest horizontal exit" logic treats a thin plate as a solid box and pushes agents sideways.
Panel/operators → bpy.app.timers (per tick) → World.step() → GN instancing via sim_bridge. The core never imports bpy, so it's unit-testable and runnable headless.
sim_core.py—World: numpy (N,3) pos/vel, fixed dt, deterministic RNG, boundary, obstacles, Boids/SIR, 2.5D surfaces/connectors/OD/fields, trajectory record + CSV/JSON export.geometry.py— obstacle triangle snapshots, ray intersection, inside test, nearest horizontal exit;SurfaceField/ConnectorFieldfor the 2.5D layer model.kdtree.py— deterministic pure-Python 2D KDTree + numpyGridNeighborsbatch neighbor finder (10k+ Boids/SIR).sim_bridge.py— scene building & per-frame sync: agents = one base object (human-scale 0.5×0.5×1.7 m) instanced via GN "Instance on Points";foreach_setbulk-writes vertices, state colors, heading;bake_keyframeswrites trajectories as keyframes.__init__.py— registration, settings, panels, operators (incl. bake/export), timer heartbeat.scripts/—sweep.py(headless parameter sweep),bench.py(performance benchmark),build_release.py(release zip).
Milestone-by-milestone plan, current status, and decided technical routes: see PLAN.md.
python scripts/sweep.py --agents 100 --steps 300 --seeds 0-4 \
--sir-enabled --sir-beta 0.02,0.05,0.1 --out out/Core-only (no bpy), also runnable in blender -b --python scripts/sweep.py -- .... Writes out/sweep_summary.csv — final S/I/R, infection peak and peak time per seed × β combination (--seeds accepts 0-4 ranges or 1,3,7 lists).
<blender>/5.2/python/bin/python.exe scripts/bench.pyMeasured on this machine (Blender 5.2 bundled Python / numpy 2.3.4):
| Scenario | Time per step |
|---|---|
| 10k random walk + boundary | ~0.6 ms |
| 10k Boids (grid neighbors) | ~190 ms |
| 10k SIR (contact spread) | ~50 ms |
| 20k random walk + boundary | ~0.8 ms |
| 1k + 108-triangle obstacles + Boids (10 substeps/frame) | ~1.2 s/frame (was ~24 s/frame pre-vectorization) |
python tests/test_sim_core.py # core only, no Blender (needs numpy)
blender -b --python tests/smoke_test.py # bpy-side smoke testsIf the system Python is PEP 668-managed, run the core scripts with Blender's bundled Python — replace <blender> with your local Blender install path (same for sweep.py, bench.py, build_release.py).
python scripts/build_release.pyProduces release/blender_abmsim-<version>.zip (version read from blender_manifest.toml; old zips in release/ are cleaned up automatically). The zip contains only the plugin modules (__init__.py, sim_core.py, geometry.py, kdtree.py, sim_bridge.py) plus blender_manifest.toml, so it installs via Install from Disk….
Verify an installed extension — this checks that the extension loads and its operators run:
blender -b -P tests/check_extension.pyAfter changing code, the installed copy under user_default/blender_abmsim/ does not auto-sync — overwrite it (or reinstall the zip) and restart Blender, since modules are loaded into memory at startup.
- Undo doesn't roll back the session — click Setup again after undoing.
- Baking inserts per-vertex keyframes; with 10k+ agents and many snapshots it gets slow (raise the record interval or cap the steps).
- Baking then Start again overwrites the keyframes; re-bake after re-running.
