Register an Operator#

This document describes the comprehensive operator registration system in the UVFlow addon_utils module, which provides multiple operator types and registration methods for different use cases.

Overview#

The operator registration system automatically handles:

  1. Automatic ID generation from class names

  2. Label creation from class names or custom labels

  3. Property integration with the Property system

  4. UI drawing through draw_ui methods

  5. Blender registration and cleanup

Basic Operator Types#

Generic Operator#

The most common operator type for simple actions:

from ..addon_utils import Register, Property

@Register.OPS.GENERIC
class TestOperator:
    def action(self, context):
        print(f"{context.active_object.name}: Hello, I'm the active object! :-)")
        # No need to return anything! The system handles it automatically

Features:

  • Simple execution model

  • Automatic ID: uvflow.test_operator

  • Automatic label: “Test Operator”

  • No return required in action() method

Operator with Properties and Invoke Dialog#

For operators that need user input before execution:

from ..addon_utils import Register, Property

@Register.OPS.INVOKE_PROPS
class TestOperatorInvokeProps:
    test_filepath: Property.FILEPATH()
    test_color: Property.COLOR_RGB(default=(1, 0, 1))
    test_bool: Property.BOOL()

    def action(self, context):
        print("Hello!", self.test_filepath, self.test_color, self.test_bool)
        # No need to return anything! Keep it simple!

Features:

  • Automatically shows property dialog when invoked

  • Properties are accessible as self.property_name

  • Perfect for file operations, settings, etc.

Advanced Operator Types#

Operator Calling Methods#

Class-based Operator Calling#

Use the operator class directly for cleaner code:

@Register.OPS.GENERIC
class HelperOperator:
    my_prop: Property.STRING(default="helper")
    
    def action(self, context):
        print(f"Helper called with: {self.my_prop}")

@Register.OPS.GENERIC
class MainOperator:
    def action(self, context):
        # Method 1: Direct execution
        HelperOperator.run()
        
        # Method 2: With properties
        HelperOperator.run(my_prop="custom_value")
        
        # Method 3: Invoke with dialog (for INVOKE_PROPS operators)
        HelperOperator.run_invoke(my_prop="dialog_value")

Traditional bpy.ops Calling#

Still available for compatibility:

@Register.OPS.GENERIC
class TraditionalCaller:
    def action(self, context):
        # Call another operator
        bpy.ops.uvflow.my_operator_1('INVOKE_DEFAULT')
        
        # Call with properties
        bpy.ops.uvflow.my_operator_2('INVOKE_DEFAULT', my_prop='custom_value')

Return Values and Control Flow#

Action Method Returns#

@Register.OPS.GENERIC
class SimpleOperator:
    def action(self, context):
        # Simple case - no return needed
        print("Operation completed")
        # System automatically returns {'FINISHED'}

@Register.OPS.GENERIC
class CustomReturnOperator:
    def execute(self, context):
        """Override execute for custom return handling"""
        try:
            self.action(context)
            return {'FINISHED'}
        except Exception as e:
            self.report({'ERROR'}, str(e))
            return {'CANCELLED'}
    
    def action(self, context):
        # Your logic here
        if some_condition:
            raise Exception("Something went wrong")

Advanced Modal Examples#

Interactive Drawing Tool#

@Register.OPS.MODAL.GENERIC
class DrawingTool:
    use_raycast_info = True
    raycast_type = 'SCENE'
    
    def modal_start(self, context, event):
        self.points = []
        self.drawing = False
    
    def modal_update(self, context, event, mouse):
        if event.type == 'LEFTMOUSE':
            if event.value == 'PRESS':
                self.drawing = True
                if self.raycast_info.hit:
                    self.points.append(self.raycast_info.location.copy())
            elif event.value == 'RELEASE':
                self.drawing = False
        
        elif event.type == 'RIGHTMOUSE' and event.value == 'PRESS':
            return OpsReturn.FINISH
        
        elif event.type == 'ESC':
            return OpsReturn.CANCEL
        
        return None
    
    def modal__mousemove(self, context, mouse):
        if self.drawing and self.raycast_info.hit:
            self.points.append(self.raycast_info.location.copy())
    
    def modal_finish(self, context, event):
        print(f"Created path with {len(self.points)} points")
        # Create mesh from points, etc.

Object Transformation Tool#

@Register.OPS.MODAL.GENERIC
class TransformTool:
    use_raycast_info = True
    raycast_type = 'OBJECT'
    
    def modal_start(self, context, event):
        self.target_object = context.active_object
        if not self.target_object:
            return OpsReturn.CANCEL
        
        self.start_location = self.target_object.location.copy()
        self.start_mouse = self.mouse.current.copy()
    
    def modal_update(self, context, event, mouse):
        if event.type == 'LEFTMOUSE' and event.value == 'RELEASE':
            return OpsReturn.FINISH
        elif event.type == 'ESC':
            # Restore original position
            self.target_object.location = self.start_location
            return OpsReturn.CANCEL
        
        return None
    
    def modal__mousemove(self, context, mouse):
        # Transform object based on mouse movement
        offset = mouse.offset
        self.target_object.location.x = self.start_location.x + offset.x * 0.01
        self.target_object.location.y = self.start_location.y + offset.y * 0.01
        
        # Update viewport
        context.area.tag_redraw()

Performance Monitor Tool#

