Quickstart Blender Scripting Tutorial: The Plane and the Ball.

Adrian Szatmari
6 min readJul 29, 2021

Hello everyone, using only python code — this is not a python tutorial — , we will be scripting this glorious scene of all scenes:

I wanted to script in order to have more control over my flow, and to be able to reproduce my results. I did not want to create an add-on, it seems most scripting tutorials are focused on that, thus here we are.

bpy.data

Starting from a clean default cube scene, you can see this:

  • In orange you have bpy.data.objects:
>>> bpy.data.objects['Camera']
>>> bpy.data.objects['Cube']
>>> bpy.data.objects['Light']
  • In green you have different data types:
>>> bpy.data.meshes['Cube']
>>> bpy.data.cameras['Camera']
>>> bpy.data.lights['Light']
  • In red you have materials types:
>>> bpy.data.materials['Materials']

The code follows the visual structure, the object ‘Cube’ will have as data the mesh ‘Cube’. bpy.data.objects['Cube'].data is the actual mesh of object ‘Cube”, and bpy.data.objects['Cube'].data.materials['Material'] is the ‘Material’ material of the ‘Cube’ mesh of the ‘Cube’ object.
However, the mesh ‘Cube’ is also here bpy.data.meshes['Cube'] and the material ‘Material’ is also here bpy.data.materials['Material'] .

Let’s make a sphere

First erase the cube and leave the camera and the light:

import bpy
import mathutils
try:
cube = bpy.data.objects['Cube']
bpy.data.objects.remove(cube, do_unlink=True)
except:
print("Object bpy.data.objects['Cube'] not found")

bpy.ops.outliner.orphans_purge()

Add some spheres (bad way):

bpy.ops.mesh.primitive_uv_sphere_add(
segments=64,
ring_count=32,
radius=1.0,
location=(0,0,0))
bpy.context.selected_objects[0].name = "MySphere1"
bpy.ops.mesh.primitive_uv_sphere_add(
segments=64,
ring_count=32,
radius=1.0,
location=(2,0,0))
bpy.context.selected_objects[0].name = "MySphere2"

ICKY!!! The mesh of the sphere object does not get updated. Everyone knows that catastrophe is imminent, we near the precipice. Let’s fix this!

def new_sphere(mylocation, myradius, myname):
bpy.ops.mesh.primitive_uv_sphere_add(
segments=64,
ring_count=32,
radius=myradius,
location=mylocation)
current_name = bpy.context.selected_objects[0].name
sphere = bpy.data.objects[current_name]
sphere.name = myname
sphere.data.name = myname + "_mesh"
return
new_sphere((0,0,0), 1.0, "MySphere1")
new_sphere((2,0,0), 1.0, "MySphere2")

Ahhhhhh, much much better! ❤ Order and discipline ❤ Moreover, if deleting the cube is not in try/except , then the first time you run the script it will work, but the second time it will give an error! Remember to program defensively :)

Let’s make a plane

I will restart on my end — complete script will be at the end — , let’s add a function to add a plane, and make a sphere and a floor.

def new_plane(mylocation, mysize, myname):
bpy.ops.mesh.primitive_plane_add(
size=mysize,
calc_uvs=True,
enter_editmode=False,
align='WORLD',
location=mylocation,
rotation=(0, 0, 0),
scale=(0, 0, 0))
current_name = bpy.context.selected_objects[0].name
plane = bpy.data.objects[current_name]
plane.name = myname
plane.data.name = myname + "_mesh"
return
new_sphere((0,0,0), 1.0, "MySphere")
new_plane((0,0,-1), 10, "MyFloor")

Ahhhhh, most satisfying! Mmmm, wait what?

Let’s change the shading to smooth (although it does not matter for the final render).

sphere = bpy.data.objects['MySphere']for poly in sphere.data.polygons:
poly.use_smooth = True

Let’s add some materials

Let’s give our scene some colors and textures. I think that the sphere could be tacky plastic pink and the floor could be tacky gold.

MAT_NAME = "TackyPlastic"
bpy.data.materials.new(MAT_NAME)
material = bpy.data.materials[MAT_NAME]
material.use_nodes = True
material.node_tree.nodes['Principled BSDF'].inputs['Roughness'].default_value = 0.2
material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = (1,0,1,1)
if len(sphere.data.materials.items()) != 0:
sphere.data.materials.clear()
else:
sphere.data.materials.append(material)

It is imperative to enable to use of nodes with material.use_nodes = True otherwise you will not be able to add nodes to your material (of course you could enable by it by hand also, but we want everything automatic).
NOTE! the code is material.node_tree.nodes['Principled BSDF'].etc just the medium editor has formated it.
An object can have more than one material associated to it, here we make sure that it has only one, i.e. ‘TackyPlastic’.

