Blender Tutorial: Python
@Brian Kim (2023)
Overview
This tutorial will demonstrate how to use Python to automate the creation, placement, and coloring of objects in Blender. This is extremely useful for developers who are trying to create Blender objects using large and complex volumes of data, as this takes advantage of Python's built-in functions and libraries.
For this tutorial, we will be creating the heat map below:
Step 1
Create a new, empty Blender file, and delete the camera, cube, and light. Also, create a new Python file in the same directory as the CSV file. If you've never worked with Python before, this tutorial should not require any intense knowledge, but it will still be useful to read through the Python documentation. In this case, I'll just call my file 'blender.py'.
Step 2
In Blender, go to Edit > Preferences and open the Add-ons tab:
Find the add-on "Images as Planes" and install it.
Now, we can import the map as a plane into the Blender project:
Make sure to turn on "Viewport Shading" so you can see what's on the plane.
Step 3
In Blender, open the "Scripting" window. This is where Python code can be open and run:
Click the "Open" button at the top of the screen, and find your empty Python file:
Finally, make sure to save your Blender file in the same directory as your data.
Step 4
At this point, I will go through the relevant code that needs to be added.
First, include the following import statements:
import os
import math
from inspect import getsourcefile
from os.path import abspath
import csv
import bpy
import mathutils
from string import ascii_uppercase as alc
Add the following function. This is not a particularly complex function, but it is necessary to avoid overlap. Without going into too much detail, there are repeated data points in the CSV file, and this will let us avoid creating unnecessary columns
def same_cylinder(a, b):
if len(a) != len(b):
return False
similar = True
for i in range(len(a)):
if not math.isclose(a[i], b[i]):
similar = similar and False
return similar
The next function is very important, as it will control the creation of cylinders based on data from the CSV file. Feel free to take some time to read through the comments to understand what is going on in the code:
def create_cylinder(location, radius, height, name, color=(1, 0, 0, 1), letter=None):
bpy.ops.mesh.primitive_cylinder_add(
location=mathutils.Vector((location[0] - 0.5, location[1] - 0.5, height / 2.0)),
radius=radius,
depth=height,
)
bpy.data.objects["Cylinder"].name = name
obj = bpy.context.object
# Create a material
mat = bpy.data.materials.new("Color Material")
# Activate its nodes
mat.use_nodes = True
# Get the principled BSDF (created by default)
principled = mat.node_tree.nodes["Principled BSDF"]
# Assign the color
principled.inputs["Base Color"].default_value = color
# Assign the material to the object
obj.data.materials.append(mat)
if letter:
font_curve = bpy.data.curves.new(type="FONT", name="numberPlate")
font_curve.body = letter
obj = bpy.data.objects.new(name="Font Object", object_data=font_curve)
# Set scale and location
obj.location = mathutils.Vector(
(location[0] - 0.5, location[1] - 0.5, (height / 2.0) + 0.1)
)
obj.scale = (0.05, 0.05, 0.05)
obj.rotation_euler = mathutils.Euler((1.0, 0, 0), "XYZ")
obj.color = (0, 0, 1, 1)
bpy.context.scene.collection.objects.link(obj)
# Create a material
mat = bpy.data.materials.new("Color Material")
# Activate its nodes
mat.use_nodes = True
# Get the principled BSDF (created by default)
principled = mat.node_tree.nodes["Principled BSDF"]
# Assign the color
principled.inputs["Base Color"].default_value = color
# Assign the material to the object
obj.data.materials.append(mat)
Now, we move from creating functions to reading the CSV files. The first line of the code will set our directory to the same location as the Blender file. Be aware that the way that Blender runs Python means that you will need to fetch the directory location manually like, otherwise Blender will assume the directory is the same as the Blender executable.
dir_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
test_pumps = []
with open(dir_path + "/pump_map.csv") as fd:
rd = csv.reader(fd, delimiter=",")
for row in rd:
if len(row) > 0 and not any(
[
same_cylinder(test_pump, [float(row[0]), float(row[1])])
for test_pump in test_pumps
]
):
test_pumps.append([float(row[0]), float(row[1])])
The final line of code will bring this all together by going through the list of locations we have found and applying the cylinder creation function to each point:
for i, point in enumerate(test_pumps):
create_cylinder(point, 0.01, 0.2, f"pump_{i}", (0, 0, 1, 1), letter=str(i))
Step 5
Now we can finally run the program. If we look at the layout before, we see:
Back in Scripting, we hit the run button:
And we now have the following results:
Congratulations! You've successfully run a Python code file in Blender. While this is a pretty small data sample, using Python can be a huge time-saver when you're working on large data samples. As an example, and a possible next step, try to tackle this much larger dataset next.