Skip to content

Commit

Permalink
Blender "Pulsar" animation (#112)
Browse files Browse the repository at this point in the history
* Create tasty picture

* Corrected light attribute

* Added `create_activity_mat`

* Use `create_activity_mat`

* Improved `load_population` to return the collection and cells.

* Added a TransparencyBDSF node and MixShader node for transparency

* Improved return values of `load_population`(s)

* some quick docstrings

* v3.5.0 beta - Blender support

* moved patch to neuron deps v3.5.0b1

* Added new "pulsar" activity material

* Stub 'compose' function.

* Moved blender file into blender module.

* Added _mixin module. Shuffled all code around. added BlenderPopulation

* added `animate` namespace stub with `pulsar` animation. improve in v4

* Refactored pulsar animation

* fixed last spike myotonia

* moved network cam

* fixed last spike myotonia again

* fixed last spike dissapearing because of myotonia fix

* normalize after d2 to avoid float shenannigans

* fixed afterburner being only half as long as supposed to be

* adjusted camera pos to show purkinje cells better

* Added up to date docstrings
  • Loading branch information
Helveg committed Sep 25, 2020
1 parent 1cb785f commit 54bd8af
Show file tree
Hide file tree
Showing 3 changed files with 375 additions and 115 deletions.
182 changes: 69 additions & 113 deletions bsb/blender/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,145 +8,68 @@
)


def create_network(scaffold, scene, name):
def create_collection(name, parent=None):
"""
Creates the root collection that will contain all the blender components of this
network and a child collection for the cell populations. Fills the scene with a
default camera and light if they are missing.
Create a collection in the blender scene.
"""
scaffold._blender_scene = scene
scaffold._blender_collection = coll = create_collection(None, name)
scaffold._blender_name = coll.name
scene.collection.children.link(coll)
cells = scaffold.create_collection("cells", parent=scaffold._blender_collection)
scaffold._blender_cells_collection = cells
scaffold.create_network_cam()
scaffold.create_lighting()
coll_diff = _diffkey(bpy.data.collections)
bpy.ops.collection.create(name=name)
coll = coll_diff().pop()
if parent is not None:
parent.children.link(coll)
return coll


def _load_blender_network(scaffold, scene, root_collection):
scaffold._blender_scene = scene
scaffold._blender_collection = root_collection
scaffold._blender_name = root_collection.name
scaffold._blender_cells_collection = root_collection.children["cells"]
class BlenderPopulation:
def __init__(self, collection, cells):
self.name = ".".join(collection.name.split(".")[:-1])
self.collection = collection
self.cells = cells


def blend(scaffold, scene, name):
"""
Create or load the network from the given scene.
"""
if name in scene.collection.children:
_load_blender_network(scaffold, scene, scene.collection.children[name])
else:
scaffold.create_network(scene, name)


def create_population(scaffold, tag, opacity=1):
def create_population(name, material, cells, parent=None, scene=None, radius=3.0):
"""
Create a cell population where each cell is represented by a sphere in 3D space.
Each cell population will have a material associated with it that is capable of
lighting up, to have its activity animated.
Each cell population will have a matte material associated with it.
"""
scene = scaffold._blender_scene
placement_set = scaffold.get_placement_set(tag)
name = placement_set.type.name
hex_color = placement_set.type.plotting.color.lstrip("#")
color = tuple(int(hex_color[i : i + 2], 16) / 255 for i in (0, 2, 4))
radius = placement_set.type.placement.radius
cells = placement_set.cells
if scene is None:
scene = bpy.context.scene
objects = []
collection = scaffold.create_collection(name, scaffold._blender_cells_collection)
mat = scaffold.create_activity_material(name, (*color, opacity))
mesh = _create_ico_mesh(scaffold, name, radius)
mesh.materials.append(mat)
total = len(cells)
collection = create_collection(name, parent=parent)
mesh = _create_ico_mesh(name, radius)
mesh.materials.append(material)
for i, cell in enumerate(cells):
cell.object = object = bpy.data.objects.new(
name=f"{name} #{cell.id}", object_data=mesh
)
object["cell_id"] = cell.id
object.location = cell.position[[0, 2, 1]]
collection.objects.link(object)
return collection, cells
return BlenderPopulation(collection, cells)


def has_population(scaffold, tag):
"""
Check whether a given population of the network already exists in the scene.
def create_material(name, color=(0.8, 0.8, 0.8, 1.0)):
"""
return tag in scaffold._blender_cells_collection.children


