Example scripts¶
1. Writing your first script: a tutorial¶
Examples of simulation script can be found in the /simulations/ folder, located here. Goo extends Blender towards agent-based simulations of cell mechanics, molecular reactions and gene regulatory networks. With Goo, you can create custom scripts to simulate various cellular phenomena by specifying initial conditions and parameters.
Running scripts¶
Goo scripts typically get ran from Blender’s scripting tab, though they can be ran from Visual Studio Code directly using the developer’s extension developed by Jacques Lucke.
Initialization¶
All simulation scripts should begin with goo.reset_modules() and reset_scene() to ensure a clean starting environment. The latter removes all objects from the Blender scene and resets simulation parameters to their default values.
import goo
goo.reset_modules()
goo.reset_scene()
Next, define cell types along with their physical properties, such as surface stiffness and adhesion strength. Stiffer cells are less deformable, while higher adhesion strength increases deformation from their initial spherical shape. The ratio of stiffness to adhesion strength influences resulting cell patterns (Garner, Tsai, and Megason, 2022). Most parameters in Goo are dimensionless.
Defining cell types¶
cellsA = goo.CellType("A", target_volume=70, pattern="simple")
cellsB = goo.CellType("B", target_volume=70, pattern="simple")
cellsC = goo.CellType("C", target_volume=70, pattern="simple")
cellsA.homo_adhesion_strength = 1
cellsA.stiffness = 1 # ratio = 1
cellsB.homo_adhesion_strength = 500
cellsB.stiffness = 1 # ratio = 500
cellsC.homo_adhesion_strength = 2000
cellsC.stiffness = 1 # ratio = 2000
Creating cells¶
Populate cell types with individual cells using create_cell(). Specify their initial location, size, shape, and optional material color for visualization.
cellsA.create_cell("A1", (-20, +1.75, 0), color=(1, 1, 0), size=1.6)
cellsA.create_cell("A2", (-20, -1.75, 0), color=(1, 1, 0), size=1.6)
cellsB.create_cell("B1", (0, +1.75, 0), color=(0, 1, 1), size=1.6)
cellsB.create_cell("B2", (0, -1.75, 0), color=(0, 1, 1), size=1.6)
cellsC.create_cell("C1", (20, +1.75, 0), color=(1, 0, 1), size=1.6)
cellsC.create_cell("C2", (20, -1.75, 0), color=(1, 0, 1), size=1.6)
Setting up the simulator¶
To introduce cell behaviors like adhesion, motility and division, use the simulator. It handles the simmulation of cell physics and solving sets of ODEs over time for genetic circuitry. Define total simulation time, time step (physics_dt for mechanics, molecular_dt for reactions), and which cell types to include in the simulation. If not included, objects will remain static.
Note
Goo uses two simulation engines: one for cell mechanics on meshes and one for discrete molecular reactions on KD-trees and gene regulatory circuitry for each cell. Molecular processes happen at faster time scale than cell mechanics; therefore physics_dt typically needs be set at least 10 times larger than molecular_dt.
The setup_world() function always needs be defined, and it’s best practice to always set a random seed for reproducibility. It sets some general parameters (e.g. turning gravity off), units and length scales.
sim = goo.Simulator([cellsA, cellsB, cellsC], time=180, physics_dt=1)
sim.setup_world(seed=2024)
Appending handlers to the simulator¶
Handlers modularly define cell behavior. They execute functions sequentially at every time step. They can take some parameters as arguments to control e.g. the rate of division based on cell volume. Add handlers to the simulator to control these aspects. For example: these lines model cell growth, homotypic adhesion, volume-based division (target volume of 50 \(\mu m^3\) with a std.dev. of 1) and gaussian random motion.
sim.add_handlers(
[
goo.GrowthPIDHandler(), # in um3
goo.RecenterHandler(),
goo.SizeDivisionHandler(goo.BisectDivisionLogic, mu=60, sigma=1), # in um3
goo.RandomMotionHandler(goo.ForceDist.GAUSSIAN, strength=500)
]
)
Note
The full list of handlers–cell behaviors the library currently supports–can be found in the codebase documentation.
2. Modeling growing cell doublets with differential adhesion¶
This example simulates three cell doublets of varying stiffness and adhesion strength over ~150 minutes, ignoring heterotypic adhesion.
from importlib import reload
import goo
reload(goo)
goo.reset_modules()
goo.reset_scene()
cellsA = goo.CellType("A", target_volume=60, pattern="simple")
cellsB = goo.CellType("B", target_volume=60, pattern="simple")
cellsC = goo.CellType("C", target_volume=60, pattern="simple")
cellsA.homo_adhesion_strength = 1
cellsA.stiffness = 1 # ratio = 1
cellsB.homo_adhesion_strength = 500
cellsB.stiffness = 1 # ratio = 500
cellsC.homo_adhesion_strength = 2000
cellsC.stiffness = 1 # ratio = 2000
cellsA.create_cell("A1", (-20, +1.75, 0), color=(1, 1, 0), size=1.6)
cellsA.create_cell("A2", (-20, -1.75, 0), color=(1, 1, 0), size=1.6)
cellsB.create_cell("B1", (0, +1.75, 0), color=(0, 1, 1), size=1.6)
cellsB.create_cell("B2", (0, -1.75, 0), color=(0, 1, 1), size=1.6)
cellsC.create_cell("C1", (20, +1.75, 0), color=(1, 0, 1), size=1.6)
cellsC.create_cell("C2", (20, -1.75, 0), color=(1, 0, 1), size=1.6)
sim = goo.Simulator([cellsA, cellsB, cellsC], time=180, physics_dt=1)
sim.setup_world(seed=2024)
sim.add_handlers(
[
goo.GrowthPIDHandler(), # in um3
goo.RecenterHandler(),
goo.SizeDivisionHandler(goo.BisectDivisionLogic, mu=50, sigma=1), # in um3
goo.RandomMotionHandler(goo.ForceDist.GAUSSIAN, strength=500),
]
)
Running this script in Blender produces the following simulation:
3. Cell division based on volume growth¶
Simulates cells dividing based on volume.
Goo script
from importlib import reload
import goo
reload(goo)
goo.reset_modules()
goo.reset_scene()
# Defining cells
celltype = goo.CellType("A", target_volume=70, pattern="simple")
celltype.homo_adhesion_strength = 500
cell1 = celltype.create_cell(name="cell1", loc=(0, 0, 0), color=(0, 1, 1))
cell1.stiffness = 2
cell1.pressure = 5
sim = goo.Simulator([celltype], time=200, physics_dt=1)
sim.setup_world(seed=2025)
sim.add_handlers(
[
goo.GrowthPIDHandler(), # in um3
goo.RecenterHandler(),
goo.RemeshHandler(),
goo.SizeDivisionHandler(goo.BisectDivisionLogic, mu=65, sigma=2), # in um3
]
)
Running this script in Blender produces the following simulation:
4. Random cell mixing¶
Simulates random motion and mixing of cells.
Goo script
from importlib import reload
import goo
import numpy as np
reload(goo)
goo.reset_modules()
goo.reset_scene()
def random_point_inside_sphere(radius):
"""Generate a random point inside a sphere."""
r = (radius - 1) * np.cbrt(np.random.rand())
theta = np.random.uniform(0, 2 * np.pi)
phi = np.random.uniform(0, np.pi)
x = r * np.sin(phi) * np.cos(theta)
y = r * np.sin(phi) * np.sin(theta)
z = r * np.cos(phi)
return x, y, z
def check_min_distance(new_point, points, min_distance):
"""Check if the new point meets the minimum distance requirement."""
for point in points:
distance = np.linalg.norm(np.array(new_point) - np.array(point))
if distance < min_distance:
return False
return True
# Create cells within the sphere with minimum distance constraint
num_cells = 6 # Number of cells
radius = 15 # Sphere radius
min_distance = 3 # Mnimum distance between cells
goo.create_boundary((0, 0, 0), size=radius)
cellsA = goo.CellType("A", target_volume=100, pattern="simple")
cellsA.homo_adhesion_strength = 150
cellsA.motion_strength = 1500
cells = []
while len(cells) < num_cells:
new_point = random_point_inside_sphere(radius)
if check_min_distance(new_point, cells, min_distance):
cells.append(new_point)
cell_name = f"cell_A{len(cells)}"
color = tuple(np.random.random_sample(3))
cell = cellsA.create_cell(cell_name, new_point, color=color, size=1.5)
cell.stiffness = 1
cell.pressure = 5
sim = goo.Simulator([cellsA], time=300, physics_dt=1)
sim.setup_world(seed=2024)
sim.add_handlers(
[
goo.GrowthPIDHandler(),
goo.RecenterHandler(),
goo.SizeDivisionHandler(goo.BisectDivisionLogic, mu=105, sigma=1),
goo.RandomMotionHandler(distribution=goo.ForceDist.UNIFORM),
]
)
Running this script in Blender produces the following simulation:
5. Blinking cells and the repressilator¶
This example introduces the simulation of gene regulation networks.
We model cells that blink on and off, representing their gene concentration that is controlled by a gene regulatory network akin to a repressilator (Elowitz et al., 2000). To do so, we need to define a set of genes and their interactions. Here, gene X repress gene Z, gene Z repress gene Y, and gene Y repress gene X, and all genes degrades following first order equation. That is, the degradation of a gene concentration proceeds at a rate that is linearly proportional to the gene concentration itself. We declare two repressilator networks, one with a faster production rate than the other i.e. higher kcat, leading to a shorter period of oscillation. Cells are then populated with these networks and initial gene concentrations.
Other than that, cells still homotypically adhere, move, grow and divide.
x = Gene("x")
y = Gene("y")
z = Gene("z")
network1 = GeneRegulatoryNetwork()
network1.load_circuits(
DegFirstOrder(x, 0.1),
DegFirstOrder(y, 0.1),
DegFirstOrder(z, 0.1),
ProdRepression(y, x, kcat=0.4, n=3),
ProdRepression(z, y, kcat=0.4, n=3),
ProdRepression(x, z, kcat=0.4, n=3),
)
network2 = GeneRegulatoryNetwork()
network2.load_circuits(
DegFirstOrder(x, 0.1),
DegFirstOrder(y, 0.1),
DegFirstOrder(z, 0.1),
ProdRepression(y, x, kcat=0.4, n=3),
ProdRepression(z, y, kcat=0.4, n=3),
ProdRepression(x, z, kcat=0.4, n=3),
)
cell1.grn = network1
cell1.metabolites = {x: 2, y: 0.1, z: 0.1}
cell2.grn = network2
cell2.metabolites = {x: 2, y: 0.1, z: 0.1}
When put all together, this is the script that models blinking cells:
Goo script
from importlib import reload
import goo
from goo.gene import *
from goo.division import *
from goo.handler import *
reload(goo)
goo.reset_modules()
goo.reset_scene()
# Defining genes
x = Gene("x")
y = Gene("y")
z = Gene("z")
celltype1 = goo.CellType("cellA", pattern="simple", target_volume=70)
celltype1.homo_adhesion_strength = 500
celltype1.motion_strength = 1000
cell1 = celltype1.create_cell(name="cell1", loc=(-10, 0, 0), color=(0, 0, 0))
cell1.stiffness = 5
celltype2 = goo.CellType("cellB", pattern="simple", target_volume=70)
celltype2.homo_adhesion_strength = 500
celltype2.motion_strength = 1000
cell2 = celltype2.create_cell(name="cell1", loc=(10, 0, 0), color=(0, 0, 0))
cell2.stiffness = 5
network1 = GeneRegulatoryNetwork()
network1.load_circuits(
DegFirstOrder(x, 0.1),
DegFirstOrder(y, 0.1),
DegFirstOrder(z, 0.1),
ProdRepression(y, x, kcat=0.4, n=3),
ProdRepression(z, y, kcat=0.4, n=3),
ProdRepression(x, z, kcat=0.4, n=3),
)
cell1.grn = network1
cell1.metabolites = {x: 2, y: 0.1, z: 0.1}
network2 = GeneRegulatoryNetwork()
network2.load_circuits(
DegFirstOrder(x, 0.1),
DegFirstOrder(y, 0.1),
DegFirstOrder(z, 0.1),
ProdRepression(y, x, kcat=0.55, n=3),
ProdRepression(z, y, kcat=0.55, n=3),
ProdRepression(x, z, kcat=0.55, n=3),
)
cell2.grn = network2
cell2.metabolites = {x: 2, y: 0.1, z: 0.1}
sim = goo.Simulator(celltypes=[celltype1, celltype2], time=200, physics_dt=1)
sim.setup_world(seed=2025)
sim.add_handlers(
[
goo.GrowthPIDHandler(),
goo.SizeDivisionHandler(goo.BisectDivisionLogic, mu=60, sigma=2),
goo.RecenterHandler(),
goo.RandomMotionHandler(distribution=goo.ForceDist.GAUSSIAN),
goo.NetworkHandler(),
goo.ColorizeHandler(goo.Colorizer.GENE, x, range=(1, 2)),
]
)
Running this script in Blender produces the following simulation:
6. Controlling cell motility with gene concentration¶
In this example, the strength of the motion of a cell is controlled by the concentration of gene x, which itself is modeled as part of a repressilator regulatory network. The concentration of gene x oscillates; therefore, the speed of the cell oscillates between exploratory (fast motility) and stationary (low motility) periods.
The script to model this behavior:
Goo script
from importlib import reload
import goo
from goo.gene import *
from goo.division import *
from goo.handler import *
reload(goo)
goo.reset_modules()
goo.reset_scene()
# Defining genes
x = Gene("x")
y = Gene("y")
z = Gene("z")
celltype = goo.CellType("cellA", pattern="simple", target_volume=70)
celltype.homo_adhesion_strength = 500
celltype.motion_strength = 1000
cell = celltype.create_cell(name="cell1", loc=(0, 0, 0), color=(0, 0, 0))
cell.stiffness = 5
network1 = GeneRegulatoryNetwork()
network1.load_circuits(
DegFirstOrder(x, 0.04),
DegFirstOrder(y, 0.04),
DegFirstOrder(z, 0.04),
ProdRepression(y, x, kcat=0.5, n=3),
ProdRepression(z, y, kcat=0.5, n=3),
ProdRepression(x, z, kcat=0.5, n=3)
)
cell.grn = network1
cell.metabolites = {x: 2, y: 0.5, z: 0.5}
cell.link_gene_to_property(gene=x, property="motion_strength")
sim = goo.Simulator(celltypes=[celltype], time=1000, physics_dt=1)
sim.setup_world(seed=2025)
sim.add_handlers(
[
goo.GrowthPIDHandler(),
# goo.SizeDivisionHandler(goo.BisectDivisionLogic, mu=60, sigma=2),
goo.RecenterHandler(),
# goo.RemeshHandler(),
goo.NetworkHandler(),
goo.ColorizeHandler(goo.Colorizer.GENE, x, range=(1, 2)),
goo.RandomMotionHandler(goo.ForceDist.GAUSSIAN)
]
)
Running this script in Blender produces the following simulation: