Understanding the Scripting Capabilities and Plugin System in ArmorPaint

ArmorPaint does not embed high-level scripting languages like Python or Lua; instead, it provides a native C-based plugin architecture that allows developers to extend the editor at compile-time by registering custom importers, mesh utilities, and UI panels through a global plugin registry.

While traditional 3D applications often rely on interpreted scripting for extensibility, the armory3d/armorpaint repository implements a performance-first extension model using pure C. This system, controlled by the WITH_PLUGINS compile flag, enables deep integration with the editor's core systems but requires developers to compile extensions into the main binary or link them as external shared libraries.

Core Architecture: The C-Based Plugin System

The ArmorPaint plugin system centers on a global registry and three distinct extension mechanisms defined in paint/plugins/plugins.c. Unlike runtime scripting environments, all plugin code is compiled ahead of time, ensuring native execution speed and direct memory access to the editor's internal data structures.

The Global Registry and WITH_PLUGINS Flag

When building ArmorPaint with WITH_PLUGINS enabled (configured in base/project.js), the engine initializes a global plugin map g_plugins and several helper maps for specific functionality types:

  • import_texture_importers – Maps file extensions to texture loading functions
  • import_mesh_importers – Maps file extensions to mesh loading functions
  • util_mesh_unwrappers – Stores mesh processing utilities like UV unwrapping
  • g_plugins – Stores general plugin metadata and UI callbacks

Registration occurs during engine initialization via plugins_init() in paint/plugins/plugins.c:

void plugins_init() {
    // Texture importers
    any_map_set(import_texture_importers, "exr", import_exr);
    any_array_push(_path_texture_formats, "exr");
    
    // Mesh importers
    any_map_set(import_mesh_importers, "gltf", import_gltf_glb);
    any_array_push(_path_mesh_formats, "gltf");
    
    // Mesh utilities
    any_map_set(util_mesh_unwrappers, "uv_unwrap", proc_uv_unwrap);
}

Compile-Time vs. External Loading

Plugins can be integrated in two ways according to paint/plugins/project.js:

  1. Built-in: C files placed in the standard plugin directories are compiled directly into the main binary
  2. External: When WITH_EXTERNAL is defined, the build system checks for paint/plugins/external and links additional shared libraries:
let project = new Project("plugins");
project.add_cfiles("plugins.c");
if (fs_exists(os_cwd() + "/../paint/plugins/external")) {
    // external plugins will be compiled/linked here
}

Extending File Format Support with Custom Importers

The scripting capabilities in ArmorPaint allow developers to add support for non-native file formats by registering importer functions that conform to specific signatures.

Registering Texture Importers

To add support for a new texture format like .hdr, implement a loader function and register it in plugins_init():

static void *import_hdr(char *path) {
    buffer_t *b = data_get_blob(path);
    void *tex = io_hdr_parse(b->buffer, b->length);
    data_delete_blob(path);
    return tex;
}

// In plugins_init():
any_map_set(import_texture_importers, "hdr", import_hdr);
any_array_push(_path_texture_formats, "hdr");

Registering Mesh Importers

Similarly, mesh importers use the import_mesh_importers map:

any_map_set(import_mesh_importers, "custom_ext", import_custom_mesh);
any_array_push(_path_mesh_formats, "custom_ext");

Implementing Mesh Processing Utilities

Beyond file I/O, the plugin system exposes the util_mesh_unwrappers map for adding mesh manipulation tools accessible from the UI.

For example, the built-in UV unwrapper in paint/plugins/uv_unwrap/uv_unwrap.c registers itself as:

any_map_set(util_mesh_unwrappers, "uv_unwrap", proc_uv_unwrap);

To add a custom "smooth normals" utility:

static void proc_smooth_normals(void *mesh_ptr) {
    raw_mesh_t *mesh = (raw_mesh_t *)mesh_ptr;
    // Process mesh->nora array to smooth normals
}

// Register in plugins_init():
any_map_set(util_mesh_unwrappers, "smooth_normals", proc_smooth_normals);

Building Custom UI Extensions

The third extension point enables custom interface panels within the Plugins tab. This requires populating a plugin_t structure with a valid on_ui callback function.

The Plugin Structure and UI Callback

Define your plugin structure and UI drawing function:

static void my_plugin_ui(void) {
    if (ui_button(tr("Do Something"), UI_ALIGN_LEFT)) {
        do_something();
    }
}

// Registration:
plugin_t *p = malloc(sizeof(plugin_t));
p->name = "My UI Plugin";
p->on_ui = my_plugin_ui;
any_map_set(g_plugins, "my_ui_plugin", p);

UI Rendering in the Plugins Tab

The file paint/sources/ui/tab_plugins.c handles the rendering loop. When the Plugins tab is active, it iterates through g_plugins and invokes each registered on_ui callback:

string_array_t *keys = map_keys(g_plugins);
for (i32 i = 0; i < keys->length; ++i) {
    plugin_t *p = any_map_get(g_plugins, keys->buffer[i]);
    if (p->on_ui != NULL) {
        minic_ctx_call_fn(p->ctx, p->on_ui, NULL, 0);
    }
}

Because these callbacks use the existing UI API (ui_* functions), plugins render native interface elements that match the editor's visual style and input handling.

Summary

The scripting capabilities and plugin system in ArmorPaint provide high-performance extensibility through native C code rather than interpreted scripting languages:

  • Architecture: Pure C-based system controlled by the WITH_PLUGINS flag with global registries for importers, utilities, and UI plugins
  • Extension Points: Three main categories—file importers (textures and meshes), mesh processing utilities, and custom UI panels in the Plugins tab
  • Registration: Uses any_map_set() in paint/plugins/plugins.c to bind functions to extension registries like import_texture_importers and util_mesh_unwrappers
  • UI Integration: Plugins expose on_ui callbacks invoked by tab_plugins.c to render custom interface elements using the native UI API
  • Build Integration: Extensions compile into the main binary or link externally from paint/plugins/external when WITH_EXTERNAL is defined

Frequently Asked Questions

Does ArmorPaint support Python or Lua scripting?

No. ArmorPaint does not embed high-level scripting languages like Python or Lua. Instead, it provides a C-based plugin architecture that requires developers to write extensions in C, compile them ahead of time, and register them using the native API in paint/plugins/plugins.c.

How do I add support for a new file format in ArmorPaint?

Create a C function that loads your format and returns the appropriate data structure (texture or mesh), then register it in plugins_init() using any_map_set(). For textures, use the import_texture_importers map; for meshes, use import_mesh_importers. You must also add the file extension to the corresponding _path_texture_formats or _path_mesh_formats array.

Can plugins be loaded at runtime without recompiling ArmorPaint?

Runtime loading is only supported for external plugins compiled as shared libraries. When building with WITH_EXTERNAL defined, the build system in paint/plugins/project.js looks for libraries in paint/plugins/external and links them. However, standard usage requires recompiling the editor to register new importers, utilities, or UI extensions.

Where do I place code to render custom UI panels?

Implement an on_ui callback function in your plugin that uses the ui_* API functions, store it in a plugin_t structure, and register it in g_plugins via any_map_set(). The tab_plugins.c file automatically invokes these callbacks when the Plugins tab is active, allowing your custom panels to appear in the interface.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →