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_importersstores callbacks returningraw_mesh_t*for 3D geometryimport_texture_importersstores 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
- PSD:
paint/plugins/io_psd/io_psd.cdecodes layered Photoshop files, registering each layer as a separate texture entry in the global cache. - TIFF:
paint/plugins/io_tiff/io_tiff.chandles uncompressed and LZW-compressed TIFF images, converting them to GPU-ready textures. - SVG:
paint/plugins/io_svg/io_svg.crasterizes vector graphics into bitmap textures that the rendering pipeline can sample directly.
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_importersandimport_texture_importers) duringplugins_init()inpaint/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_tfor geometry andgpu_texture_tfor images, with parsers callinggpu_create_texture_from_bytes()to upload texture data directly. - Build configuration in
paint/plugins/project.jsallows extending support by adding newio_*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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →