How to Add Properties In Your Blender Addon UI

GOAL

Today’s goal is to develop 3 types of Blender addons with properties, showing properties in Panel or Properties Editor as below.

Note: Addons in this article are the simplest examples and not practical.

Environment

Blender 2.83(LTS)
Windows10

What is property?

At first, I recommend to read “Properties” in Blender manual and “Property Definitions (bpy.props)” in Blender Python API documentation. And check “Operator Property” for details about how to use properties in Addon.

Properties

The Properties shows and allows editing of many active data, including the active scene and object.

from Blender 2.93 Manual

Property has information about any type of data such as Object, Material, Scene and so on. For example, Objects have properties such as “location”, “scale”, “active_material”, “animation_data”, “bound_box” and more. (Be careful that some of them are internal or non-editable!) You can add custom properties to the existing data type or create property group in Addon.

Example of properties

1. Change Existing property

Change the value stored in Blender data with UI. For example, change the size of the object, change the color of material.

bl_info = {
        "name": "Existing Property Test",
        "description": "test addon to change existing property",
        "version": (1, 0),
        "blender": (2, 83, 0),
        "category": "3D View",
        }
import bpy

class PROPTEST_PT_my_panel(bpy.types.Panel):
    bl_label = "Prop Test Addon1"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'

    def draw(self, context):
        layout = self.layout
        col = layout.column(align=True)
        # existing property
        if "Cube" in bpy.data.objects:
            col.prop(bpy.data.objects["Cube"], "scale")
            
            active_material = bpy.data.objects["Cube"].active_material
            col.prop(active_material, "diffuse_color")
            
        if "Light" in bpy.data.lights:
            col.prop(bpy.data.lights["Light"], "type")

def register():
    bpy.utils.register_class(PROPTEST_PT_my_panel)
def unregister():
    bpy.utils.unregister_class(PROPTEST_PT_my_panel)

if __name__ == '__main__':
    register()

The following is the panel UI for properties.

col = layout.column(align=True)
if "Cube" in bpy.data.objects:
    col.prop(bpy.data.objects["Cube"], "scale")        # Scale of obj

    active_material = bpy.data.objects["Cube"].active_material
    col.prop(active_material, "diffuse_color")         # Diffuse Color of material

    if "Light" in bpy.data.lights:
        col.prop(bpy.data.lights["Light"], "type")     # Type of light

Properties

You can see the properties of each data type in blender python API documentation as below.

2. Add property to the existing data

This is the method of adding custom property to the existing data such as object, material and so on.

def register():
    bpy.types.<TYPE>.prop_name1 = bpy.props.TypeOfProperty()
    bpy.types.<TYPE>.prop_name2= bpy.props.TypeOfProperty()

def unregister():
    del bpy.types.<TYPE>.prop_name1
    del bpy.types.<TYPE>.prop_name2

For example, the following is an addon to show the distance from current object to target. A new property “target_obj” and non-editable “distance” are added to bpy.types.Object.

Add parameters to Object
Example of distance output

The following is the source code.

bl_info = {
        "name": "Add Property Test",
        "description": "test addon to add properties to Object",
        "version": (1, 0),
        "blender": (2, 80, 0),
        "category": "3D View",
        }

import bpy
import math
from bpy.app.handlers import persistent

def distance(pos1, pos2):
    return math.sqrt((pos1.x-pos2.x)**2+(pos1.y-pos2.y)**2+(pos1.z-pos2.z)**2)

@persistent
def distance_update(scene):
    if hasattr(bpy.context.object, "target_obj"):
        target_obj = bpy.context.object.target_obj
        if target_obj:
            target_pos = target_obj.location
            current_pos = bpy.context.object.location
            new_distance = distance(target_pos, current_pos)
            if bpy.context.object.distance != new_distance:
                bpy.context.object.distance = distance(target_pos, current_pos)
        else:
            bpy.context.object.target_obj = None
            bpy.context.object.distance = 0

bpy.app.handlers.depsgraph_update_post.append(distance_update)

def register():
    bpy.types.Object.target_obj = bpy.props.PointerProperty(name="TargetObj", type=bpy.types.Object) # add prop "target_obj"
    bpy.types.Object.distance = bpy.props.FloatProperty(name="Distance") # add prop "distance"

