# ArmorPaint ui_nodes.c Internal Architecture: How the Node Graph Drives Material Evaluation

> Explore the internal architecture of ArmorPaint's ui_nodes.c. Understand how the node graph drives material evaluation and discover the MVC pattern behind material recompilation and graph traversal.

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

---

**The [`ui_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/ui_nodes.c) and [`util_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/util_nodes.c) files in ArmorPaint implement a model-view-controller pattern where [`util_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/util_nodes.c) manages the node graph data model and material recompilation triggers, while the UI layer captures input and delegates to material parsing functions that traverse the graph to generate shader code.**

ArmorPaint's node-based material system relies on a tight integration between the visual node editor and the real-time material evaluation pipeline. The internal architecture spans across [`util_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/util_nodes.c)—which serves as the core backend referred to as *uinodes* in the source—and [`ui_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/ui_nodes.c), which handles UI event delegation. Together, these components manage the node graph state, track changes for undo/redo, and dispatch shader recompilation when the graph topology changes.

## Core Architecture Components

The node editor's backend in [`util_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/util_nodes.c) organizes functionality into three primary domains: graph storage, node grouping, and change management.

### The Canvas and Graph Model

At the heart of the system lies the **canvas structure** that persists the node graph for materials and brushes. The `ui_node_canvas_t` structure contains dynamic arrays of `ui_node_t` (nodes) and `ui_node_link_t` (connections), while `ui_nodes_t` tracks editor state including selection, the undo stack, and preview flags.

Key accessor functions retrieve the active context:

- `ui_nodes_get_canvas()` – Returns the active canvas for the current material, brush, or nested group.
- `ui_nodes_get_nodes()` – Retrieves the active `ui_nodes_t` instance based on the current canvas type.

```c
/* Example: Accessing the active canvas to add a new node */
ui_node_t *new_node = ui_nodes_make_node(template_node, ui_nodes_get_nodes(), ui_nodes_get_canvas(true));
any_array_push(ui_nodes_get_nodes()->nodes, new_node);

```

### Node Grouping and Nested Sub-Graphs

ArmorPaint supports hierarchical node groups through the `node_group_t` structure, which owns an isolated `ui_node_canvas_t` for its sub-graph. The system maintains a `ui_nodes_group_stack` to track nested group editing contexts.

Critical validation functions ensure graph integrity:

- `ui_nodes_make_group_node()` – Constructs a GROUP node from a material group definition.
- `ui_nodes_traverse_group()` – Recursively discovers all nested groups within a canvas.
- `ui_nodes_can_place_group()` – Prevents recursive group insertion that would create cycles.

## The Material Evaluation Pipeline

The connection between the visual graph and the rendered material follows a deferred recompilation pattern. Changes are flagged immediately but processed during the engine's main loop to batch updates and prevent redundant shader regeneration.

### Change Detection and Recompilation Flags

When a user modifies the graph—adding nodes, changing connections, or adjusting parameters—the UI layer calls `ui_nodes_canvas_changed()`. This sets the `ui_nodes_recompile_mat` boolean flag, signaling that the material requires reprocessing. A secondary flag, `ui_nodes_recompile_mat_final`, triggers after compilation to update dependent layers like fill or path layers.

```c
/* Example: Flagging material for recompilation after edit */
any_array_push(ui_nodes_get_canvas(true)->nodes, new_node);
ui_nodes_canvas_changed();   // Sets ui_nodes_recompile_mat = true

```

### Shader Code Generation

The `ui_nodes_recompile()` function serves as the central dispatcher. When invoked each frame, it checks the recompilation flag and initiates the parsing phase:

1. Switches the active context (`g_context->material`) to the currently edited material.
2. Invokes material-specific parsing functions such as `make_material_parse_brush()` or `make_material_parse_paint_material()`.
3. These functions traverse the node graph via `ui_nodes_get_nodes()` to generate shader code.