@Register.OPS.MODAL.GENERIC
class PerformanceMonitor:
    def modal_start(self, context, event):
        self.frame_count = 0
        self.start_time = time.time()
        self.start_modal_timer(context, 1.0)  # Update every second
    
    def modal__timer(self, context, event, mouse):
        current_time = time.time()
        elapsed = current_time - self.start_time
        
        if elapsed >= 1.0:
            fps = self.frame_count / elapsed
            print(f"FPS: {fps:.2f}")
            
            # Reset counters
            self.frame_count = 0
            self.start_time = current_time
        
        return None
    
    def modal__mousemove(self, context, mouse):
        self.frame_count += 1
    
    def modal_update(self, context, event, mouse):
        if event.type == 'ESC':
            return OpsReturn.CANCEL
        return None

Property Integration#

All Property Types#

@Register.OPS.INVOKE_PROPS
class ComprehensivePropertiesOperator:
    # Basic types
    string_prop: Property.STRING(default="Default text")
    int_prop: Property.INT(default=5, min=0, max=100)
    float_prop: Property.FLOAT(default=1.0, min=0.0, max=10.0)
    bool_prop: Property.BOOL(default=True)
    
    # Advanced types
    filepath_prop: Property.FILEPATH()
    dirpath_prop: Property.DIRPATH()
    color_prop: Property.COLOR_RGB(default=(1, 0, 0))
    vector_prop: Property.VECTOR_3D(default=(0, 0, 1))
    
    # Enum
    enum_prop: Property.ENUM(
        items=[
            ('OPTION1', 'Option 1', 'First option'),
            ('OPTION2', 'Option 2', 'Second option'),
        ],
        default='OPTION1'
    )
    
    def action(self, context):
        print(f"String: {self.string_prop}")
        print(f"Int: {self.int_prop}")
        print(f"Float: {self.float_prop}")
        print(f"Bool: {self.bool_prop}")
        print(f"File: {self.filepath_prop}")
        print(f"Color: {self.color_prop}")
        print(f"Vector: {self.vector_prop}")
        print(f"Enum: {self.enum_prop}")

Automatic ID and Label Generation#

ID Generation Rules#

Class names are automatically converted to operator IDs:

  • TestOperatoruvflow.test_operator

  • UVUnwrapAdvanceduvflow.uv_unwrap_advanced

  • MyCustomTooluvflow.my_custom_tool

Label Generation Rules#

Labels are automatically created from class names:

  • TestOperator → “Test Operator”

  • UVUnwrapAdvanced → “UV Unwrap Advanced”

  • MyCustomTool → “My Custom Tool”

Error Handling and Return Values#

Standard Return Values#

@Register.OPS.GENERIC
class ProperReturnOperator:
    def execute(self, context):
        """Override execute for custom error handling"""
        try:
            self.action(context)
            return {'FINISHED'}
        except Exception as e:
            self.report({'ERROR'}, f"Operation failed: {str(e)}")
            return {'CANCELLED'}
    
    def action(self, context):
        # Your operation logic
        if some_error_condition:
            raise Exception("Something went wrong")

Integration with UI System#

Drawing in Layouts#

Operators can be drawn in UI layouts using the draw_in_layout method:

# In a panel or menu
MyOperator.draw_in_layout(layout, text="Custom Text", icon='MODIFIER')
MyOperator.draw_in_layout(layout, op_props={'my_prop': 'custom_value'})

Best Practices#

1. Use Descriptive Class Names#

# Good
@Register.OPS.GENERIC
class UVUnwrapWithSeams:
    pass

# Avoid
@Register.OPS.GENERIC
class Op1:
    pass

2. Handle Context Properly#

@Register.OPS.GENERIC
class ContextAwareOperator:
    def action(self, context):
        if not context.active_object:
            self.report({'ERROR'}, "No active object")
            return  # No need to return specific values
        
        if context.active_object.type != 'MESH':
            self.report({'ERROR'}, "Active object must be a mesh")
            return
        
        # Proceed with operation

3. Use Modal Features Effectively#

@Register.OPS.MODAL.GENERIC
class EffectiveModalOperator:
    use_raycast_info = True
    raycast_type = 'SCENE'
    
    def modal_start(self, context, event):
        # Initialize modal state
        self.operation_data = {}
    
    def modal_update(self, context, event, mouse):
        # Handle main events
        if event.type == 'ESC':
            return OpsReturn.CANCEL
        return None
    
    def modal__mousemove(self, context, mouse):
        # Handle mouse movement efficiently
        if mouse.enough_movement:  # Built-in movement threshold
            # Update based on mouse movement
            pass
    
    def modal_finish(self, context, event):
        # Clean up and finalize
        print("Operation completed successfully")

4. Use Class-based Operator Calling#

@Register.OPS.GENERIC
class ModernOperator:
    def action(self, context):
        # Modern approach - cleaner and more maintainable
        SomeHelperOperator.run(param="value")
        
        # Instead of:
        # bpy.ops.uvflow.some_helper_operator('INVOKE_DEFAULT', param="value")

Automatic Cleanup#

The operator registration system automatically handles:

  • Blender registration/unregistration

  • Property cleanup

  • UI integration cleanup

  • Modal state cleanup

  • Timer cleanup (for modal operators)

No manual cleanup is required in most cases.

This operator registration system provides a powerful, flexible foundation for creating all types of Blender operators with minimal boilerplate code and maximum functionality, while providing nice scalability as developers can create new operator types with ease and add them to the system.