def unregister():
    del bpy.types.Object.target_obj
    del bpy.types.Object.distance

if __name__ == '__main__':
    register()

Use the value of properties in custom operator

If you’d like to use properties in global not in each objects, use bpy.types.Scene instead. If you’d like to use values of properties in custom operator, see “How To Pass Arguments To Custom Operator In Blender Python”.

# add parameter "count_x" and "count_y" to bpy.types.Scene
bpy.types.Scene.count_x = bpy.props.IntProperty()
bpy.types.Scene.count_y = bpy.props.IntProperty()

3. Custom property group

If there are many properties or set of properties, it is better to use property group instead of properties registered independently. This is the method of adding, setting and getting values of property group.

class CusomPropGroup(bpy.types.PropertyGroup):
    # define properties here
    # prop_name1: bpy.props.TypeOfProperty()
    # prop_name2: bpy.props.TypeOfProperty()

def register():
    bpy.utils.register_class(CusomPropGroup)
    bpy.types.Scene.custom_prop_name= bpy.props.PointerProperty(type=CusomPropGroup)

def unregister():
    bpy.utils.unregister_class(CusomPropGroup)
    del bpy.types.Scene.custom_prop_name

Th following code is the example addon using custom property group.

import bpy


class PROPTEST3_add(bpy.types.Operator):
    bl_idname = 'color_obj.add'
    bl_label = "Add Color Obj"
    bl_options = {'REGISTER'}

    def execute(self, context):
        obj_type = context.scene.colorObjProp.object_select
        for i in range(5):
            color = getattr(context.scene.colorObjProp, "color_id{}".format(i))
            mat = bpy.data.materials.new("test_material")
            mat.diffuse_color = list(color) + [1.0]
            x = 2.0 + 2.0 * i
            if obj_type == "CUBE":
                obj = bpy.ops.mesh.primitive_cube_add(enter_editmode=False, align='WORLD', location=(x, 0, 0))
            elif obj_type == "SPHERE":
                obj = bpy.ops.mesh.primitive_uv_sphere_add(enter_editmode=False, align='WORLD', location=(x, 0, 0))
            bpy.context.object.data.materials.append(mat)
        return {'FINISHED'}


class PROPTEST3_PT_panel(bpy.types.Panel):
    bl_label = "Add Color Object"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'

    def draw(self, context):
        layout = self.layout
        col = layout.column()
        col.prop(context.scene.colorObjProp, "object_select")
        row = col.row()
        for i in range(5):
            row.prop(context.scene.colorObjProp, "color_id{}".format(i), text="")
        col.operator("color_obj.add", text="Add Colored Obj")


class colorObjProp(bpy.types.PropertyGroup): # add property group
    obj_items = [
        ("CUBE", "Cube", '', 'MESH_CUBE', 0),
        ("SPHERE", "Sphere", '', 'MESH_UVSPHERE', 1),
    ]
    object_select: bpy.props.EnumProperty(
        items=obj_items,
        default="CUBE"
    )

    def get_color(i):
        return bpy.props.FloatVectorProperty(
            name="Color",
            description="Set Color for the Palette",
            subtype="COLOR",
            default=(0.5, 0.2 * i, 0.5),
            max=1.0, min=0.0
        )

    color_id0: get_color(0)
    color_id1: get_color(1)
    color_id2: get_color(2)
    color_id3: get_color(3)
    color_id4: get_color(4)


classes = [
    PROPTEST3_add, PROPTEST3_PT_panel
]


def register():
    bpy.utils.register_class(colorObjProp)
    bpy.types.Scene.colorObjProp = bpy.props.PointerProperty(type=colorObjProp)
    for c in classes:
        bpy.utils.register_class(c)


def unregister():
    bpy.utils.unregister_class(colorObjProp)
    del bpy.types.Scene.colorObjProp
    for c in classes:
        bpy.utils.unregister_class(c)


if __name__ == '__main__':
    register()

The following is panel of this addon.

When click “Add Colored Obj” button, 5 colored objects are added to the scene.