Icons System#

This is the developers guide to cover the UVFlow icons system architecture, implementation details, and how to extend it.

Overview#

The UVFlow icons system provides automatic icon loading, code generation, and easy access to icons for UI and GPU drawing. It’s built on top of Blender’s preview system with additional GPU texture support for custom rendering.

Architecture Overview#

Core Components#

addon_utils/auto_code/    # Auto-code module
├── icons.py              # Core icon system implementation
└── __init__.py

---

├── icons.py              # Auto-generated icon access classes
└── assets/icons/         # Icon source directory
    ├── icons_data        # Cache database (auto-generated)
    └── [category]/       # Category folders

Automatic System Flow#

  1. Icon Discovery: Scans assets/icons/ directory (or the specified - relative to the addon source path - directory) for image files, including .png, .jpg, .jpeg files.

  2. Metadata Processing: Creates IconData objects with smart naming

  3. Code Generation: Uses templates to generate icons.py (or the specified filename) with enum classes

  4. Lazy Loading: Icons loaded only when accessed (example, user opens an addon panel that uses an icon)

  5. Caching: Preview collections and GPU textures cached for performance

Class Hierarchy#

IconsViewer                    # Base class for icon preview functionality
├── Icons                      # Auto-generated main class
    ├── MAIN(IconsEnum)        # Root directory icons
    ├── TOOLS(IconsEnum)       # tools/ directory icons
    └── [CATEGORY](IconsEnum)  # Custom category directories

IconsEnum                      # Base enum for icon collections
├── collection property        # Collection name
├── filepath property          # Full path to icon file
├── icon_id property          # Blender preview icon ID
├── gputex property           # GPU texture for custom drawing
└── draw_in_layout() method   # UI drawing helper

IconData                       # Internal metadata class
├── Smart name processing      # Converts filenames to identifiers
├── Modification tracking      # Detects file changes
└── Path management           # Handles file paths

Adding New Icons to the Addon#

Basic Icon Addition#

  1. Place icon files in the appropriate directory:

assets/icons/
├── tools/
│   └── my_new_tool.png       # Add your icon here
├── ui/
│   └── custom_button.jpg     # Or create new categories
└── logo.png                  # Root directory for main icons
  1. Regenerate icons code:

Nothing to worry about! When reloading the addon, the icons code will be regenerated automatically as it will detect the changes in the icons directory.

  1. Use the new icon:

from uvflow.icons import Icons

# Icon is automatically available
icon_id = Icons.TOOLS.MY_NEW_TOOL.icon_id
Icons.UI.CUSTOM_BUTTON.draw_in_layout(layout)

Supported File Formats#

  • PNG (recommended for UI icons)

  • JPG/JPEG

  • File size: depends on the use case. If for UI, Recommended 64x64 to 256x256 pixels. If for GPU drawing, Recommended 100x100 to 512x512 pixels.

  • Naming: Use descriptive names, special characters will be cleaned automatically. Check naming convention below.

Smart Naming Convention#

The system automatically converts filenames to valid Python identifiers:

# Filename → Generated Identifier
"[prefix] My Icon.png"      MY_ICON
"(category) tool-name.jpg"  TOOL_NAME
"icon.with.dots.png"        ICON_WITH_DOTS
"special chars!@#.png"      SPECIAL_CHARS

Recommended convention:

# Use lowercase letters and underscores between words!
"my_icon.png"  MY_ICON
"my_icon.jpg"  MY_ICON
"my_icon.jpeg"  MY_ICON

Custom Icon Categories and Organization#

Creating New Categories#

Categories are automatically created based on folder structure, as easy as that!

assets/icons/
├── tools/              # → Icons.TOOLS
├── ui_elements/        # → Icons.UI_ELEMENTS
├── materials/          # → Icons.MATERIALS
├── custom_category/    # → Icons.CUSTOM_CATEGORY
└── main_icons.png      # → Icons.MAIN

Nested Categories#

The system ONLY supports ONE level of nesting:

assets/icons/
├── tools/
│   ├── modeling/       # Files here go to Icons.TOOLS
│   │   └── tool.png    # → Icons.TOOLS.TOOL (with path: tools/modeling/tool.png)
│   └── sculpting/      # Files here go to Icons.TOOLS
│       └── brush.png   # → Icons.TOOLS.BRUSH (with path: tools/sculpting/brush.png)

