UI Append Utility#

This document describes the UI append utility that allows you to add custom draw functions to existing Blender UI classes (Menus, Panels, Headers) without overriding them completely.

Overview#

The Register.UI.APPEND decorator provides a clean way to extend existing Blender UI elements by appending or prepending custom draw functions. This is particularly useful when you want to add menu items, buttons, or other UI elements to built-in Blender menus and panels.

How it Works#

The UI append system:

  1. Registers your draw function to be called when the target UI class draws

  2. Appends or prepends your function to the existing draw calls

  3. Preserves all original UI functionality

  4. Automatically manages registration and cleanup

  5. Handles function signature conversion for Blender’s UI system

Basic Usage#

Appending to Existing Menus#

import bpy
from bpy.types import UILayout, Context
from ..addon_utils import Register

@Register.UI.APPEND(bpy.types.VIEW3D_MT_object)
def draw_custom_menu_items(context: Context, layout: UILayout) -> None:
    layout.separator()
    layout.operator("my_addon.custom_operator", text="Custom Action")
    layout.operator("my_addon.another_operator", text="Another Action")

Prepending to Existing Menus#

@Register.UI.APPEND(bpy.types.VIEW3D_MT_mesh, prepend=True)
def draw_mesh_tools_top(context: Context, layout: UILayout) -> None:
    layout.operator("my_addon.priority_tool", text="Priority Tool")
    layout.separator()

Real-world Example from UVFlow#

Here’s how UVFlow uses the append utility to add a submenu to the 3D View object menu:

import bpy
from bpy.types import UILayout, Context
from ..addon_utils import Register
from ..operators.op_checker import ToggleUvCheckerMaterial
from ..operators.op_material import MaterialSelectObjects, MaterialSelectFaces
from ..operators.op_unwrap import UVUnwrap
from ..operators.op_pack import UVPack
from ..operators.op_texel_density import UVSetScale

# First, create a custom menu class
@Register.UI.MENU
class VIEW3D_MT_object_uvflow:
    label = 'UV Flow'
    
    def draw_ui(self, context: Context, layout: UILayout) -> None:
        UVUnwrap.draw_in_layout(layout, label="Unwrap UVs")
        if bpy.app.version > (3, 6, 0):
            UVPack.draw_in_layout(layout, label="Pack UVs")
        UVSetScale.draw_in_layout(layout)
        layout.separator()
        ToggleUvCheckerMaterial.draw_in_layout(layout, label='Enable UV Checkers', 
                                               op_props={'enable': True, 'auto': False})
        ToggleUvCheckerMaterial.draw_in_layout(layout, label='Disable UV Checkers', 
                                               op_props={'enable': False, 'auto': False})
        layout.separator()
        MaterialSelectObjects.draw_in_layout(layout, label='Select Material Objects')
        MaterialSelectFaces.draw_in_layout(layout, label='Select Material Faces')

# Then, append the submenu to the existing object menu
@Register.UI.APPEND(bpy.types.VIEW3D_MT_object)
def draw_uvflow_menu(context: Context, layout: UILayout) -> None:
    layout.separator()
    VIEW3D_MT_object_uvflow.draw_in_layout(layout, label="UVFlow")

Function Signature#

Your append function must follow this signature:

def your_draw_function(context: Context, layout: UILayout) -> None:
    # Your UI drawing code here
    pass

Parameters:#

  • context: The current Blender context

  • layout: The UI layout object to draw into

  • Return: Must return None

Common Use Cases#

Adding Operators to Menus#

@Register.UI.APPEND(bpy.types.VIEW3D_MT_mesh)
def draw_mesh_operators(context: Context, layout: UILayout) -> None:
    layout.separator()
    layout.operator("mesh.my_custom_tool", text="Custom Mesh Tool")
    layout.operator("mesh.another_tool", text="Another Tool", icon='MODIFIER')

Adding Conditional Menu Items#

@Register.UI.APPEND(bpy.types.VIEW3D_MT_object)
def draw_conditional_items(context: Context, layout: UILayout) -> None:
    obj = context.active_object
    if obj and obj.type == 'MESH':
        layout.separator()
        layout.label(text="Mesh Tools:")
        layout.operator("object.mesh_specific_tool")
    elif obj and obj.type == 'CURVE':
        layout.separator()
        layout.label(text="Curve Tools:")
        layout.operator("object.curve_specific_tool")

Adding Submenus#

