Adding Objects

import bpy
bpy.app.version_string # current blender version
'5.3.0 Beta'
import sys
from pathlib import Path

# The kernel runs inside Blender (not in the notebook folder), so add the docs helpers to sys.path
sys.path.append(str(Path.home() / "projects/bpy-gallery/docs/utils"))
from gallery_utils import ASSETS, fresh_scene, render_result

# Set render resolution
bpy.context.scene.render.resolution_x = 500
bpy.context.scene.render.resolution_y = 200
bpy.ops.mesh.primitive_monkey_add(size=2.5, location=(0, 0, 0))

#bpy.ops.object.gpencil_add(location=(0, 0, 0), type='STROKE') # Blender 4.2
bpy.ops.object.grease_pencil_add(location=(0, 0, 0), type="STROKE") # Blender 4.3.1
gpencil_object = bpy.context.active_object
gpencil_object.scale = (3, 3, 3)

bpy.ops.mesh.primitive_uv_sphere_add(radius=1.5, location=(0, 0, 0)) 

bpy.ops.mesh.primitive_cone_add(radius1=1, depth=2, location=(0, 0, 0))  

bpy.ops.mesh.primitive_cylinder_add(radius=1, depth=2, location=(0, 0, 0)) 

bpy.ops.mesh.primitive_torus_add(major_radius=2, minor_radius=0.3, location=(0,0, 0))

bpy.ops.mesh.primitive_ico_sphere_add(radius=1, location=(0, 0, 0)) 

bpy.ops.object.text_add(location=(-4, 0, 0))
text = bpy.context.active_object
text.data.body = "Hello 😄"
text.scale = (2, 2, 2)

bpy.ops.mesh.primitive_plane_add(size=3, location=(0, 0, 0))  

bpy.ops.mesh.primitive_circle_add(radius=2.5, location=(0, 0, 0), fill_type='NGON')

bpy.ops.object.empty_add(type='PLAIN_AXES', location=(4, 0, 0)) # (This is not shown in the image, but it is there)

bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0)) 

bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0)) 
light = bpy.data.objects.new("Light", bpy.data.lights.new("Light", 'POINT'))
light.location = (3, 0, 2)
light.data.energy = 8000
bpy.context.collection.objects.link(light)

Applying Modifiers

bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0)) 
cube = bpy.context.active_object
bevel_modifier = cube.modifiers.new(name="Bevel", type='BEVEL')
bevel_modifier.width = 0.4  # Adjust the bevel width
bevel_modifier.segments = 5  # Number of segments for a smoother bevel

# Optionally apply the modifier if needed (not necessary for visualization)
# bpy.ops.object.modifier_apply(modifier="Bevel")

bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.active_object

# Create the subdivision surface modifier and change it to 'SIMPLE'
subsurf_modifier = cube.modifiers.new(name="Subdivision", type='SUBSURF')
subsurf_modifier.subdivision_type = 'SIMPLE'  # Change to 'SIMPLE' instead of 'CATMULL_CLARK'
subsurf_modifier.levels = 4  # Viewport subdivision level
subsurf_modifier.render_levels = 4  # Render subdivision level

displace_modifier = cube.modifiers.new(name="Displace", type='DISPLACE')
displace_modifier.strength = 0.5  # Adjust the displacement strength

texture = bpy.data.textures.new("DisplaceTexture", type='CLOUDS')
texture.noise_scale = 1  # Reduce the noise scale to make it more detailed
displace_modifier.texture = texture  # Assign the texture to the modifier

Settings

# current blender version
bpy.app.version_string
'5.3.0 Beta'
# current blender version
bpy.context.scene.render.engine
'BLENDER_EEVEE'
bpy.context.scene.render.resolution_x, bpy.context.scene.render.resolution_y
(500, 200)
bpy.context.scene.name
'Scene'
bpy.context.active_object.name
'Cube'
bpy.context.active_object.location
Vector((0.0, 0.0, 0.0))
bpy.context.collection.objects.keys()
['Camera', 'Sun', 'Cube']
bpy.context.collection.objects.values()
[bpy.data.objects['Camera'], bpy.data.objects['Sun'], bpy.data.objects['Cube']]

Using colors

# Add a cube
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.active_object

# Create a green material
mat = bpy.data.materials.new(name="GreenMaterial")
mat.diffuse_color = (0, 1, 0, 1)  # viewport color only (Solid mode)
mat.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = (0, 1, 0, 1)  # render color

# Apply material to the cube
cube.data.materials.clear()  # Clear any existing materials
cube.data.materials.append(mat)

# colors with nodes

bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.active_object

# Create and assign a blue material to the cube
mat = bpy.data.materials.new(name="BlueMaterial")
# mat.use_nodes = True  # outdated: materials always use nodes since Blender 5.0
bsdf = mat.node_tree.nodes.get('Principled BSDF')
bsdf.inputs['Base Color'].default_value = (0, 0, 1, 1)  # Blue color (R, G, B, A)

