How ArmorPaint's Plugin Architecture Handles GLTF, FBX, EXR, PSD, TIFF, and SVG Imports Using the Iron Engine

ArmorPaint uses a C-only plugin system that registers format-specific importer callbacks during initialization, routing file data through dedicated parsers that convert GLTF, FBX, EXR, PSD, TIFF, and SVG into Iron engine-native meshes and GPU textures.

The armory3d/armorpaint repository implements a lightweight plugin architecture that abstracts file format complexities from the core engine. This design allows the Iron engine—the low-level graphics and data layer—to consume standardized raw_mesh_t structures and GPU textures regardless of the original file format, enabling seamless import of industry-standard 3D and image formats.

Plugin Registration and Callback Architecture

At startup, plugins_init() in paint/plugins/plugins.c establishes a registration system using two global callback maps. These maps associate file extensions with C function pointers that handle the actual parsing.

The system distinguishes between mesh importers and texture importers:

  • import_mesh_importers stores callbacks returning raw_mesh_t* for 3D geometry
  • import_texture_importers stores callbacks returning GPU textures for image data
any_map_set(import_mesh_importers, "gltf", import_gltf_glb);
any_map_set(import_mesh_importers, "fbx",  import_fbx);
any_map_set(import_texture_importers, "exr",  import_exr);
any_map_set(import_texture_importers, "psd",  import_psd);
any_map_set(import_texture_importers, "tiff", import_tiff);
any_map_set(import_texture_importers, "svg",  import_svg);

Source: paint/plugins/plugins.c – lines 55-75 & 68-74

File-to-Memory Pipeline

Every importer follows a consistent pattern to retrieve data from ArmorPaint's virtual file system. Rather than accessing disk directly, importers work with memory blobs managed by the engine.

The standard workflow uses data_get_blob() to load files into buffer_t structures, passes the raw bytes to format-specific parsers, then releases the temporary storage:

buffer_t *b = data_get_blob(path);
void *res = io_<fmt>_parse(b->buffer, b->length, …);
data_delete_blob(path);

This pattern appears universally across import_exr, import_psd, import_tiff, import_svg, import_gltf_glb, and import_fbx implementations.

Source: paint/plugins/plugins.c – e.g., import_exr lines 49-54, import_fbx lines 43-48

Format-Specific Parser Implementations

Each supported format resides in its own subdirectory under paint/plugins/, containing C implementations that transform binary buffers into engine-compatible structures.

GLTF and GLB Import

The GLTF importer leverages the single-file cgltf library (paint/plugins/io_gltf/cgltf.c). The parser extracts vertex positions, normals, UVs, and skinning data, populating a raw_mesh_t structure that the Iron engine can convert to GPU buffers.

Source: io_gltf/cgltf.c line 15-17

FBX Import

For FBX files, ArmorPaint wraps the ufbx library in paint/plugins/io_fbx/io_fbx.c. The implementation traverses the FBX scene graph, constructs mesh geometry, and optionally handles material splits while converting coordinate systems to match the engine's expectations.

Source: io_fbx/io_fbx.c line 1-4

EXR Import

OpenEXR handling uses a hand-rolled parser in paint/plugins/io_exr/io_exr.c. The code reads EXR headers, decompresses scanlines using the format's native compression, and immediately uploads the resulting pixel data via gpu_create_texture_from_bytes().

Source: io_exr/io_exr.c line 17-22

PSD, TIFF, and SVG Import

Iron Engine Integration

The parsers return data in formats expected by the Iron engine's rendering layer. Mesh data flows through raw_mesh_t structures containing short-precision arrays for positions (posa), normals (nora), UVs (texa), and optional vertex colors. Texture data bypasses intermediate structures, with importers like import_exr calling gpu_create_texture_from_bytes() to create gpu_texture_t objects immediately.

These objects enter the engine's global caches (data_cached_textures, import_mesh_importers), making them available to the UI and real-time rendering systems without format-specific handling in the core engine code.

Build System Configuration

The plugin system uses paint/plugins/project.js to declare which source files compile into the plugin module. Adding support for new formats requires only appending the directory to this configuration:

project.add_cfiles("io_gltf/**");
project.add_cfiles("io_fbx/**");
project.add_cfiles("io_exr/**");
project.add_cfiles("io_psd/**");
project.add_cfiles("io_tiff/**");
project.add_cfiles("io_svg/**");

Source: paint/plugins/project.js lines 5-11

Practical Usage Examples

Importing a GLTF Mesh

char *gltf_path = "models/house.gltf";

void *(*import_gltf)(char *) = any_map_get(import_mesh_importers, "gltf");
raw_mesh_t *mesh = (raw_mesh_t *)import_gltf(gltf_path);

if (mesh) {
    gpu_mesh_t *gpu_mesh = gpu_create_mesh_from_raw(mesh);
}

Relevant source: plugins.c – import_gltf_glb function lines 27-41

Importing a PSD with Layers

char *psd_path = "textures/brick_wall.psd";

void *(*import_psd)(char *) = any_map_get(import_texture_importers, "psd");
void *psd_tex = import_psd(psd_path);

char *layer_key = "brick_wall.diffuse.png";
gpu_texture_t *layer_tex = any_map_get(data_cached_textures, layer_key);

Relevant source: plugins.c – import_psd function lines 56-72

Importing an EXR Texture

char *exr_path = "hdr/sky.exr";

void *(*import_exr)(char *) = any_map_get(import_texture_importers, "exr");
gpu_texture_t *hdr_tex = (gpu_texture_t *)import_exr(exr_path);

Relevant source: plugins.c – import_exr function lines 49-54

Summary

  • ArmorPaint's plugin architecture registers C callbacks in global maps (import_mesh_importers and import_texture_importers) during plugins_init() in paint/plugins/plugins.c.
  • File loading uses the virtual file system via data_get_blob() to stream files into memory buffers before parsing.
  • Format-specific parsers for GLTF (cgltf), FBX (ufbx), EXR (custom), PSD, TIFF, and SVG convert binary data into Iron engine structures.
  • Iron integration relies on raw_mesh_t for geometry and gpu_texture_t for images, with parsers calling gpu_create_texture_from_bytes() to upload texture data directly.
  • Build configuration in paint/plugins/project.js allows extending support by adding new io_* directories to the C file list.

Frequently Asked Questions

How does ArmorPaint register new import formats?

New formats register by calling any_map_set() in paint/plugins/plugins.c during the plugins_init() function. Developers map file extensions to importer functions that return either raw_mesh_t* for meshes or GPU textures for images, depending on which global map they target (import_mesh_importers or import_texture_importers).

What data structures does the Iron engine use for imported meshes?

The Iron engine consumes ** raw_mesh_t ** structures containing short-precision arrays: posa for positions, nora for normals, and texa for UV coordinates. The engine later converts these to gpu_mesh_t objects via gpu_create_mesh_from_raw() for rendering.

How are texture formats like EXR and PSD handled differently from meshes?

Texture importers bypass intermediate mesh structures and call ** gpu_create_texture_from_bytes() ** directly to create gpu_texture_t objects. PSD importers additionally register individual layers as separate entries in data_cached_textures, while EXR importers handle high dynamic range scanline decompression before GPU upload.

Where are the importer source files located in the ArmorPaint repository?

All importer implementations reside in paint/plugins/io_*/ directories (e.g., paint/plugins/io_gltf/, paint/plugins/io_fbx/). The central registration logic lives in paint/plugins/plugins.c, while the build system references these directories in paint/plugins/project.js.

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 →