# How ArmorPaint Dynamically Loads Extensions Like io_gltf, io_fbx, and io_exr via plugins/plugins.c

> Discover how ArmorPaint dynamically loads extensions like io_gltf, io_fbx, and io_exr using its plugin system in plugins.c. Learn about runtime dispatch for seamless file parsing.

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

---

**ArmorPaint's plugin architecture uses registration tables and hash maps in [`plugins/plugins.c`](https://github.com/armory3d/armorpaint/blob/main/plugins/plugins.c) to map file extensions to C function pointers, enabling runtime dispatch of parsers like `io_gltf_parse`, `io_fbx_parse`, and `io_exr_parse` without hardcoded conditional logic.**

ArmorPaint is an open-source 3D texture painting application developed by armory3d. Understanding how the **ArmorPaint plugin system dynamically loads extensions** at runtime reveals a sophisticated data-driven dispatch mechanism that decouples file format support from the core engine using static compilation combined with runtime hash map lookups.

## The Runtime Loading Architecture

The dynamic loading process centers on `plugins_init()` inside [`paint/plugins/plugins.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/plugins.c). This function executes during application startup to establish a lookup table mapping file extensions to their corresponding parser implementations.

### Initializing Format Lists

First, the system initializes dynamic string arrays that track supported extensions. The initialization calls `path_texture_formats()` and `path_mesh_formats()` to create these arrays, which the UI queries to display available import options in file dialogs.

```c
path_texture_formats();
path_mesh_formats();

```

### Registering Parser Functions

Next, `plugins_init()` populates two global hash maps with function pointers: `import_texture_importers` for image formats and `import_mesh_importers` for geometry. The maps use file extensions as keys (e.g., `"exr"`, `"fbx"`) and store pointers to the parser functions as values.

For the EXR texture and FBX mesh loaders, the registration looks like this:

```c
any_map_set(import_texture_importers, "exr", import_exr);
any_map_set(import_mesh_importers, "fbx", import_fbx);

```

The actual parser implementations reside in dedicated plugin directories. For example, `import_exr` lives in [`paint/plugins/io_exr/io_exr.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/io_exr/io_exr.c), while `io_fbx_parse` is defined in [`paint/plugins/io_fbx/io_fbx.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/io_fbx/io_fbx.c). The GLTF parsers follow the same pattern in their respective source files.

### Populating Extension Arrays

Finally, the system pushes the extension strings onto public format arrays. This step ensures the UI can enumerate supported types without accessing internal hash maps directly:

```c
any_array_push(_path_texture_formats, "exr");
any_array_push(_path_mesh_formats, "fbx");

```

## Runtime Dispatch Mechanism

When a user opens a file, the core engine extracts the extension and queries the appropriate importer map. This **data-driven dispatch** eliminates hardcoded switch statements and conditional chains.

```c
char *ext = string_last_index_of(filename, '.') + 1;

if (any_map_has(import_mesh_importers, ext)) {
    void *(*loader)(char *) = any_map_get(import_mesh_importers, ext);
    void *mesh = loader(filename);
    // mesh now holds the loaded geometry
}

```

If the extension exists in the map, the stored function pointer executes immediately. The engine remains agnostic about which concrete parser runs; it simply invokes the generic pointer retrieved via `any_map_get()`.

## Implementing a New Extension

Adding support for a new file format requires three straightforward modifications to the **ArmorPaint dynamic plugin loading** system:

1. **Implement the parser** using the signature `void *my_parser(char *path)` in a new source file under `paint/plugins/`.
2. **Register the mapping** in `plugins_init()` by calling `any_map_set()` to link the extension string to your function.
3. **Update the format arrays** by pushing the extension onto `_path_mesh_formats` or `_path_texture_formats` so the UI recognizes the new type.

For example, to add Alembic (`.abc`) support:

```c
/* Declare the parser in plugins.c */
void *import_abc(char *path);

/* Registration inside plugins_init() */
any_map_set(import_mesh_importers, "abc", import_abc);
any_array_push(_path_mesh_formats, "abc");

```

## Compile-Time Configuration Flags

The plugin system respects two conditional compilation flags that control how **ArmorPaint loads extensions at runtime**:

- **`WITH_PLUGINS`** – When undefined, the entire plugin initialization system is excluded from the build, creating a minimal binary without import/export capabilities.
- **`WITH_EXTERNAL`** – Enables support for third-party shared libraries via `external_init()`. When set, `plugins_init()` invokes `external_init()` to load additional plugins compiled as separate shared objects (`.so` or `.dll` files) rather than statically linked code.

```c
#ifdef WITH_EXTERNAL
void external_init();
#endif

void plugins_init() {
    // ... core registrations ...
    #ifdef WITH_EXTERNAL
    external_init();
    #endif
}

```

## Summary

- **`plugins_init()`** in [`paint/plugins/plugins.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/plugins.c) serves as the central registration hub executed at application startup.
- **Hash maps** (`import_mesh_importers`, `import_texture_importers`) store function pointers keyed by extension strings like `"gltf"`, `"fbx"`, and `"exr"`.
- **Runtime dispatch** occurs through `any_map_get()`, retrieving the appropriate parser without hardcoded conditional logic.
- **Parser implementations** reside in dedicated directories such as [`io_fbx/io_fbx.c`](https://github.com/armory3d/armorpaint/blob/main/io_fbx/io_fbx.c) and [`io_exr/io_exr.c`](https://github.com/armory3d/armorpaint/blob/main/io_exr/io_exr.c), maintaining separation between the core engine and format-specific code.
- **Compile-time flags** (`WITH_PLUGINS`, `WITH_EXTERNAL`) optionally disable the system or enable external shared library loading for proprietary extensions.

## Frequently Asked Questions

### How does ArmorPaint determine which parser to use for a specific file extension?

ArmorPaint extracts the file extension from the path and queries the corresponding hash map using `any_map_has()`. If the extension exists in `import_mesh_importers` or `import_texture_importers`, the system retrieves the function pointer via `any_map_get()` and executes it immediately. This constant-time lookup eliminates the need for lengthy conditional chains in the file loading code.

### Can I add support for a new file format without modifying the core engine source code?

Yes. You only need to implement a parser function matching the signature `void *parser(char *path)`, add a registration line in `plugins_init()` inside [`paint/plugins/plugins.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/plugins.c), and push the extension string onto the appropriate format array. The core engine automatically recognizes the new format through the hash map dispatch system without requiring changes to its loading logic or UI code.

### What is the difference between the `WITH_PLUGINS` and `WITH_EXTERNAL` compile flags?

`WITH_PLUGINS` controls whether the entire plugin registration system compiles into the binary. When undefined, `plugins_init()` and all importer registrations are excluded, creating a minimal build. `WITH_EXTERNAL` specifically enables loading third-party shared libraries at runtime through `external_init()`, allowing proprietary or optional plugins to exist as separate `.so` or `.dll` files rather than being statically linked into the main executable.

### Where are the actual io_gltf, io_fbx, and io_exr parser functions defined?

The parser implementations reside in their respective plugin directories within `paint/plugins/`. For example, `io_fbx_parse` is implemented in [`paint/plugins/io_fbx/io_fbx.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/io_fbx/io_fbx.c), and `io_exr_parse` lives in [`paint/plugins/io_exr/io_exr.c`](https://github.com/armory3d/armorpaint/blob/main/paint/plugins/io_exr/io_exr.c). These functions are compiled into the main binary and registered at startup via function pointers, making the loading "dynamic" in terms of runtime dispatch rather than dynamic linking of shared libraries.