# Apply material to the cube
cube.data.materials.clear()  # Clear any existing materials
cube.data.materials.append(mat)

bpy.ops.mesh.primitive_plane_add(size=6, location=(0, 0, 0))
# plane = bpy.context.object # only blender 4.1
plane = bpy.context.active_object #blender 4.3

material = bpy.data.materials.new(name="ImageMaterial")
# material.use_nodes = True  # outdated: materials always use nodes since Blender 5.0
bsdf = material.node_tree.nodes["Principled BSDF"]

tex_image = material.node_tree.nodes.new('ShaderNodeTexImage')

tex_image.image = bpy.data.images.load(str(ASSETS / "cute_dog.jpg"))

material.node_tree.links.new(bsdf.inputs['Base Color'], tex_image.outputs['Color'])
plane.data.materials.append(material)

Setting Up Shaders

Node trees can be written as code with nodebpy. Install it once into Blender’s Python with pip:

import subprocess

subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "nodebpy"])
Requirement already satisfied: nodebpy in ./Applications/Blender.app/Contents/Resources/5.3/python/lib/python3.13/site-packages (520.35.1)
[notice] A new release of pip is available: 26.0.1 -> 26.2.1

[notice] To update, run: /Applications/Blender.app/Contents/Resources/5.3/python/bin/python3.13 -m pip install --upgrade pip
0
import importlib.metadata

importlib.metadata.version("nodebpy")
'520.34.0'

This builds the following shader tree:
image.png

from nodebpy import shader as s

bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.active_object

with s.material("YellowToOrangeGradient") as tree:
    _ = (
        s.GradientTexture(gradient_type="LINEAR")
        >> s.ColorRamp(items=[(0.0, (1, 1, 0, 1)), (1.0, (1, 0.3, 0, 1))])  # yellow -> orange
        >> s.PrincipledBSDF()
        >> s.MaterialOutput()
    )

cube.data.materials.clear()
cube.data.materials.append(tree.material)

Setting up Geometry Nodes

The same works for geometry node trees:

from nodebpy import geometry as g

bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))

with g.tree("AnotherTree", collapse=True, arrange="sugiyama") as tree:
    rotation = (
        g.RandomValue.vector(min=-1, seed=2)
        >> g.AlignRotationToVector()
        >> g.RotateRotation(rotate_by=g.AxisAngleToRotation(angle=0.3))
    )

    _ = (
        tree.inputs.integer("Count", 10)
        >> g.Points(position=g.RandomValue.vector(min=-1))
        >> g.InstanceOnPoints(instance=g.Cube(), rotation=rotation)
        >> g.SetPosition(
            position=g.Position() * 2.0 + (0, 0.2, 0.3),
            offset=(0, 0, 0.1),
        )
        >> g.RealizeInstances()
        >> g.InstanceOnPoints(g.Cube(), instance=...)
        >> tree.outputs.geometry("Instances")
    )

# Apply to active object
obj = bpy.context.active_object
modifier = obj.modifiers.new(name="AnotherTree", type="NODES")
modifier.node_group = bpy.data.node_groups["AnotherTree"]

Save File

You can any time also save your Blender file like this:

path = str(Path.home() / 'Downloads/temp_scene.blend')
bpy.ops.wm.save_as_mainfile(filepath=path) 
Info: Saved as "temp_scene.blend"
{'FINISHED'}

Load file

#bpy.ops.wm.open_mainfile(filepath="donut.blend") # this will loose kernel connection

filepath = str(ASSETS / "donut.blend")

# Import all objects from the .blend file
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
    data_to.objects = data_from.objects

# Link imported objects to the current scene
for obj in data_to.objects:
    if obj is not None:
        bpy.context.collection.objects.link(obj)

# Render through the camera that came with donut.blend
bpy.context.scene.camera = next(obj for obj in data_to.objects if obj.type == 'CAMERA')

Choose render engine

bpy.context.scene.render.engine = "BLENDER_WORKBENCH" 

bpy.context.scene.render.resolution_x = 500
bpy.context.scene.render.resolution_y = 200

bpy.context.scene.render.engine = "BLENDER_EEVEE"

bpy.context.scene.render.engine = "CYCLES"
bpy.context.scene.cycles.samples = 10

Manipulate geometry nodes

# old 
# bpy.data.objects['ICINGLong'].modifiers["GeometryNodes"]["Socket_3"] = 0.1

#new:
bpy.data.objects["ICINGLong"].modifiers["GeometryNodes"].properties.inputs.Socket_3.value = 0.1

bpy.data.objects["ICINGLong"].modifiers["GeometryNodes"].properties.inputs.Socket_3.value = 2

bpy.data.objects["ICINGLong"].modifiers["GeometryNodes"].properties.inputs.Socket_3.value = 10