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 functionsimport_mesh_importers– Maps file extensions to mesh loading functionsutil_mesh_unwrappers– Stores mesh processing utilities like UV unwrappingg_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:
- Built-in: C files placed in the standard plugin directories are compiled directly into the main binary
- External: When
WITH_EXTERNALis defined, the build system checks forpaint/plugins/externaland 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_PLUGINSflag 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()inpaint/plugins/plugins.cto bind functions to extension registries likeimport_texture_importersandutil_mesh_unwrappers - UI Integration: Plugins expose
on_uicallbacks invoked bytab_plugins.cto render custom interface elements using the native UI API - Build Integration: Extensions compile into the main binary or link externally from
paint/plugins/externalwhenWITH_EXTERNALis 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →