Writing plugins
Plugins from the Plugins menu run inside Pixel Painter’s own Python and Qt process. They are for trusted TD tools: a plugin can parent a PySide window to Pixel Painter, add a layer through the public API, and affect the open project. Treat a script from somebody else like a plugin: do not run it unless you trust its author.
1. Add a plugin
Section titled “1. Add a plugin”Put a top-level .py file in Documents\PixelPainter\plugins. Its filename
appears in the Plugins menu without .py. Clicking it imports the file
once for the current session and calls its required run() function. Clicking
it again calls the same function again, so a script can reuse a dialog in a
module global.
"""A small trusted host script that adds a paint layer."""from PySide6.QtWidgets import QDialog, QHBoxLayout, QLabel, QLineEdit, QPushButton, QVBoxLayoutfrom shiboken6 import isValid
from pixelpaint import uifrom pixelpaint.api import ProjectAPI
_window = None
class LayerTool(QDialog): def __init__(self, parent): super().__init__(parent) self.setWindowTitle("Layer Tool") self.name = QLineEdit("Layer") self.status = QLabel("Open a project first.") add = QPushButton("Add Layer") add.clicked.connect(self.add_layer) buttons = QHBoxLayout() buttons.addStretch() buttons.addWidget(add) layout = QVBoxLayout(self) layout.addWidget(self.name) layout.addWidget(self.status) layout.addLayout(buttons)
def add_layer(self): texture_set = ProjectAPI().get_active_texture_set() if texture_set is None: self.status.setText("Open a project before adding a layer.") return name = self.name.text().strip() or "Layer" texture_set.add_layer(name, "paint") self.status.setText(f"Added: {name}")
def run(): global _window if _window is None or not isValid(_window): _window = LayerTool(ui.get_main_window()) _window.show() _window.raise_() _window.activateWindow()The dialog has the real main window as its Qt parent, so it follows the app’s
theme, focus and lifetime. TextureSetAPI.add_layer() is the same service used
by Pixel Painter’s own UI, so normal signals and undo integration remain intact.
2. Public API
Section titled “2. Public API”Use only documented public imports for a tool that should survive updates. The API reference lists every command they offer.
from pixelpaint import uifrom pixelpaint.api import ProjectAPI, TextureSetAPIfrom pixelpaint.viewport.preview import PreviewScene, PreviewViewport, box_meshThe last line is a 3D preview for a tool’s own test mesh: Pixel Painter’s own
viewport, lit by its current environment, showing a PreviewScene you fill
yourself. It never shows or changes the open project.
pixelpaint.ui.window, pixelpaint.core, and private attributes are host
internals. They may be useful in a studio, but they are not guaranteed to stay
compatible between versions.
3. Stop hook
Section titled “3. Stop hook”stop() is optional. Pixel Painter calls it for every script it loaded after
the artist confirms quitting and before Qt destroys the host window. Use it to
remove a menu, close a worker, or call ui.delete_ui_element() for UI owned by
the script.
4. Startup scripts
Section titled “4. Startup scripts”Every top-level .py file in Documents\PixelPainter\startup runs once, in
filename order, after the main window opens. Each one defines the same run()
hook as a manual plugin. A broken entry is logged and does not stop the next
one. Use Startup to add a studio menu whose actions import your tools lazily.
A startup script can also reach tools on a shared drive by adding it to the import path itself:
import sys
SHARED = r"P:\pipeline"if SHARED not in sys.path: sys.path.append(SHARED) # appended, so the app's own packages still win
def run(): import studio_tools studio_tools.install_menu()To recover from a broken startup script, move it out of the startup folder
and restart Pixel Painter.
5. Shared folders for a studio
Section titled “5. Shared folders for a studio”Pixel Painter also reads these environment variables. Each is a ;-separated
list of folders, read in the order given:
PIXELPAINT_STARTUP_PATH- more startup folders, run after the local one.PIXELPAINT_PLUGINS_PATH- more folders for the Plugins menu. These are listed, not run automatically.PYTHONPATH- extra import folders for your tools. Pixel Painter’s own packages and Qt always take precedence over them.PIXELPAINT_SKIP_STARTUP=1- skip every startup script for that session.
Empty, duplicate and unavailable entries are ignored; unavailable folders are logged.
6. Plugin folders
Section titled “6. Plugin folders”A larger tool can keep its entry, helpers and assets together in one folder:
material_helper/ plugin.json plugin.py dialog.py assets/plugin.json supplies display metadata without importing the tool while the
Plugins menu opens:
{ "manifest_version": 1, "id": "studio.material-helper", "name": "Material Helper", "entry": "plugin.py", "version": "1.0", "description": "Creates a studio material layer"}entry must be a relative .py file inside that folder. id is a stable
ASCII identifier using letters, digits, dots, underscores or hyphens. The
entry still defines run() and optional stop(); it may use normal relative
imports such as from .dialog import MaterialDialog. Duplicate ids and invalid
manifests are not loaded; the Plugins menu offers details with the reason.