This parsing occurs in the material-parsing module (typically [`util_material.c`](https://github.com/armory3d/armorpaint/blob/main/util_material.c) or similar), which translates the visual graph into executable GPU shaders.

### Preview Texture Updates

After shader compilation, the system generates visual feedback through two pathways:

- **Material Previews**: `util_render_make_material_preview()` renders the full material to the viewport.
- **Node Previews**: Individual node thumbnails are updated via `util_render_make_node_preview()`, which renders isolated outputs for each node.

Node-specific previews are cached in `g_context->node_preview_map`, mapping node IDs to their texture resources.

```c
/* Example: Generating a preview for a selected node */
ui_node_t *selected = ui_get_node(active_canvas->nodes, selected_id);
ui_nodes_make_node_preview(selected);   // Updates cached preview texture

```

## Integration Flow: From User Input to Rendered Preview

The complete data flow from interaction to evaluation follows these stages:

1. **User Interaction** – The UI layer in [`ui_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/ui_nodes.c) detects input and calls [`util_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/util_nodes.c) functions to modify the active canvas (`g_context->material->canvas` or `g_context->brush->canvas`).

2. **State Recording** – `ui_nodes_push_undo()` records the current canvas state onto the undo stack before modifications occur.

   ```c
   /* Example: Recording state for undo */
   ui_node_canvas_t *last = NULL;
   ui_nodes_push_undo(last);
   ```

3. **Recompilation Trigger** – `ui_nodes_canvas_changed()` sets `ui_nodes_recompile_mat = true`.

4. **Shader Rebuild** – The main loop invokes `ui_nodes_recompile()`, which calls the appropriate `make_material_parse_*` function to traverse the graph and generate shader code.

5. **Render Update** – Post-compilation, preview textures refresh through `util_render_make_node_preview()` and viewport updates.

## Summary

- **Architecture Pattern**: [`util_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/util_nodes.c) implements a model-view-controller structure where `ui_node_canvas_t` stores the graph model, preview utilities handle the view, and recompilation flags manage the controller logic.
- **File Responsibilities**: [`util_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/util_nodes.c) contains the core data structures and evaluation logic, while [`ui_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/ui_nodes.c) forwards UI events to the backend.
- **Key Structures**: The system centers on `ui_node_canvas_t` (graph storage), `ui_nodes_t` (editor state), and `node_group_t` (hierarchical grouping).
- **Evaluation Trigger**: The `ui_nodes_recompile_mat` flag bridges user edits to shader generation, parsed by `make_material_parse_*` functions that traverse the active canvas.
- **Preview System**: Node previews map to GPU textures via `g_context->node_preview_map`, updated through `util_render_make_node_preview()`.

## Frequently Asked Questions

### What is the difference between ui_nodes.c and util_nodes.c in ArmorPaint?

[`util_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/util_nodes.c) (referred to as *uinodes* internally) contains the core node graph implementation including data structures (`ui_node_canvas_t`), grouping logic, and material recompilation dispatch. [`ui_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/ui_nodes.c) serves as the UI event layer that captures user input and delegates actions to the functions defined in [`util_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/util_nodes.c).

### How does ArmorPaint prevent recursive node group insertion?

The system validates group placement through `ui_nodes_can_place_group()`, which checks the `ui_nodes_group_stack` and existing node hierarchy to detect potential cycles before allowing a group to be nested within itself or its descendants.

### What triggers a material recompilation in the node editor?

Any modification to the graph topology or node parameters calls `ui_nodes_canvas_changed()`, which sets the `ui_nodes_recompile_mat` flag to true. The engine checks this flag each frame via `ui_nodes_recompile()` to determine if the material parser needs to regenerate shader code.

### How are node preview textures generated and stored?

Individual node previews are created through `ui_nodes_make_node_preview()`, which renders the node's output to a texture cached in `g_context->node_preview_map` using the node's ID as the key. These textures are updated when the node graph recompiles or when specific nodes are selected for inspection.