Skip to content

API reference

These are the commands a plugin can use, and the ones we keep working from one update to the next. Anything not on this page is part of Pixel Painter’s insides and may change without warning.

Everything a plugin changes in the project goes on the undo list, the same as if you had clicked it yourself. Ctrl+Z takes it back.

from pixelpaint import ui
from pixelpaint.api import ProjectAPI, TextureSetAPI

This adds an orange “Rust tint” fill layer, sets it to Multiply at 50%, and tags it orange. Wrapping the edits in gesture() makes them one undo step instead of four.

from pixelpaint.api import ProjectAPI
project = ProjectAPI()
texture_set = project.get_active_texture_set()
with project.gesture():
layer_id = texture_set.add_layer(
"Rust tint", "fill", fills={"base_color": [150, 80, 40, 255]})
texture_set.set_layer_blend(layer_id, "multiply")
texture_set.set_layer_opacity(layer_id, 0.5)
texture_set.set_layer_color(layer_id, "orange")

Channel ids: "base_color", "metallic", "roughness", "normal", "height", "emissive".

Colour and channel values are whole numbers from 0 to 255:

  • Base Color is [r, g, b, a]
  • Normal is [r, g, b]. Flat is [128, 128, 255]
  • Metallic, Roughness and Height are a single number. For Height, 128 is flat

Tiles are (u, v) pairs. (0, 0) is UDIM 1001, (1, 0) is 1002, and so on.

Blend modes: "normal", "multiply", "divide", "screen", "add", "subtract", "lighten", "darken".

Colour tags: "red", "orange", "yellow", "green", "blue", "purple", "gray", or None for no tag.

from pixelpaint import ui

Command What it does
ui.get_main_window() The main Pixel Painter window. Use it as the parent of your own windows so they follow the app’s theme and close with it.
ui.add_menu(menu) Puts your QMenu on the menu bar, just before Help. Returns the menu.
ui.delete_ui_element(widget) Removes a window or menu your plugin made.
ui.add_close_guard(callback) Calls callback() when the artist quits. Return True to let the app close, False to stop it, for example to ask about unsaved work in your tool.
ui.remove_close_guard(callback) Takes a close guard away again.
ui.refresh_asset_libraries() Reloads the Asset Shelf. Call it after your plugin saves a material or brush into the asset folders.
from PySide6.QtWidgets import QMenu
from pixelpaint import ui
menu = QMenu("Studio Tools", ui.get_main_window())
menu.addAction("Say hello").triggered.connect(lambda: print("hello"))
ui.add_menu(menu)

project = ProjectAPI(). There is only ever one project, so every ProjectAPI() you make talks to the same one.

Command What it does
project.is_open() True if a project is open. Check this first.
project.resolution Texture size in pixels, for example 64.
project.mesh_path Path of the model file.
project.texture_sets Every texture set, by name. project.texture_sets["lambert1"] gives you that set, and .keys() lists the names.
project.get_active_texture_set() The set the artist is working on, or None.
project.set_active_texture_set(name) Switches to another set.
project.set_brush_color([r, g, b]) Changes the brush colour.
project.get_texture(channel, tile) The finished image of one channel, with every layer combined, as a NumPy array. Base Color is (size, size, 4).
with project.gesture(): Everything inside the block becomes a single undo step.
project = ProjectAPI()
image = project.get_texture("base_color", (0, 0))
print(image.shape) # (64, 64, 4)

A texture set holds a stack of layers. Get one with project.get_active_texture_set() or project.texture_sets["name"].

Most commands take a layer_id. You get one back when you create a layer, or you can read it from texture_set.layers.

Command What it gives you
texture_set.name The set’s name.
texture_set.layers Every layer, bottom first. Each one is a dictionary with id, name, type ("paint", "fill", "group"…), visible, opacity, blend, color and parent_id. Read these; change them with the commands below.
texture_set.active_layer_id The id of the selected layer.
texture_set.get_active_layer() The selected layer’s dictionary, or None.
texture_set.set_active_layer(layer_id) Selects a layer.
Command What it does
add_layer(name, "paint") A new empty paint layer. Returns its id.
add_layer(name, "fill", fills={...}) A new fill layer. fills sets a value per channel, for example {"base_color": [255, 0, 0, 255], "roughness": 200}. Channels you leave out get the defaults.
add_group(name) A new empty group. Returns its id.
wrap_layers([id, id, ...], name) Puts those layers into a new group. They must all be in the same group already, or all at the top level. Returns the group’s id.
delete_layer(layer_id) Deletes a layer.

New layers go on top of the stack.

Command What it does
rename_layer(layer_id, new_name) Renames it.
set_layer_visibility(layer_id, True) Shows or hides it.
set_layer_opacity(layer_id, 0.5) Opacity from 0.0 to 1.0.
set_layer_blend(layer_id, "multiply") Blend mode.
set_layer_color(layer_id, "green") Colour tag, or None to clear it.
set_layer_parent(layer_id, group_id) Moves a layer into a group, or back to the top level with None.
Command What it does
set_layer_fill(layer_id, channel, value) Changes one channel’s value, for example set_layer_fill(layer_id, "roughness", 220).
set_layer_texture(layer_id, channel, path) Loads an image file into one channel. If the file name has a UDIM number in it (rust.1001.png), the other tiles are loaded too.

These only work on fill layers. Paint layers keep what was painted.

Command What it does
copy_layer(layer_id) Copies a layer (a group copies with everything in it). Returns True if it worked.
can_paste_layer() True if this set can take what was copied.
paste_layer() Pastes above the selected layer. Returns the new layer’s id. Works across texture sets.
mirror_paint_layer(layer_id, "x", onto="-") Copies the +X half of a paint layer onto the -X half. Use "y" or "z" for the other axes, and onto="+" to go the other way.
mirror_paint_layer(layer_id, "x", replace=True) Flips the whole paint layer across X.
mirror_paint_effect(layer_id, effect_id, "x", onto="-") The same for a mask’s paint effect. Takes replace=True too.

mirror_layer is the old name of mirror_paint_layer and still works, so plugins written for it keep running. Both mirror commands return how many texels they wrote, or None if they could not run (not a paint layer or effect, a bad axis, or no model loaded).

# Copy the selected layer into every texture set
project = ProjectAPI()
source = project.get_active_texture_set()
source.copy_layer(source.active_layer_id)
with project.gesture():
for name in project.texture_sets.keys():
target = project.texture_sets[name]
if target.can_paste_layer():
target.paste_layer()
Command What it does
apply_material(path) Adds a .pmat material on top of the stack, like dropping it from the Asset Shelf. Returns the new layer ids.
add_layer_mask(layer_id, white=True) Adds a mask. white=True starts with the whole layer showing. Without it the mask starts black and the layer is hidden until you paint the mask.
remove_layer_mask(layer_id) Removes the mask.

from pixelpaint.viewport.preview import PreviewScene, PreviewViewport, box_mesh

A small 3D view you can put in your own window, lit like the main viewport. It shows only what you put in its PreviewScene and never touches the open project. See Writing plugins.

These are not available to plugins yet. For now, do them in the app:

  • Painting pixels directly from code. You can load an image into a fill layer with set_layer_texture instead.
  • Exporting textures.
  • Saving the project.
  • Baking mesh maps.