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:
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 and shared drives
Section titled “4. Startup and shared drives”Launch Pixel Painter through a studio .bat or launcher to add shared folders.
On Windows, each list uses ; and keeps the declared order:
set PIXELPAINT_STARTUP_PATH=P:\pipeline\pixelpaint\startupset PIXELPAINT_PLUGINS_PATH=P:\pipeline\pixelpaint\pluginsset PYTHONPATH=P:\pipeline;P:\pipeline\thirdparty\python_packages;%PYTHONPATH%start "" "C:\Program Files\PixelPainter\bin\PixelPainter.exe"Documents\PixelPainter\startup and PIXELPAINT_STARTUP_PATH run each
top-level .py entry, in local-folder then shared-folder order and then
filename order, once after the main window opens. Each entry defines the same
run() hook as a manual plugin; a broken entry is logged and does not prevent
the next one from running. Use Startup to add a studio menu whose actions
lazily import tools from PYTHONPATH.
PIXELPAINT_PLUGINS_PATH adds folders to the manual Plugins menu. It does
not autorun those files. Empty, duplicate, and unavailable entries are ignored;
unavailable folders are logged. Pixel Painter’s own packages and Qt take
precedence over shared-drive imports. To recover from a broken startup tool,
launch once with PIXELPAINT_SKIP_STARTUP=1.
5. Plugin folders
Section titled “5. 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.