Skip to content

Blender 4.1: Python API

Python 3.11

Python has been upgraded to version 3.11, matching the VFX platform 2024.

Enum ID Properties

Support for enum items has been added to integer properties. 92cf9dd2f2

There is no support yet for editing enum items through the UI (see commit message for technical reasons).

Enum items can be added to an integer property using the id_properties_ui python method. The as_dict method can be used to retrieve a list of enum items of a property.

my_object["my_prop"] = 2
ui_data = my_object.id_properties_ui("my_prop")
    ("TOMATOES", "Tomatoes", "Solanum lycopersicum"),
    ("CUCUMBERS", "Cucumbers", "Cucumis sativus"),
    ("RADISHES", "Radishes", "Raphanus raphanistrum"),

The existing conveniency wrapper function supports it as well:

from rna_prop_ui import rna_idprop_ui_create

rna_idprop_ui_create(my_object, "my_prop", default=2, items=[
    ("TOMATOES", "Tomatoes", "Solanum lycopersicum"),
    ("CUCUMBERS", "Cucumbers", "Cucumis sativus"),
    ("RADISHES", "Radishes", "Raphanus raphanistrum"),

Int properties with enum items are shown as a dropdown button in the UI.

Layout Panels

Blender has new so called "layout panels". These panels are defined inside of draw functions and don't require extra registration. This significantly reduces the amount of code required to create collapsable sections in a panel. (f824476bd5, 8896446f7e, ddd06eeb8f)

import bpy
from bpy.props import BoolProperty

class LayoutDemoPanel(bpy.types.Panel):
    bl_label = "Layout Panel Demo"
    bl_idname = "SCENE_PT_layout_panel"
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "scene"

    def draw(self, context):
        layout = self.layout
        scene = context.scene


        header, panel = layout.panel("my_panel_id", default_closed=False)
        header.label(text="Hello World")
        if panel:

        header, panel = layout.panel_prop(scene, "show_demo_panel")
        header.label(text="My Panel")
        if panel:
            panel.prop(scene, "frame_start")
            panel.prop(scene, "frame_end")


bpy.types.Scene.show_demo_panel = BoolProperty(default=False)

Breaking Changes


  • foreach_get and foreach_set have been optimized for cases where internal storage is 8, 16 or 64 bit. (PR#115761)
  • foreach_set now performs more accurate bounds checks, and raises a TypeError in cases where the value was previously silently written incorrectly.

Light Probes

  • The Lightprobe type items have been renamed. It affects bpy.types.LightProbe.type, and bpy.ops.object.lightprobe_add().
    • GRID -> VOLUME
  • show_data has been deprecated. Use use_data_display instead.
  • Each LightProbe now has its own data_display_size property.


  • Sculpt mask values are stored in a generic attribute (f2bcd73bd2).
    • The name is ".sculpt_mask", with the FLOAT type.
    • Accessing, adding, and removing masks is done in a simpler way:
      • The Mesh.vertex_paint_mask property returns the attribute directly, rather than a collection.
      • The Mesh.vertex_paint_mask_ensure() and Mesh.vertex_paint_mask_remove() functions add and remove the attribute.
  • The Mesh auto_smooth property has been replaced by a modifier node group asset (89e3ba4e25).
    • use_auto_smooth is removed. Face corner normals are now used automatically if there are mixed smooth vs. not smooth tags. Meshes now always use custom normals if they exist.
    • auto_smooth_angle is removed. Replaced by a modifier (or operator) controlling the "sharp_edge" attribute. This means the mesh itself (without an object) doesn't know anything about automatically smoothing by angle anymore.
    • create_normals_split, calc_normals_split, and free_normals_split are removed, and are replaced by the simpler Mesh.corner_normals collection property. Since it gives access to the normals cache, it is automatically updated when relevant data changes.
    • MeshLoop.normal is now a read-only property. Custom normals should be created by normals_split_custom_set or normals_split_custom_set_from_vertices.


  • The displacement_method property has moved from to bpy.types.Material a001cf9f2b


  • Strip transform filter type SUBSAMPLING_3x3 was renamed to BOX.
    It affects bpy.types.SequenceTransform.filter. (PR#117584)

View Layer

  • Add Scene.view_layers.move() method to reorder view layers. (PR#117037)


  • Some nodes (e.g. Store Named Attribute) use dynamic socket types now instead of having all socket types exist at the same time. This means that doing something like node.outputs[some_index] does not work the same for those nodes anymore. Instead use node.outputs[some_socket_identifier] which is more reliable. (8149678d5e)

Grease Pencil Brushes

  • The property brush.gpencil_settings.direction has been replaced by brush.direction. (998514af7b)


  • Add methods (0061f2f650)
  • Add ID.session_uid read-only property (c68b22cfdf)
  • Add NodesModifierBake.bake_id read-only property (7149424087)
  • Add NodesModifierBake.node read-only property (65b722bc30)
  • Add update handler that runs when translations change (8564e03cdf)
  • Add optional "frame" & "tile_index" arguments to Image.scale() (3d60209d3d)
  • Add ShapeKey.points property (7d77caab9b)
  • Add access to ordering of links in multi-input socket (8f36281787)

Shape Key Locks

Shape Keys can now be locked by the user to prevent accidental edits in sculpt and edit mode (b350d7a4c3). Add-ons that implement operators that transform the active shape without topology changes, or invoke other built-in operators that already perform the check, may consider adding these checks to their own code.

Built-in operator examples:

  • mesh.vertices_smooth does the check, because it transforms the active shape.
  • mesh.subdivide doesn't check, because it changes topology.
  • object.transform_apply doesn't check, because it applies the same transformation to all shapes (the active shape key choice doesn't matter).
  • mesh.polybuild_extrude_at_cursor_move checks, because internally it invokes another operator that does the check.

Example of implementing the check:

from bpy_extras.object_utils import object_report_if_active_shape_key_is_locked

def execute(self, context):
  if object_report_if_active_shape_key_is_locked(context.object, self):
    return {'CANCELLED'}