# Understanding the Scripting Capabilities and Plugin System in ArmorPaint

> Explore ArmorPaint's native C plugin system for extending the editor. Learn how to register custom importers, mesh utilities, and UI panels without high-level scripting.

- Repository: [Armory 3D/armorpaint](https://github.com/armory3d/armorpaint)
- Tags: deep-dive
- Published: 2026-09-13

---

**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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/plugins.c):

```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`](https://github.com/armory3d/armorpaint/blob/main/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:

```js
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()`:

```c
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:

```c
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`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/uv_unwrap/uv_unwrap.c) registers itself as:

```c
any_map_set(util_mesh_unwrappers, "uv_unwrap", proc_uv_unwrap);

```

To add a custom "smooth normals" utility:

```c
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:

```c
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`](https://github.com/armory3d/armorpaint/blob/main/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:

```c
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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/tab_plugins.c) file automatically invokes these callbacks when the Plugins tab is active, allowing your custom panels to appear in the interface.