Example scripts

Writing your first simulation script

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. Once you get a good grasp of the library, you will be able to write your own Goo scripts and specify lots of initial conditions for your simulations of cells.

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.

Simulation scripts should start with the reset_scene() to start with a fresh scene - it removes all objects contained in the scene and resets simulation parameters to default.

import goo

goo.reset_scene()

Then, you should declare cell types, along with some of their physical parameters such as surface stiffness, homotypic and heterotypic adhesion strength. Stiffer cells will lead to less deformable cells. Higher adhesion strength will lead to larger cell deformation from their initial spherical shape. The ratio of stiffness to adhesion strength controls the resulting patterns cells make up (Garner, Tsai and Megason, 2022). Parameters are dimensionless in Goo.

cellsA = goo.CellType("A")
cellsB = goo.CellType("B")
cellsC = goo.CellType("C")

cellsA.homo_adhesion_strength = 15
cellsA.stiffness = 3  # ratio = 5
cellsB.homo_adhesion_strength = 100
cellsB.stiffness = 2  # ratio = 50
cellsC.homo_adhesion_strength = 500
cellsC.stiffness = 1  # ratio = 500

Cell types can then be populated with actual cell instances using create_cell(). The initial location, size and shape of each cell need to be specified, or set randomly. A material (i.e. a color) can also be given, which comes handy to visualize cell types or track individual cells with a color.

cellsA.create_cell("A1", (-10, +1.75, 0), color=(1, 1, 0), size=1.6)
cellsA.create_cell("A2", (-10, -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", (10, +1.75, 0), color=(1, 0, 1), size=1.6)
cellsC.create_cell("C2", (10, -1.75, 0), color=(1, 0, 1), size=1.6)

Until now, cells remain static meshes–no cell behaviours have been modelled yet. To do so, you need to call the simulator, which handles simulating the cell physics and solving sets of ODEs over time for gene regulation circuitry. The simulator takes lists of cell types and reaction-diffusion systems to be included in the simulation. If not included, objects will remain static. This is also where the total simulation time is specified, as well as the time step used in simulations.

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. It sets some general parameters (e.g. turning gravity off), units and length scales.

sim = goo.Simulator([cellsA, cellsB, cellsC], time=130, physics_dt=1)
sim.setup_world(seed=2024)

Lastly, cell behaviors–cell division, adhesion, motility, signal sensing and transduction–can be modelled in a modular fashion using handlers. Handlers 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. In this example, we 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(target_volume=50),  # in um3
      goo.AdhesionLocationHandler(),  # no parameters needed
      goo.SizeDivisionHandler(goo.BisectDivisionLogic, mu=50, sigma=1),  # in um3
      goo.RandomMotionHandler(goo.ForceDist.GAUSSIAN, max_strength=1000)
   ]
)

Note

The full list of handlers–cell behaviors the library currently supports–can be found in the codebase documentation.

Put together, this script models three cell doublets of varying stiffness and homotypic adhesion strength, all moving at the same strength for ~150 minutes. Heterotypic adhesion is ignored here.

from importlib import reload
import goo

reload(goo)
goo.reset_modules()
goo.reset_scene()

cellsA = goo.CellType("A")
cellsB = goo.CellType("B")
cellsC = goo.CellType("C")

cellsA.homo_adhesion_strength = 15
cellsA.stiffness = 3  # ratio = 5
cellsB.homo_adhesion_strength = 100
cellsB.stiffness = 2  # ratio = 50
cellsC.homo_adhesion_strength = 500
cellsC.stiffness = 1  # ratio = 500

cellsA.create_cell("A1", (-10, +1.75, 0), color=(1, 1, 0), size=1.6)
cellsA.create_cell("A2", (-10, -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", (10, +1.75, 0), color=(1, 0, 1), size=1.6)
cellsC.create_cell("C2", (10, -1.75, 0), color=(1, 0, 1), size=1.6)

sim = goo.Simulator([cellsA, cellsB, cellsC], time=130, physics_dt=1)
sim.setup_world(seed=2024)
sim.add_handlers(
    [
        goo.GrowthPIDHandler(target_volume=50),  # in um3
        goo.AdhesionLocationHandler(),  # no parameters needed
        goo.SizeDivisionHandler(goo.BisectDivisionLogic, mu=50, sigma=1),  # in um3
        goo.RandomMotionHandler(goo.ForceDist.GAUSSIAN, max_strength=1000)
    ]
)

Running this script in Blender should produce the following simulation:

Cell division based a threshold volume

from importlib import reload
import goo


reload(goo)
goo.reset_modules()
goo.reset_scene()

# Defining cells
celltype = goo.CellType("cellA")
celltype.homo_adhesion_strength = 1000
cell = celltype.create_cell(name="cell", loc=(0, 0, 0), color=(0, 1, 1))
cell.stiffness = 2
cell.pressure = 5

sim = goo.Simulator(celltypes=[celltype], time=150, physics_dt=1)
sim.setup_world()
sim.add_handlers(
    [
        goo.GrowthPIDHandler(target_volume=70),
        goo.SizeDivisionHandler(goo.BisectDivisionLogic, mu=60, sigma=2),
        goo.AdhesionLocationHandler(),
        goo.RemeshHandler(),
    ]
)

Running this script in Blender should produce the following simulation:

Random cell motion in a confined sphere

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  # Minimum distance between cells

goo.create_boundary((0, 0, 0), size=radius * 1.2)

cellsA = goo.SimpleType("A")
cellsA.homo_adhesion_strength = 500
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)
        cell.stiffness = 1
        cell.pressure = 5

sim = goo.Simulator([cellsA], time=500, physics_dt=1)
sim.setup_world()
sim.toggle_gravity(True)
sim.add_handlers(
    [
        goo.GrowthPIDHandler(target_volume=50), 
        goo.AdhesionLocationHandler(),
        goo.RandomMotionHandler(distribution=goo.ForceDist.CONSTANT, max_strength=2500),
    ]
)