Category Best Practices#

1. Logical Organization#

# Good: Clear category purposes
assets/icons/
├── tools/          # Tool-specific icons
├── ui/             # UI element icons
├── materials/      # Material preview icons
└── status/         # Status indicator icons

# Avoid: Mixed purposes
assets/icons/
├── misc/           # Too generic
├── stuff/          # Unclear purpose

2. Consistent Naming#

# Good: Consistent naming within categories
tools/
├── cut_uv.png
├── unwrap_tool.png
├── pack_islands.png

# Avoid: Inconsistent naming
tools/
├── CutUV.png
├── unwrap-tool.jpg
├── PackIslands.PNG

3. Icon Sizing#

# Recommended sizes for different uses
UI_ICONS = (32, 32)      # Small UI elements
TOOL_ICONS = (64, 64)    # Tool buttons
PREVIEW_ICONS = (128, 128)  # Material/object previews
GPU_ICONS = (256, 256)   # Custom GPU drawing

API Documentation#

IconsEnum Class#

Base enum class for all icon collections.

class IconsEnum(Enum):
    """Base enum class for icon collections with automatic loading and caching."""

Properties#

collection: str#
@property
def collection(self) -> str:
    """Get the collection name (class name)."""
    return self.__class__.__name__
filepath: str | None#
@property
def filepath(self) -> str | None:
    """Get the full file path to the icon."""
    path: Path = GLOBALS.ADDON_SOURCE_PATH / self.value
    if path.exists() and path.is_file():
        return str(path)
    return None
identifier: str#
@property
def identifier(self) -> str:
    """Get the unique identifier for this icon."""
    return self.name
filename: str#
@property
def filename(self) -> str:
    """Get the filename of the icon."""
    return os.path.basename(self.filepath)
icon_id: int#
@property
def icon_id(self) -> int:
    """Get the Blender preview icon ID for UI usage."""
    collection: previews.ImagePreviewCollection = icon_previews.get(self.collection, None)
    if collection is None:
        collection: previews.ImagePreviewCollection = previews.new()
        icon_previews[self.collection] = collection
    elif preview := collection.get(self.identifier, None):
        return preview.icon_id
    filepath = self.filepath
    if filepath is None:
        return 0
    return collection.load(self.identifier, filepath, 'IMAGE', force_reload=True).icon_id
gputex: GPUTexture#
@property
def gputex(self) -> GPUTexture:
    """Get the GPU texture for custom drawing operations."""
    # Loads and caches GPU texture at GPUTEX_ICON_SIZE (100x100)
    # Returns cached texture if already loaded
    # Creates new texture from image data if not cached

Methods#

draw_in_layout(layout, scale=2.0)#
def draw_in_layout(self, layout: UILayout, scale: float = 2.0):
    """Draw the icon in a Blender UI layout.
    
    Args:
        layout (UILayout): Blender UI layout to draw in
        scale (float): Icon scale factor (default: 2.0)
    """
    if icon_id := self.icon_id:
        layout.template_icon(icon_id, scale=scale)
__call__() -> tuple[str, str]#
def __call__(self) -> tuple[str, str]:
    """Get identifier and filename as tuple."""
    return self.identifier, self.filename

IconsViewer Class#

Base class for icon preview functionality.

class IconsViewer:
    """Base class providing icon preview and organization functionality."""

Class Methods#

draw_icons_in_layout(layout)#
@classmethod
def draw_icons_in_layout(cls, layout: UILayout):
    """Draw all icon collections in a layout for preview/debugging.
    
    Args:
        layout (UILayout): Blender UI layout to draw in
        
    Creates organized boxes showing all available icons grouped by collection.
    """
    box = layout.box()
    box.label(text="I c o n s   P r e v i e w s :")
    for icons_enum in cls.__dict__.values():
        if inspect.isclass(icons_enum):
            collection = box.column(align=True)
            collection.box().row(align=True).label(text="Collection '%s'" % icons_enum.__name__)
            grid_layout = collection.box().column(align=True).grid_flow(columns=0)
            for icon in icons_enum:
                icon.draw_in_layout(grid_layout)