# Create a custom submenu first
@Register.UI.MENU
class MY_MT_custom_submenu:
    label = "My Tools"
    
    def draw_ui(self, context: Context, layout: UILayout) -> None:
        layout.operator("my_addon.tool_one")
        layout.operator("my_addon.tool_two")
        layout.operator("my_addon.tool_three")

# Then append it to an existing menu
@Register.UI.APPEND(bpy.types.VIEW3D_MT_object)
def draw_submenu(context: Context, layout: UILayout) -> None:
    layout.separator()
    MY_MT_custom_submenu.draw_in_layout(layout, text="My Tools")

Adding to Panels#

@Register.UI.APPEND(bpy.types.VIEW3D_PT_tools_active)
def draw_panel_extension(context: Context, layout: UILayout) -> None:
    if context.mode == 'EDIT_MESH':
        box = layout.box()
        box.label(text="Custom Tools")
        box.operator("mesh.custom_operator")

Append vs Prepend#

Append (Default)#

  • Adds your content after the original UI content

  • Most common use case

  • Good for adding supplementary tools

@Register.UI.APPEND(bpy.types.VIEW3D_MT_object)  # Appends by default
def draw_at_bottom(context: Context, layout: UILayout) -> None:
    layout.operator("my_addon.tool")

Prepend#

  • Adds your content before the original UI content

  • Useful for priority tools or important actions

  • Use sparingly to avoid disrupting user expectations

@Register.UI.APPEND(bpy.types.VIEW3D_MT_object, prepend=True)
def draw_at_top(context: Context, layout: UILayout) -> None:
    layout.operator("my_addon.priority_tool")
    layout.separator()

Best Practices#

1. Use Separators Appropriately#

@Register.UI.APPEND(bpy.types.VIEW3D_MT_object)
def draw_with_separator(context: Context, layout: UILayout) -> None:
    layout.separator()  # Add visual separation
    layout.operator("my_addon.tool")

3. Use Context-Sensitive Drawing#

@Register.UI.APPEND(bpy.types.VIEW3D_MT_object)
def draw_context_sensitive(context: Context, layout: UILayout) -> None:
    obj = context.active_object
    if not obj:
        return
    
    layout.separator()
    if obj.type == 'MESH':
        layout.operator("my_addon.mesh_tool")
    elif obj.type == 'CURVE':
        layout.operator("my_addon.curve_tool")

4. Provide Clear Labels and Icons#

@Register.UI.APPEND(bpy.types.VIEW3D_MT_object)
def draw_with_icons(context: Context, layout: UILayout) -> None:
    layout.separator()
    layout.operator("my_addon.unwrap", text="Smart Unwrap", icon='MOD_UVPROJECT')
    layout.operator("my_addon.pack", text="Pack UVs", icon='PACKAGE')

Common Target UI Classes#

3D Viewport Menus#

  • bpy.types.VIEW3D_MT_object - Object mode context menu

  • bpy.types.VIEW3D_MT_mesh - Edit mesh mode context menu

  • bpy.types.VIEW3D_MT_edit_mesh_context_menu - Right-click context menu in edit mode

  • bpy.types.VIEW3D_MT_add - Add menu (Shift+A)

Editor Menus#

  • bpy.types.IMAGE_MT_image - Image editor menu

  • bpy.types.OUTLINER_MT_context_menu - Outliner context menu

  • bpy.types.NODE_MT_add - Node editor add menu

Panels#

  • bpy.types.VIEW3D_PT_tools_active - Active tool panel

  • bpy.types.PROPERTIES_PT_context_object - Object properties panel

Automatic Cleanup#

The append system automatically handles registration and cleanup:

def register():
    # UI appends are automatically registered
    pass

def unregister():
    # UI appends are automatically unregistered
    pass

The system maintains a list of all appended functions and properly removes them when the addon is disabled, ensuring no leftover UI elements remain.

Error Handling#

The append system is robust and handles common issues:

  • Missing target class: Gracefully skips if the target UI class doesn’t exist

  • Function errors: Isolates your function errors from breaking the original UI

  • Cleanup: Ensures proper removal even if errors occur during registration

Limitations#

  1. Function signature: Must match the expected (context, layout) signature

  2. Order dependency: Multiple appends to the same target are processed in registration order

This append utility provides a clean, maintainable way to extend Blender’s UI without the complexity of full UI overrides, making it perfect for adding addon-specific tools and menus to existing Blender interfaces.