# ArmorPaint Asset Management: How Models and Textures Are Imported and Organized

> Discover how ArmorPaint manages assets. Learn about its import pipeline for models and textures, centralized storage in g_project, and synchronous access for seamless development.

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

---

**ArmorPaint uses a centralized import pipeline that detects file formats via `path_is_mesh()` and `path_is_texture()`, dispatches to format-specific importers through function pointer maps, and stores all assets in the global `g_project` structure for synchronous access across the engine.**

The armory3d/armorpaint repository implements a robust asset management system that decouples the UI from raw file handling. This article examines how ArmorPaint asset management processes 3D models and textures through a unified pipeline, from format detection to scene integration.

## Centralized Import Pipeline Architecture

### File Type Detection and Dispatcher

Every asset import begins with type detection in [`import_mesh.c`](https://github.com/armory3d/armorpaint/blob/main/import_mesh.c) (lines 13–20) and [`import_texture.c`](https://github.com/armory3d/armorpaint/blob/main/import_texture.c). The system uses boolean checks to classify incoming files:

```c
// From import_mesh.c
if (!path_is_mesh(path)) {
    if (!context_enable_import_plugin(path)) {
        console_error(strings_unknown_asset_format());
        return;
    }
}

```

When a user drops a file or selects **Import Mesh**, the pipeline extracts the extension and queries one of two global dispatch tables: `import_mesh_importers` or `import_texture_importers`. These `any_map_t` structures map file extensions to importer function pointers populated at startup from modules in the `io/` directory.

### Importer Lookup and Execution

The dispatcher retrieves the appropriate loader via `any_map_get()`:

```c
// From import_mesh.c, line 40
char *ext = string_tmp("%s", path + string_last_index_of(path, ".") + 1);
raw_mesh_t *(*importer)(char *path) = any_map_get(import_mesh_importers, ext);
raw_mesh_t *mesh = importer(path);  // Actual file parsing occurs here

```

This design allows the engine to support OBJ, FBX, BLEND, KTX, and custom formats without hardcoding logic into the UI layer.

## Mesh Import Implementation

The `import_mesh_run()` function in [`import_mesh.c`](https://github.com/armory3d/armorpaint/blob/main/import_mesh.c) orchestrates the complete geometry pipeline. After parsing, raw data converts to engine objects through `import_mesh_make_mesh()` or `import_mesh_add_mesh()`:

```c
void import_mesh_run(char *path, bool clear_layers,
                     bool replace_existing, bool keep_camera) {
    // 1. Detect format
    if (!path_is_mesh(path)) {
        if (!context_enable_import_plugin(path)) {
            console_error(strings_unknown_asset_format());
            return;
        }
    }

    // 2. Pick the correct importer
    char *ext = string_tmp("%s", path + string_last_index_of(path, ".") + 1);
    raw_mesh_t *(*importer)(char *path) = any_map_get(import_mesh_importers, ext);
    raw_mesh_t *mesh = importer(path);

    // 3. Create engine mesh object(s)
    replace_existing ? import_mesh_make_mesh(mesh)
                     : import_mesh_add_mesh(mesh);

    // 4. Store source path for reloads
    g_project->mesh_assets = any_array_create_from_raw((void *[]){ path }, 1);
}

```

### Scene Integration and Asset Tracking

Post-import steps occur in [`import_mesh.c`](https://github.com/armory3d/armorpaint/blob/main/import_mesh.c) (lines 88–115). The system handles **re-parenting**, **UV-unwrapping**, and **scaling to view bounds** via `util_mesh_merge()`. The active paint object updates through `context_select_paint_object()`, while source paths persist in `g_project->mesh_assets` for future reload operations.

## Texture Import and Material Binding

Texture processing follows a parallel path in [`import_texture.c`](https://github.com/armory3d/armorpaint/blob/main/import_texture.c). The pipeline creates `texture_t` objects and binds them to materials immediately:

```c
void import_texture_run(char *path) {
    char *ext = string_tmp("%s", path + string_last_index_of(path, ".") + 1);
    raw_texture_t *(*importer)(char *path) = any_map_get(import_texture_importers, ext);
    raw_texture_t *tex = importer(path);

    // Create engine texture and update material system
    texture_t *t = texture_create(tex->data, tex->width, tex->height, tex->format);
    material_set_texture(current_material, t);
}

```

Textures integrate directly into the material system rather than appearing as standalone scene objects, ensuring immediate availability for painting operations.

## Global Project State and Resource Management

All imported assets ultimately reference the global **`g_project`** structure defined in [`context.c`](https://github.com/armory3d/armorpaint/blob/main/context.c). This centralized state maintains:

- **`mesh_assets`**: Array of source paths for reload operations
- **`paint_objects`**: Active mesh instances for the current project
- **Material definitions**: Linked textures and shaders

### Generic Resource Loading

For non-project assets like icons and default materials, [`resource.c`](https://github.com/armory3d/armorpaint/blob/main/resource.c) provides `resource_load()`:

```c
// From ui_base.c (lines 121-124)
void ui_base_draw() {
    string_array_t *resources = string_array_create();
    string_array_push(resources, "icons");
    string_array_push(resources, "default_material");
    resource_load(resources);  // Pulls from /paint/assets/
}

```

### Loading Behavior

Standard asset loading operates **synchronously** on the main thread using `iron_load_blob()`. However, large downloads such as neural-node models utilize `iron_file_download()` with callbacks to maintain UI responsiveness during transfer.

## Practical Code Examples

### Importing a Mesh Programmatically

Custom plugins can trigger imports directly through the pipeline:

```c
char *my_mesh_path = "C:/Models/character.obj";
import_mesh_run(my_mesh_path, true, false, false);  
// clear_layers = true, replace_existing = false, keep_camera = false

```

### Replacing an Existing Texture

Update the active material's diffuse map at runtime:

```c
char *new_tex = "C:/Textures/diffuse.png";
import_texture_run(new_tex);  // Swaps the active material's texture reference

```

### Accessing Loaded Mesh Objects

Iterate the global project state to find specific assets by name:

```c
mesh_object_t *obj = NULL;
for (i32 i = 0; i < g_project->_->paint_objects->length; ++i) {
    mesh_object_t *p = g_project->_->paint_objects->buffer[i];
    if (string_equals(p->base->name, "Character")) {
        obj = p;
        break;
    }
}
if (obj) {
    // Apply masks, export, or modify geometry
}

```

## Summary

- **Format Detection**: `path_is_mesh()` and `path_is_texture()` classify assets before dispatch
- **Importer Maps**: `any_map_get()` queries `import_mesh_importers` or `import_texture_importers` for format-specific parsers
- **Global State**: All assets register with `g_project`, enabling cross-referencing between UI, rendering, and painting systems
- **Scene Integration**: `util_mesh_merge()` and `context_select_paint_object()` handle post-import scene updates
- **Resource Loader**: `resource_load()` manages shared assets like icons independently of project files

## Frequently Asked Questions

### How does ArmorPaint detect file formats?

ArmorPaint checks file extensions using `path_is_mesh()` and `path_is_texture()` in [`import_mesh.c`](https://github.com/armory3d/armorpaint/blob/main/import_mesh.c) (lines 13–20). If the extension matches supported types like OBJ or PNG, the system proceeds to importer lookup; otherwise, it attempts to load a plugin via `context_enable_import_plugin()`.

### Where are imported assets stored after loading?

All assets reference the global **`g_project`** structure. Meshes append to `g_project->mesh_assets` for path tracking and `g_project->paint_objects` for scene instances. Textures bind directly to the material system rather than persisting in standalone arrays.

### Is asset loading synchronous or asynchronous?

Standard imports operate synchronously on the main thread through `iron_load_blob()`. Large network resources utilize `iron_file_download()` with asynchronous callbacks to prevent UI blocking during lengthy transfers.

### How can developers extend the import system?

Developers register custom importers by populating `import_mesh_importers` or `import_texture_importers` with extension-to-function mappings. The `any_map_t` architecture allows runtime registration of new parsers without modifying core UI code.