def load_population(scaffold, tag, opacity=1):
"""
Load a cell population from the scene, if it doesn't exist it is created. Couples
the placement set cells to their blender objects.
"""
ps = scaffold.get_placement_set(tag)
if not ps.type.relay:
if tag not in scaffold._blender_cells_collection.children:
collection, cells = scaffold.create_population(tag, opacity=opacity)
else:
cells = ps.cells
collection = scaffold._blender_cells_collection.children[tag]
index = {
int(k.split("#")[1].split(".")[0]): v
for k, v in collection.objects.items()
}
for cell in cells:
cell.object = index[cell.id]
return collection, cells


def load_populations(scaffold):
"""
Load all cell populations from the scene, skipping relays.
"""
colls, cells = {}, {}
for tag in scaffold.configuration.cell_types:
colls[tag], cells[tag] = scaffold.load_population(tag) or (None, None)
return colls, cells


def create_collection(scaffold, name, parent=None):
"""
Create a collection in the blender scene.
Create a material with a certain base color. The 4th float of the color is the
opacity.
"""
coll_diff = _diffkey(bpy.data.collections)
bpy.ops.collection.create(name=name)
coll = coll_diff().pop()
if parent is not None:
parent.children.link(coll)
elif scaffold is not None:
scaffold._blender_collection.children.link(coll)
return coll


def create_network_cam(scaffold):
cam_data = bpy.data.cameras.new("Network Camera")
cam_data.lens = 18
cam = bpy.data.objects.new("Network Camera", cam_data)
cam.location = (9.69, -10.85, 12.388)
cam.rotation_euler = (0.6799, 0, 0.8254)
scaffold._blender_collection.objects.link(cam)

mat = bpy.data.materials.new(name=name)
mat.use_nodes = True
principal = mat.node_tree.nodes[0]
principal.inputs["Base Color"].default_value = color
principal.inputs["Alpha"].default_value = color[3]
mat.diffuse_color = color
mat.blend_method = "BLEND"

def create_lighting(scaffold):
if "BSB Solar" not in bpy.data.lights:
light_data = bpy.data.lights.new(name="BSB Solar", type="SUN")
light_data.energy = 1.5
light_object = bpy.data.objects.new(name="BSB Solar", object_data=light_data)
scaffold._blender_scene.collection.objects.link(light_object)
bpy.context.view_layer.objects.active = light_object
dg = bpy.context.evaluated_depsgraph_get()
dg.update()
return mat


def create_activity_material(scaffold, name, color, max_intensity=1.0, opacity=1):
def create_pulsar_material(name, color, max_intensity=1.0):
"""
Create a material capable of lighting up.
"""
mat = bpy.data.materials.new(name=name)
mat["pulsar"] = True
mat.use_nodes = True
mat.blend_method = "BLEND"

Expand Down Expand Up @@ -184,6 +107,39 @@ def create_activity_material(scaffold, name, color, max_intensity=1.0, opacity=1
return mat


def get_population(collection, cells, partial=False):
"""
Load or create a collection from a certain collection. Returns the loaded objects.
"""
index = {int(c["cell_id"]): c for c in collection.objects.values()}
if partial:
ncells = []
for cell in cells:
try:
cell.object = index[cell.id]
ncells.append(cell)
except KeyError:
pass
cells = ncells
else:
for cell in cells:
try:
cell.object = index[cell.id]
except KeyError:
raise Exception(
f"Cell {cell.id} missing from collection {collection.name}"
)
return BlenderPopulation(collection, cells)


def get_populations(collections, cells, partial=False):
"""
Zips a list of collections and a list of cell lists and passes them to
`get_population`. Returns the results as a list.
"""
return [get_population(c, p) for c, p in zip(collections, cells, partial=partial)]


def _diffkey(coll):
old_keys = set(coll.keys())

Expand All @@ -197,7 +153,7 @@ def diff():
return diff


def _create_ico_mesh(scaffold, name, radius):
def _create_ico_mesh(name, radius):
mesh_diff = _diffkey(bpy.data.meshes)
obj_diff = _diffkey(bpy.data.objects)
m = max(math.ceil(math.sqrt(radius)) - 1, 1)
Expand All @@ -206,7 +162,7 @@ def _create_ico_mesh(scaffold, name, radius):
mesh = mesh_diff().pop()
mesh.name = name
obj = obj_diff().pop()
scaffold._blender_scene.collection.objects.unlink(obj)
bpy.context.scene.collection.objects.unlink(obj)
return mesh


Expand Down

0 comments on commit 54bd8af

Please sign in to comment.