IconData Class#

Internal class for icon metadata management.

class IconData:
    """Internal class for managing icon metadata and processing."""
    
    def __init__(self, icon_path: Path):
        """Initialize icon data from file path.
        
        Args:
            icon_path (Path): Path to the icon file
        """

Properties#

idname: str#

Smart-processed identifier name with special character cleanup.

name: str#

Original filename.

ext: str#

File extension.

date: float#

File modification timestamp for cache invalidation.

filepath: str#

Full path to the icon file.

Code Generation Functions#

generate_icons_py(filename='icons.py', _icons_path=None)#

def generate_icons_py(filename: str = 'icons.py', _icons_path: Optional[str] = None):
    """Generate the icons.py file with automatic icon discovery.
    
    Args:
        filename (str): Output filename (default: 'icons.py')
        _icons_path (str, optional): Custom icons directory path
        
    Features:
        - Scans icons directory for supported formats
        - Creates category-based enum classes
        - Tracks file modifications for incremental updates
        - Generates clean, type-safe Python code
    """

Usage Patterns#

Basic Icon Usage#

from uvflow.icons import Icons

# Get icon for UI elements
@Register.UI.PANEL.VIEW3D
class MyPanel:
    def draw_ui(self, context, layout):
        # Use icon in operator
        layout.operator("uvflow.my_tool", 
                       icon_value=Icons.TOOLS.MY_TOOL.icon_id)
        
        # Draw icon directly
        Icons.TOOLS.MY_TOOL.draw_in_layout(layout, scale=1.5)

Custom GPU Drawing#

import gpu
from gpu_extras.batch import batch_for_shader

def draw_custom_overlay():
    """Example of using icons in custom GPU drawing."""
    texture = Icons.UI.OVERLAY_ICON.gputex
    
    # Create shader and batch
    shader = gpu.shader.from_builtin('2D_IMAGE')
    vertices = [(0, 0), (100, 0), (100, 100), (0, 100)]
    uvs = [(0, 0), (1, 0), (1, 1), (0, 1)]
    
    batch = batch_for_shader(shader, 'TRI_FAN', {
        "pos": vertices,
        "texCoord": uvs
    })
    
    # Draw with icon texture
    shader.bind()
    shader.uniform_sampler("image", texture)
    batch.draw(shader)

Icon Browser Panel#

@Register.UI.PANEL.VIEW3D
class IconBrowserPanel:
    label = "Icon Browser"
    
    def draw_ui(self, context, layout):
        # Draw all available icons for debugging/preview
        Icons.draw_icons_in_layout(layout)

Performance Considerations#

Caching Strategy#

  1. Preview Collections: Cached per category, loaded on first access

  2. GPU Textures: Cached individually, created on demand

  3. File Modification: Tracked to invalidate cache when files change

  4. Memory Management: Automatic cleanup on addon disable

Optimization Tips#

# Good: Access icon_id once and reuse
icon_id = Icons.TOOLS.MY_TOOL.icon_id
for i in range(100):
    layout.operator("my.op", icon_value=icon_id)

# Avoid: Repeated property access
for i in range(100):
    layout.operator("my.op", icon_value=Icons.TOOLS.MY_TOOL.icon_id)

Handling Issues#

Potential Issues#

Invalid File Formats#

# Only PNG, JPG, JPEG are supported!
# Other formats will be ignored during generation

Permission Issues#

# Ensure write permissions for:
# - assets/icons/ directory
# - icons.py file
# - icons_data cache file

Debugging#

# Enable debug output
from uvflow.addon_utils.debug import print_debug

# Check icon loading
print_debug("Icon filepath:", Icons.TOOLS.MY_TOOL.filepath)
print_debug("Icon ID:", Icons.TOOLS.MY_TOOL.icon_id)

# Preview all icons
Icons.draw_icons_in_layout(layout)  # Shows all loaded icons

This icons system provides a robust foundation for managing addon icons with minimal setup and maximum flexibility. The automatic code generation and caching ensure good performance while the enum-based interface provides type safety and ease of use.