You can do ctrl-c on any node and it gives you the name by which you would need to call it in script, here copy paste gives bpy.ops.node.add_node(type="ShaderNodeTexWhiteNoise",use_transform=True

Finally let’s do the same for the floor.

MAT_NAME = "TackyGold"
bpy.data.materials.new(MAT_NAME)
material = bpy.data.materials[MAT_NAME]
material.use_nodes = True
material.node_tree.nodes['Principled BSDF'].inputs['Roughness'].default_value = 0.1
material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = (0.75,0.5,0.05,1)
material.node_tree.nodes['Principled BSDF'].inputs['Metallic'].default_value = 0.9
if len(plane.data.materials.items()) != 0:
plane.data.materials.clear()
else:
plane.data.materials.append(material)

Note that the world is a bit dark. We can change that with

bpy.data.worlds['World'].node_tree.nodes["Background"].inputs[0].default_value = (0.7, 0.7, 0.7, 1)

Let’s move the camera

I care about processing and rendering scenes from script, so I need to be able to move the camera around from script as well. It goes in the same vain as the rest, but note that bpy.data.cameras['Camera'] refers to the intrinsic camera properties, whereas bpy.data.objects['Camera'] refers to the camera in the scene. You can see this via:

>>> bpy.data.cameras['Camera'].type
'PERSP'
>>> bpy.data.objects['Camera'].type
'CAMERA'

In script, we can simply set or increment the position like so (you could also rotate the camera via bpy.data.objects['Camera'].rotation_euler ):

cam = bpy.data.objects['Camera'] 
cam.location = cam.location + mathutils.Vector((0.1, 0, 0))

And finally, tadaaaaa *small tap dance*:

Complete script is here:

import bpy
import mathutils
# Remove stuff
try:
cube = bpy.data.objects['Cube']
bpy.data.objects.remove(cube, do_unlink=True)
except:
print("Object bpy.data.objects['Cube'] not found")

bpy.ops.outliner.orphans_purge()
# Declare constructors
def new_sphere(mylocation, myradius, myname):
bpy.ops.mesh.primitive_uv_sphere_add(
segments=64,
ring_count=32,
radius=myradius,
location=mylocation)
current_name = bpy.context.selected_objects[0].name
sphere = bpy.data.objects[current_name]
sphere.name = myname
sphere.data.name = myname + "_mesh"
return
def new_plane(mylocation, mysize, myname):
bpy.ops.mesh.primitive_plane_add(
size=mysize,
calc_uvs=True,
enter_editmode=False,
align='WORLD',
location=mylocation,
rotation=(0, 0, 0),
scale=(0, 0, 0))
current_name = bpy.context.selected_objects[0].name
plane = bpy.data.objects[current_name]
plane.name = myname
plane.data.name = myname + "_mesh"
return
# Create objects
new_sphere((0,0,0), 1.0, "MySphere")
new_plane((0,0,-1), 10, "MyFloor")
sphere = bpy.data.objects['MySphere']
plane = bpy.data.objects['MyFloor']
# Smoothen sphere
for poly in sphere.data.polygons:
poly.use_smooth = True
# Create TackyPlastic material
MAT_NAME = "TackyPlastic"
bpy.data.materials.new(MAT_NAME)
material = bpy.data.materials[MAT_NAME]
material.use_nodes = True
material.node_tree.nodes['Principled BSDF'].inputs['Roughness'].default_value = 0.2
material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = (1,0,1,1)
# Associate TackyPlastic to sphere
if len(sphere.data.materials.items()) != 0:
sphere.data.materials.clear()
else:
sphere.data.materials.append(material)
# Create TackyGold material
MAT_NAME = "TackyGold"
bpy.data.materials.new(MAT_NAME)
material = bpy.data.materials[MAT_NAME]
material.use_nodes = True
material.node_tree.nodes['Principled BSDF'].inputs['Roughness'].default_value = 0.1
material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = (0.75,0.5,0.05,1)
material.node_tree.nodes['Principled BSDF'].inputs['Metallic'].default_value = 0.9
# Associate TackyGold to plane
if len(plane.data.materials.items()) != 0:
plane.data.materials.clear()
else:
plane.data.materials.append(material)
# Lighten the world light
bpy.data.worlds['World'].node_tree.nodes["Background"].inputs[0].default_value = (0.7, 0.7, 0.7, 1)
# Move camera
cam = bpy.data.objects['Camera']
cam.location = cam.location + mathutils.Vector((0.1, 0, 0))

Thanks for reading

I hope this was useful. If anyone has any criticism or ways to do things better, don’t hesitate to share in the comments below. Thank you for reading and thank you for your time. Happy coding ❤

--

--