### Install Dependencies and Start Local Development
Source: https://github.com/adobedocs/painter-python-api/blob/main/README.md
Run these commands to install project dependencies and start the local development server for the Adobe I/O Theme.
```shell
$ yarn install
$ yarn dev
```
--------------------------------
### Build and Preview Locally
Source: https://github.com/adobedocs/painter-python-api/blob/main/README.md
This command builds the documentation site and starts a local preview server for testing.
```shell
yarn start
```
--------------------------------
### Start Substance 3D Painter with Remote Scripting Enabled
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/remote-control.md
Launch the application with the `--enable-remote-scripting` argument to allow remote command execution. Ensure the application is fully started before running scripts.
```bash
"Adobe Substance 3D painter.exe" --enable-remote-scripting
```
--------------------------------
### Example Deployment Path Structure
Source: https://github.com/adobedocs/painter-python-api/blob/main/README.md
This text illustrates the typical path structure for deploying documentation to Adobe's developer websites, following the pattern `developer.adobe.com/{product}/`.
```text
developer.adobe.com/{product}/docs
developer.adobe.com/{product}/community
developer.adobe.com/{product}/community/code_of_conduct
developer.adobe.com/{product}/community/contribute
```
--------------------------------
### Export Module
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Documentation and examples for using existing export presets within Substance Painter.
```APIDOC
## Module: substance_painter.export
### Description
This module provides functionalities related to exporting textures and other assets from Substance Painter. It includes documentation on using existing export presets and examples for module usage.
```
--------------------------------
### Example: Drive Painter Remotely
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Demonstrates how to use the RemotePainter client to execute both JavaScript and Python commands in Substance Painter. Ensure the RemotePainter server is running.
```python
# example_remote.py – drive Painter from an external script
import lib_remote
remote = lib_remote.RemotePainter()
remote.checkConnection()
# JavaScript: get application version
version = remote.execScript("alg.version.painter", "js")
print(f"Painter version (JS): {version}")
# Python: list resources, converting objects to strings inline
import_cmd = "import substance_painter"
remote.execScript(import_cmd, "python")
search_cmd = '"|||".join([x.identifier().url() for x in substance_painter.resource.search("p:starter_assets/")])'
files = remote.execScript(search_cmd, "python").split("|||")
for f in files:
print(f)
# Expected output: resource://starter_assets/... (one URL per line)
```
--------------------------------
### Dynamic Qt5 vs Qt6 Import and Usage
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/qt6-migration.md
Provides a comprehensive example of how to dynamically check the Painter application version and import the appropriate PySide module (PySide2 or PySide6). It also shows how to conditionally use QAction based on the detected Qt version, ensuring plugin compatibility across different Painter releases.
```python
# Qt5 vs Qt6 check import
import substance_painter as sp
IsQt5 = sp.application.version_info() < (10,1,0)
if IsQt5 :
from PySide2 import QtGui
from PySide2 import QtCore
from PySide2 import QtWidgets
else :
from PySide6 import QtGui
from PySide6 import QtCore
from PySide6 import QtWidgets
# Usage example
ActionBuilder = None
if IsQt5 :
ActionBuilder = QtWidgets.QAction
else :
ActionBuilder = QtGui.QAction
Action = ActionBuilder(
"My menu action",
triggered=MyFunctionToCall
)
```
--------------------------------
### Install numpy using pip
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/using-external-modules.md
Use pip to install external Python packages like numpy. Ensure compatibility with the application's Python version.
```bash
> pip install numpy
Defaulting to user installation because normal site-packages is not writeable
Collecting numpy
Downloading numpy-1.21.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (15.7 MB)
|████████████████████████████████| 15.7 MB 2.5 MB/s
Installing collected packages: numpy
Successfully installed numpy-1.21.0
```
--------------------------------
### Display Python sys.path
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/using-external-modules.md
View the list of directories Python searches for modules. This helps identify where packages are installed.
```python
python
>>> import sys
>>> sys.path
['', '/usr/lib/python37.zip', '/usr/lib/python3.7', '/usr/lib/python3.7/lib-dynload', '/home/username/.local/lib/python3.7/site-packages', '/usr/lib/python3.7/site-packages']
```
--------------------------------
### Make External Python Modules Available
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Install third-party pip packages and then set the PYTHONPATH environment variable before launching Substance Painter to make these modules accessible within the application's Python interpreter.
```bash
# Install the package with the system pip first
pip install numpy
# Find where site-packages lives
python -c "import sys; print([p for p in sys.path if 'site-packages' in p])"
# e.g. ['/home/user/.local/lib/python3.7/site-packages']
```
--------------------------------
### substance_painter.project.create()
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Creates a new Substance Painter project, with support for OCIO color management.
```APIDOC
## substance_painter.project.create()
### Description
Creates a new project. Supports OCIO color management via environment variables.
### Method
Python function call
### Endpoint
N/A (Python API)
### Parameters
- **settings** (dict) - Optional - Project settings, including color management configuration.
### Request Example
```python
import substance_painter.project
# Example with basic settings
project_settings = {
"color_management": {
"enabled": True,
"config_path": "/path/to/config.ocio"
}
}
substance_painter.project.create(settings=project_settings)
# Example without specific settings (uses defaults)
# substance_painter.project.create()
```
### Response
None explicitly documented.
```
--------------------------------
### Blending Modes
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Allows getting and setting per-channel blending modes on layers and provides a mechanism to build interactive UI dropdowns for users to change them.
```APIDOC
## Blending Modes
### Description
Provides functionality to manage per-channel blending modes for layers. This includes functions to set blending modes and to build dynamic UI elements like dropdowns for interactive user control.
### Usage
```python
import substance_painter as sp
from PySide6 import QtWidgets, QtGui # use PySide2 on Painter < 10.1
BLEND_MODES = [v for v in sp.layerstack.BlendingMode.__members__]
def change_blend_mode(layer, channel_type, dropdown):
index = dropdown.currentIndex()
mode = sp.layerstack.BlendingMode(index)
layer.set_blending_mode(mode, channel_type)
def build_channel_row(layer, channel_type, parent_layout):
frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout()
frame.setLayout(layout)
layout.addWidget(QtWidgets.QLabel(channel_type.name))
combo = QtWidgets.QComboBox()
combo.addItems(BLEND_MODES)
# Pre-select the layer's current blending mode
current_mode_name = layer.get_blending_mode(channel_type).name
current_mode_index = BLEND_MODES.index(current_mode_name)
combo.setCurrentIndex(current_mode_index)
combo.currentIndexChanged.connect(
lambda: change_blend_mode(layer, channel_type, combo)
)
layout.addWidget(combo)
parent_layout.addWidget(frame)
# Usage: build a row for every channel of the active layer
stack = sp.textureset.get_active_stack()
selected = sp.layerstack.get_selected_nodes(stack)
if selected:
container_layout = QtWidgets.QVBoxLayout()
for ch_type, _ in stack.all_channels().items():
build_channel_row(selected[0], ch_type, container_layout)
```
### Parameters
- `layer` (sp.layerstack.Node): The layer to modify.
- `channel_type` (sp.layerstack.ChannelType): The type of channel (e.g., 'color', 'normal').
- `dropdown` (QtWidgets.QComboBox): The UI dropdown widget.
- `parent_layout` (QtWidgets.QLayout): The layout to which the channel row will be added.
### Returns
None
### Example
```python
# Assuming 'selected' is a list of selected layer nodes
# and 'container_layout' is a QBoxLayout
if selected:
for ch_type, _ in sp.textureset.get_active_stack().all_channels().items():
build_channel_row(selected[0], ch_type, container_layout)
```
```
--------------------------------
### Initialize Plugin and UI
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/examples/blending_mode.md
Initializes the plugin by connecting to layer stack change events and building the main UI. It also populates the list of available blending modes.
```python
def start_plugin():
global BLEND_MODES
# Add event for getting layer stack selection updates
sp.event.DISPATCHER.connect( sp.event.LayerStacksModelDataChanged, Update )
# Build dock UI
Parent = BuildUI()
sp.ui.add_dock_widget( Parent, sp.ui.UIMode.Edition )
ResetUI()
WIDGETS.append(Parent)
# Some enums at not iteratable currently,
# so we rely on the members attribute to build a list
BLEND_MODES = [ Value for Value in sp.layerstack.BlendingMode.__members__ ]
```
--------------------------------
### Project State Checks with substance_painter.project
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Verify if a project is loaded using is_open() before accessing project data. Use Metadata to persist arbitrary key/value pairs within the .spp file.
```python
import substance_painter.project as project
# Guard: is a project loaded?
if not project.is_open():
print("No project open – aborting")
else:
path = project.file_path()
print(f"Project path: {path}")
# Read project-level edition state
if project.is_in_edition_state():
# Read/write arbitrary metadata stored in the .spp file
meta = project.Metadata("MyPlugin")
meta.set("last_run", "2024-01-15")
value = meta.get("last_run")
print(f"Stored metadata: {value}") # "2024-01-15"
```
--------------------------------
### Custom Texture Export Plugin Script
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/creating-python-plugin.md
This script defines functions to export texture sets using a specified preset. It includes setup for UI actions and proper cleanup.
```python
import os
# Substance 3D Painter modules
import substance_painter.ui
import substance_painter.export
import substance_painter.project
import substance_painter.textureset
# PySide module to build custom UI
from PySide2 import QtWidgets
plugin_widgets = []
def export_textures() :
# Verify if a project is open before trying to export something
if not substance_painter.project.is_open() :
return
# Get the currently active layer stack (paintable)
stack = substance_painter.textureset.get_active_stack()
# Get the parent Texture Set of this layer stack
material = stack.material()
# Build Export Preset resource URL
# - Context: name of the library where the resource is located
# - Name: name of the resource (filename without extension or Substance graph path)
export_preset = substance_painter.resource.ResourceID(
context="starter_assets",
name="PBR Metallic Roughness" )
print( "Preset:"
print( export_preset.url() )
# Setup the export settings
resolution = material.get_resolution()
# Setup the export path, in this case the textures
# will be put next to the spp project file on the disk
Path = substance_painter.project.file_path()
Path = os.path.dirname(Path) + "/"
# Build the configuration
config = {
"exportShaderParams" : False,
"exportPath" : Path,
"exportList" : [ { "rootPath" : str(stack) } ],
"exportPresets" : [ { "name" : "default", "maps" : [] } ],
"defaultExportPreset" : export_preset.url(),
"exportParameters" : [
{
"parameters" : { "paddingAlgorithm": "infinite" }
}
]
}
substance_painter.export.export_project_textures( config )
def start_plugin():
# Create a text widget for a menu
Action = QtWidgets.QAction("Custom Python Export",
triggered=export_textures)
# Add this widget to the existing File menu of the application
substance_painter.ui.add_action(
substance_painter.ui.ApplicationMenu.File,
Action )
# Store the widget for proper cleanup later when stopping the plugin
plugin_widgets.append(Action)
def close_plugin():
# Remove all widgets that have been added to the UI
for widget in plugin_widgets:
substance_painter.ui.delete_ui_element(widget)
plugin_widgets.clear()
if __name__ == "__main__":
start_plugin()
```
--------------------------------
### substance_painter.project.create()
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Creates a new Substance Painter project. Supports custom unit scales and environment variables for color management.
```APIDOC
## substance_painter.project.create()
### Description
Creates a new Substance Painter project. This function supports using a custom unit scale for mesh size and can be configured using the PAINTER_ACE_CONFIG environment variable for project color management.
### Method
substance_painter.project.create()
### Parameters
None explicitly documented for direct invocation, but relies on environment variables for configuration.
### Notes
- Supports custom unit scale for mesh size.
- Supports PAINTER_ACE_CONFIG environment variable for color management setup.
- Raises an error if OCIO or PAINTER_ACE_CONFIG environment variables are set to an invalid configuration.
```
--------------------------------
### Set PYTHONPATH and launch Painter with Python
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/using-external-modules.md
Use Python's os and subprocess modules to set the PYTHONPATH environment variable and launch the application.
```python
import os
import subprocess
# Add the environment variable
os.environ["PYTHONPATH"] = "C:/Python/lib/site-packages/"
# Launch the application with the new environment
subprocess.call( "Adobe Substance 3D Painter.exe", env=os.environ )
```
--------------------------------
### Save and Restore Layer Selection
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/examples/save_selection.md
This script saves the currently selected layers' UIDs and their selection type to project metadata when the project edition starts. It restores this selection when the project is re-opened. Ensure the script is placed in the correct Python plugin folder.
```python
import substance_painter as sp
PROJECT_READY = False
def OnStackChange( Arg ) :
if not sp.project.is_in_edition_state() or not PROJECT_READY :
return
Stack = sp.textureset.get_active_stack()
Layers = sp.layerstack.get_selected_nodes( Stack )
IDs = []
for Layer in Layers :
IDs.append( Layer.uid() )
Metadata = sp.project.Metadata( "LayerStackSelection" )
Metadata.set( "Selection", IDs )
if len(IDs) == 1 and (
Layer.get_type() == sp.layerstack.NodeType.PaintLayer \
or Layer.get_type() == sp.layerstack.NodeType.FillLayer \
or Layer.get_type() == sp.layerstack.NodeType.InstanceLayer \
or Layer.get_type() == sp.layerstack.NodeType.GroupLayer ) \ :
Type = sp.layerstack.get_selection_type( Layers[0] )
Metadata.set(
"SelectionType",
sp.layerstack.SelectionType( Type )
)
def OnEditionStart( Arg ) :
Metadata = sp.project.Metadata( "LayerStackSelection" )
IDs = Metadata.get( "Selection" )
Nodes = []
if IDs :
for ID in IDs :
Nodes.append( sp.layerstack.Node(ID) )
sp.layerstack.set_selected_nodes( Nodes )
if len(IDs) == 1 and (
Nodes[0].get_type() == sp.layerstack.NodeType.PaintLayer \
or Nodes[0].get_type() == sp.layerstack.NodeType.FillLayer \
or Nodes[0].get_type() == sp.layerstack.NodeType.InstanceLayer \
or Nodes[0].get_type() == sp.layerstack.NodeType.GroupLayer ) \ :
Type = Metadata.get( "SelectionType" )
sp.layerstack.set_selection_type( Nodes[0], int(Type) )
# Need to be done at the end because
# of concurrency between events
global PROJECT_READY
PROJECT_READY = True
def OnEditionStop( Arg ) :
global PROJECT_READY
PROJECT_READY = False
def start_plugin():
sp.event.DISPATCHER.connect( sp.event.ProjectEditionEntered, OnEditionStart )
sp.event.DISPATCHER.connect( sp.event.ProjectEditionLeft, OnEditionStop )
sp.event.DISPATCHER.connect( sp.event.LayerStacksModelDataChanged, OnStackChange )
def close_plugin():
sp.event.DISPATCHER.disconnect( sp.event.ProjectEditionEntered, OnEditionStart )
sp.event.DISPATCHER.disconnect( sp.event.ProjectEditionLeft, OnEditionStop )
sp.event.DISPATCHER.disconnect( sp.event.LayerStacksModelDataChanged, OnStackChange )
if __name__ == "__main__":
start_plugin()
```
--------------------------------
### Plugin Lifecycle: start_plugin and close_plugin
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Implement start_plugin() to register UI elements and event handlers, and close_plugin() to clean them up. Painter calls these when the plugin is enabled or disabled.
```python
import substance_painter.ui as ui
from PySide6 import QtGui # PySide2 on Painter < 10.1
WIDGETS = []
def start_plugin():
action = QtGui.QAction("Run My Tool", triggered=my_tool)
ui.add_action(ui.ApplicationMenu.File, action)
WIDGETS.append(action)
def close_plugin():
for widget in WIDGETS:
ui.delete_ui_element(widget)
WIDGETS.clear()
if __name__ == "__main__":
start_plugin()
```
--------------------------------
### Launch Painter with Custom PYTHONPATH
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Create a launch script that sets the PYTHONPATH environment variable to include your custom site-packages directory before executing the Substance Painter executable.
```python
# Launch script that sets the environment before opening Painter
import os
import subprocess
os.environ["PYTHONPATH"] = "/home/user/.local/lib/python3.7/site-packages"
subprocess.call(["Adobe Substance 3D Painter"], env=os.environ)
```
--------------------------------
### Project Module - UV Tile Workflow
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Adapts the project module to support the new UV Tile workflow in Substance Painter.
```APIDOC
## Module: substance_painter.project
### Description
This module handles project-related functionalities, including support for the UV Tile workflow.
### Classes
* **substance_painter.project.ProjectWorkflow**: Represents the project workflow, adapted for UV Tiles.
* **substance_painter.project.Settings**: Class for project settings, with a modified constructor to accommodate the UV Tile workflow.
```
--------------------------------
### Build Main UI Window
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/examples/blending_mode.md
Constructs the main UI window for the blend mode plugin. It sets up the window title, icon, and layout, including sections for layer information and blend mode controls.
```python
def BuildUI() :
global WIDGET_NODE_NAME
global WIDGET_NODE_TYPE
global WIDGET_BLEND_LAYOUT
Parent = QtWidgets.QFrame()
Parent.setWindowTitle("Blend Modes")
Parent.setWindowIcon( QtGui.QIcon(SCRIPT_FOLDER + "/" + "icon.svg") )
ParentLayout = QtWidgets.QVBoxLayout()
Parent.setLayout( ParentLayout )
Margin = 10
ParentLayout.setContentsMargins( Margin, Margin, Margin, Margin )
# Build top UI
TitleLabel = QtWidgets.QLabel( "INFO" )
NodeLabel = QtWidgets.QLabel( "(No layer selected)" )
NodeType = QtWidgets.QLabel( "(none)" )
ParentLayout.addWidget( TitleLabel )
ParentLayout.addWidget( NodeLabel )
ParentLayout.addWidget( NodeType )
EmptyLabel = QtWidgets.QLabel( "" )
ParentLayout.addWidget( EmptyLabel )
ParentLayout.addWidget( Separator() )
# Build scrollable layout for blend modes
BlendLabel = QtWidgets.QLabel( "BLEND MODES" )
ParentLayout.addWidget( BlendLabel )
Widget = QtWidgets.QWidget()
VerticalLayout = QtWidgets.QVBoxLayout( Widget )
BlendLayout = QtWidgets.QVBoxLayout()
VerticalLayout.addLayout( BlendLayout )
VerticalLayout.addStretch()
VerticalLayout.setContentsMargins( 0, 0, 8, 0 )
ScrollArea = QtWidgets.QScrollArea()
ScrollArea.setWidget( Widget )
ScrollArea.setWidgetResizable( True )
ParentLayout.addWidget( ScrollArea )
# Finish
WIDGET_NODE_NAME = NodeLabel
WIDGET_NODE_TYPE = NodeType
WIDGET_BLEND_LAYOUT = BlendLayout
return Parent
```
--------------------------------
### substance_painter.resource.show_resources_in_ui()
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Highlights a specified list of resources within the Substance Painter user interface.
```APIDOC
## substance_painter.resource.show_resources_in_ui()
### Description
Locates and highlights a given list of resources in the Substance Painter application's UI, making them easily discoverable by the user.
### Method
Python function call
### Endpoint
N/A (Python API)
### Parameters
- **resource_identifiers** (list of strings) - Required - A list containing the identifiers of the resources to highlight.
### Request Example
```python
import substance_painter.resource
resources_to_highlight = [
"resource_id_1",
"resource_id_2"
]
substance_painter.resource.show_resources_in_ui(resources_to_highlight)
```
### Response
None explicitly documented.
```
--------------------------------
### Run Linters Locally
Source: https://github.com/adobedocs/painter-python-api/blob/main/README.md
Execute this command to run the configured linters locally. Docker is required. Refer to `.github/super-linter.env` for enabled linters and Supported Linters documentation for tool details if Docker cannot be used.
```shell
yarn lint
```
--------------------------------
### QAction Module Change
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/qt6-migration.md
Illustrates the change in module location for QAction from QtWidgets in Qt5 to QtGui in Qt6.
```python
QtWidgets.QAction
```
```python
QtGui.QAction
```
--------------------------------
### Build Interactive Blending Mode UI
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Dynamically builds a Qt UI dropdown for selecting per-channel blending modes on a layer. Requires PySide6 (or PySide2 for older Painter versions).
```python
import substance_painter as sp
from PySide6 import QtWidgets, QtGui # use PySide2 on Painter < 10.1
BLEND_MODES = [v for v in sp.layerstack.BlendingMode.__members__]
def change_blend_mode(layer, channel_type, dropdown):
index = dropdown.currentIndex()
mode = sp.layerstack.BlendingMode(index)
layer.set_blending_mode(mode, channel_type)
def build_channel_row(layer, channel_type, parent_layout):
frame = QtWidgets.QFrame()
layout = QtWidgets.QVBoxLayout()
frame.setLayout(layout)
layout.addWidget(QtWidgets.QLabel(channel_type.name))
combo = QtWidgets.QComboBox()
combo.addItems(BLEND_MODES)
# Pre-select the layer's current blending mode
current_mode_name = layer.get_blending_mode(channel_type).name
current_mode_index = BLEND_MODES.index(current_mode_name)
combo.setCurrentIndex(current_mode_index)
combo.currentIndexChanged.connect(
lambda: change_blend_mode(layer, channel_type, combo)
)
layout.addWidget(combo)
parent_layout.addWidget(frame)
# Usage: build a row for every channel of the active layer
stack = sp.textureset.get_active_stack()
selected = sp.layerstack.get_selected_nodes(stack)
if selected:
container_layout = QtWidgets.QVBoxLayout()
for ch_type, _ in stack.all_channels().items():
build_channel_row(selected[0], ch_type, container_layout)
```
--------------------------------
### Event System - substance_painter.event.DISPATCHER
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Allows subscribing and unsubscribing from application-level events like project openings, closings, and layer changes.
```APIDOC
## Event System - `substance_painter.event.DISPATCHER`
Subscribes to and unsubscribes from application-level events. Key events include `ProjectEditionEntered`, `ProjectEditionLeft`, and `LayerStacksModelDataChanged`.
```python
import substance_painter.event as event
import substance_painter.project as project
def on_project_opened(arg):
print(f"Project opened: {project.file_path()}")
def on_project_closed(arg):
print("Project closed")
def on_layer_changed(arg):
print("Layer stack changed")
def start_plugin():
event.DISPATCHER.connect(event.ProjectEditionEntered, on_project_opened)
event.DISPATCHER.connect(event.ProjectEditionLeft, on_project_closed)
event.DISPATCHER.connect(event.LayerStacksModelDataChanged, on_layer_changed)
def close_plugin():
event.DISPATCHER.disconnect(event.ProjectEditionEntered, on_project_opened)
event.DISPATCHER.disconnect(event.ProjectEditionLeft, on_project_closed)
event.DISPATCHER.disconnect(event.LayerStacksModelDataChanged, on_layer_changed)
```
```
--------------------------------
### Resource Module
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Provides access to Substance Painter's resource management, including shelf manipulation facilities.
```APIDOC
## Module: substance_painter.resource
### Description
This module provides functionalities for managing resources within Substance Painter, including access to shelves and their contents.
### Classes
* **substance_painter.resource.Shelf**: Represents a shelf in Substance Painter.
* **substance_painter.resource.Shelves**: Provides facilities for manipulating shelves.
```
--------------------------------
### Register External Plugins via Environment Variable
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Set the SUBSTANCE_PAINTER_PLUGINS_PATH environment variable to point to a directory containing your custom plugins. This directory must include 'plugins/', 'startup/', and 'modules/' sub-folders.
```bash
# Windows (Command Prompt)
set SUBSTANCE_PAINTER_PLUGINS_PATH=C:\studio\painter-plugins
start "" "C:\Program Files\Adobe\Adobe Substance 3D Painter\Adobe Substance 3D Painter.exe"
# Linux / macOS
export SUBSTANCE_PAINTER_PLUGINS_PATH="/studio/painter-plugins"
"/opt/Adobe/Adobe Substance 3D Painter/Adobe Substance 3D Painter"
```
--------------------------------
### UI Integration - substance_painter.ui
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Enables the addition of custom menu actions and dockable panels to the Substance Painter interface.
```APIDOC
## UI integration – `substance_painter.ui`
Adds menu actions and dockable panels to Painter's interface. Supports `ApplicationMenu.File`, `ApplicationMenu.Edit`, and custom dock widgets bound to edition mode.
```python
import substance_painter.ui as ui
from PySide6 import QtGui, QtWidgets
WIDGETS = []
def start_plugin():
# Add a menu item under Edit
action = QtGui.QAction("My Tool", triggered=lambda: print("Tool triggered"))
ui.add_action(ui.ApplicationMenu.Edit, action)
WIDGETS.append(action)
# Add a dockable panel visible only in edition mode
panel = QtWidgets.QFrame()
panel.setWindowTitle("My Panel")
layout = QtWidgets.QVBoxLayout()
layout.addWidget(QtWidgets.QLabel("Hello from My Panel"))
panel.setLayout(layout)
ui.add_dock_widget(panel, ui.UIMode.Edition)
WIDGETS.append(panel)
def close_plugin():
for w in WIDGETS:
ui.delete_ui_element(w)
WIDGETS.clear()
```
```
--------------------------------
### Plugin Lifecycle: start_plugin / close_plugin
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Defines the entry points for Painter Python plugins. `start_plugin` is called when a plugin is enabled to register UI elements and event handlers, and `close_plugin` is called when disabled to clean them up.
```APIDOC
## Plugin lifecycle – `start_plugin` / `close_plugin`
Every Painter Python plugin must expose `start_plugin()` to register UI elements and event handlers, and `close_plugin()` to tear them all down cleanly. Painter calls these functions when the user enables or disables the plugin from the Python menu.
```python
import substance_painter.ui as ui
from PySide6 import QtGui # PySide2 on Painter < 10.1
WIDGETS = []
def start_plugin():
action = QtGui.QAction("Run My Tool", triggered=my_tool)
ui.add_action(ui.ApplicationMenu.File, action)
WIDGETS.append(action)
def close_plugin():
for widget in WIDGETS:
ui.delete_ui_element(widget)
WIDGETS.clear()
if __name__ == "__main__":
start_plugin()
```
```
--------------------------------
### setMargin to setContentsMargins Replacement
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/qt6-migration.md
Demonstrates the change from setMargin() to setContentsMargins() for setting widget margins in Qt6.
```python
setMargin(10)
```
```python
setContentsMargins(10, 10, 10, 10)
```
--------------------------------
### Project State Checks: `substance_painter.project`
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Provides utility functions for checking the current project's state, such as whether a project is open or if it's in an editable state. It also includes functionality to persist metadata within the `.spp` file.
```APIDOC
## Project state checks – `substance_painter.project`
Guards and metadata helpers for project state. Plugins must always verify `is_open()` before accessing project data, and use `Metadata` to persist arbitrary key/value pairs inside the `.spp` file.
```python
import substance_painter.project as project
# Guard: is a project loaded?
if not project.is_open():
print("No project open – aborting")
else:
path = project.file_path()
print(f"Project path: {path}")
# Read project-level edition state
if project.is_in_edition_state():
# Read/write arbitrary metadata stored in the .spp file
meta = project.Metadata("MyPlugin")
meta.set("last_run", "2024-01-15")
value = meta.get("last_run")
print(f"Stored metadata: {value}") # "2024-01-15"
```
```
--------------------------------
### RemotePainter Client Library
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
A reusable client library for controlling Substance Painter remotely via HTTP. Requires the Painter application to be running with the remote server enabled.
```python
import sys, json, base64
import http.client as http
class RemotePainter:
def __init__(self, port=60041, host="localhost"):
self._host, self._port = host, port
self._ROUTE = "/run.json"
self._HEADERS = {"Content-type": "application/json", "Accept": "application/json"}
def checkConnection(self):
http.HTTPConnection(self._host, self._port).connect()
def execScript(self, script, type):
cmd = base64.b64encode(script.encode("utf-8")).decode("utf-8")
key = "js" if type == "js" else "python"
body = f'{{"key":"cmd"}}'.encode("utf-8")
conn = http.HTTPConnection(self._host, self._port, timeout=3600)
conn.request("POST", self._ROUTE, body, self._HEADERS)
data = conn.getresponse().read()
conn.close()
return data.decode("utf-8").rstrip()
```
--------------------------------
### Texture Sets and Stacks with substance_painter.textureset
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Iterate through all texture sets in a project, query their resolution, and list their stacks and channels. Also retrieves the currently active stack in the viewport.
```python
import substance_painter.textureset as textureset
for ts in textureset.all_texture_sets():
print(f"Texture Set: {ts.name()}")
resolution = ts.get_resolution()
print(f" Resolution: {resolution}")
for stack in ts.all_stacks():
print(f" Stack: {stack}")
channels = stack.all_channels()
for channel_type, channel_info in channels.items():
print(f" Channel: {channel_type.name}")
# Get the stack currently visible in the viewport
active_stack = textureset.get_active_stack()
print(f"Active stack: {active_stack}")
```
--------------------------------
### Register External Plugins at Runtime
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Use the substance_painter_plugins module to register plugin paths dynamically from within the application. Call update() to trigger a re-scan for new plugins.
```python
# Alternatively, register at runtime from inside the application
import substance_painter_plugins as spp
spp.add_path("/studio/painter-plugins")
spp.update() # triggers a re-scan for newly available plugins
```
--------------------------------
### Integrate UI Elements in Painter
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Add custom menu actions and dockable panels to the Substance Painter interface. Ensure UI elements are properly deleted when the plugin closes to avoid issues.
```python
import substance_painter.ui as ui
from PySide6 import QtGui, QtWidgets
WIDGETS = []
def start_plugin():
# Add a menu item under Edit
action = QtGui.QAction("My Tool", triggered=lambda: print("Tool triggered"))
ui.add_action(ui.ApplicationMenu.Edit, action)
WIDGETS.append(action)
# Add a dockable panel visible only in edition mode
panel = QtWidgets.QFrame()
panel.setWindowTitle("My Panel")
layout = QtWidgets.QVBoxLayout()
layout.addWidget(QtWidgets.QLabel("Hello from My Panel"))
panel.setLayout(layout)
ui.add_dock_widget(panel, ui.UIMode.Edition)
WIDGETS.append(panel)
def close_plugin():
for w in WIDGETS:
ui.delete_ui_element(w)
WIDGETS.clear()
```
--------------------------------
### External Plugins - SUBSTANCE_PAINTER_PLUGINS_PATH
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Registers a custom plugin root directory using an environment variable, allowing for external plugin loading.
```APIDOC
## External plugins – `SUBSTANCE_PAINTER_PLUGINS_PATH`
Registers a custom plugin root directory via an environment variable. The directory must contain `plugins/`, `startup/`, and `modules/` sub-folders.
```bash
# Windows (Command Prompt)
set SUBSTANCE_PAINTER_PLUGINS_PATH=C:\studio\painter-plugins
start "" "C:\Program Files\Adobe\Adobe Substance 3D Painter\Adobe Substance 3D Painter.exe"
# Linux / macOS
export SUBSTANCE_PAINTER_PLUGINS_PATH="/studio/painter-plugins"
"/opt/Adobe/Adobe Substance 3D Painter/Adobe Substance 3D Painter"
```
```python
# Alternatively, register at runtime from inside the application
import substance_painter_plugins as spp
spp.add_path("/studio/painter-plugins")
spp.update() # triggers a re-scan for newly available plugins
```
```
--------------------------------
### substance_painter.resource.Resource methods
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Provides methods to query properties of a resource, including its category, usages, name, type, tags, and internal properties.
```APIDOC
## substance_painter.resource.Resource methods
### Description
These methods allow querying various properties of a resource within Substance Painter.
### Methods
- **`category()`**: Queries the category of a resource.
- **`usages()`**: Queries the usages of a resource.
- **`gui_name()`**: Queries the display name of a resource.
- **`type()`**: Queries the type of a resource.
- **`tags()`**: Queries the tags associated with a resource.
- **`internal_properties()`**: Queries a dictionary of a resource's internal properties.
### Parameters
None for the methods themselves, they operate on a Resource object.
### Returns
- `category()`: string
- `usages()`: list of `substance_painter.resource.Usage` enum values
- `gui_name()`: string
- `type()`: `substance_painter.resource.Type` enum value
- `tags()`: list of strings
- `internal_properties()`: dictionary
```
--------------------------------
### Core Modules
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Introduction to the core modules available in the initial version of the Substance Painter Python API.
```APIDOC
## Initial Version Modules (0.0.1)
### Description
The initial release of the Substance Painter Python API (version 0.0.1) introduces several core modules for various functionalities.
### Available Modules
* `substance_painter`
* `substance_painter.display`
* `substance_painter.event`
* `substance_painter.exception`
* `substance_painter.export`
* `substance_painter.logging`
* `substance_painter.project`
* `substance_painter.resource`
* `substance_painter.textureset`
* `substance_painter.ui`
* `substance_painter_plugins`
```
--------------------------------
### Texture Export using substance_painter.export.export_project_textures
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Exports all channels of a texture set using a specified preset. The configuration dictionary controls output path, stacks to export, preset URL, and per-export parameters like padding.
```python
import os
import substance_painter.export
import substance_painter.project
import substance_painter.textureset
import substance_painter.resource
def export_textures():
if not substance_painter.project.is_open():
return
stack = substance_painter.textureset.get_active_stack()
material = stack.material()
preset = substance_painter.resource.ResourceID(
context="starter_assets",
name="PBR Metallic Roughness"
)
output_dir = os.path.dirname(substance_painter.project.file_path()) + "/"
config = {
"exportShaderParams" : False,
"exportPath" : output_dir,
"exportList" : [{"rootPath": str(stack)}],
"exportPresets" : [{"name": "default", "maps": []}],
"defaultExportPreset" : preset.url(),
"exportParameters" : [
{"parameters": {"paddingAlgorithm": "infinite"}}
]
}
result = substance_painter.export.export_project_textures(config)
print(result)
# Expected: ExportStatus object listing exported file paths
```
--------------------------------
### Enable Remote Scripting in Painter
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Launch Substance Painter with the --enable-remote-scripting flag to allow control of the running instance over HTTP.
```bash
# Start Painter with remote scripting enabled
"Adobe Substance 3D Painter.exe" --enable-remote-scripting
```
--------------------------------
### Qt5/Qt6 Compatibility Check
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Detects the Painter version at runtime to import the correct PySide flavor, ensuring plugins work across Qt5 and Qt6 migrations. This code snippet handles differences in module imports, function names, and operator usage.
```python
import substance_painter as sp
IS_QT5 = sp.application.version_info() < (10, 1, 0)
if IS_QT5:
from PySide2 import QtGui, QtCore, QtWidgets
QAction = QtWidgets.QAction
def exec_dialog(dialog): return dialog.exec_()
def set_margin(widget, n): widget.setMargin(n)
key_combo = QtCore.Qt.CTRL + QtCore.Qt.Key_P
else:
from PySide6 import QtGui, QtCore, QtWidgets
QAction = QtGui.QAction # QAction moved from QtWidgets to QtGui
def exec_dialog(dialog): return dialog.exec() # exec_() → exec()
def set_margin(widget, n): widget.setContentsMargins(n, n, n, n) # setMargin removed
key_combo = QtCore.Qt.CTRL | QtCore.Qt.Key_P # + operator → | operator
# Build a cross-version action
action = QAction("My Action", triggered=lambda: print("clicked"))
```
--------------------------------
### substance_painter.project.execute_when_not_busy()
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Executes a given function only when Substance 3D Painter is not currently busy with other operations.
```APIDOC
## substance_painter.project.execute_when_not_busy()
### Description
Executes a function when Substance 3D Painter is not busy.
### Method
Python function call
### Endpoint
N/A (Python API)
### Parameters
None explicitly documented for this function signature, but it takes a callable as an argument.
### Request Example
```python
import substance_painter
def my_task():
print("Painter is not busy, executing task!")
substance_painter.project.execute_when_not_busy(my_task)
```
### Response
None explicitly documented.
```
--------------------------------
### substance_painter.display.set_environment_resource()
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Sets the environment resource for display. Throws TypeError for incorrect argument types.
```APIDOC
## substance_painter.display.set_environment_resource()
### Description
Sets the environment resource (e.g., an HDRI map) for display. Will raise a TypeError if arguments are not of the expected type.
### Method
Python function call
### Endpoint
N/A (Python API)
### Parameters
- **resource_identifier** (string) - Required - Identifier for the environment resource.
### Request Example
```python
import substance_painter.display
# Assuming 'my_env_resource_id' is a valid resource identifier
# substance_painter.display.set_environment_resource('my_env_resource_id')
```
### Response
None explicitly documented.
```
--------------------------------
### Import and use an external module
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/using-external-modules.md
After setting up PYTHONPATH, import and use external modules like numpy within the Painter Python Console.
```python
>>> import numpy
>>> numpy.__version__
'1.21.0'
```
--------------------------------
### Qt5 vs Qt6 Version Check
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/examples/blending_mode.md
Checks the Qt version to import the correct PySide modules. This is necessary for compatibility between different Qt versions.
```python
IsQt5 = sp.application.version_info() < (10,1,0)
if IsQt5 :
from PySide2 import QtGui
from PySide2 import QtWidgets
else :
from PySide6 import QtGui
from PySide6 import QtWidgets
```
--------------------------------
### UI Mode Management
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Functions for interacting with the Substance Painter UI modes.
```APIDOC
## substance_painter.ui.get_current_mode()
### Description
Query the current UI mode.
### Method
Call
### Endpoint
N/A (Python API)
### Parameters
None.
### Request Example
```python
# current_mode = substance_painter.ui.get_current_mode()
```
### Response
The current UI mode.
```
```APIDOC
## substance_painter.ui.switch_to_mode()
### Description
Switch to a specified UI mode.
### Method
Call
### Endpoint
N/A (Python API)
### Parameters
- **mode** (UI Mode Type) - Required - The UI mode to switch to.
### Request Example
```python
# substance_painter.ui.switch_to_mode(substance_painter.ui.UIMode.Baking)
```
### Response
None explicitly documented.
```
--------------------------------
### Import PySide2 vs PySide6
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/qt6-migration.md
Demonstrates how to conditionally import PySide2 or PySide6 based on the Painter application version. This is crucial for maintaining compatibility with older versions of Painter.
```python
from PySide2 import QtWidgets, QtGui
```
```python
from PySide6 import QtWidgets, QtGui
```
--------------------------------
### substance_painter.project.is_busy()
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Queries whether Substance 3D Painter is currently busy with an operation.
```APIDOC
## substance_painter.project.is_busy()
### Description
Checks if Substance 3D Painter is currently performing a long-running operation.
### Method
Python function call
### Endpoint
N/A (Python API)
### Parameters
None.
### Request Example
```python
import substance_painter.project
if substance_painter.project.is_busy():
print("Painter is currently busy.")
else:
print("Painter is idle.")
```
### Response
- **is_busy** (boolean) - True if the painter is busy, False otherwise.
```
--------------------------------
### substance_painter.display.set_color_lut_resource()
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Sets the color LUT resource for display. Throws TypeError for incorrect argument types.
```APIDOC
## substance_painter.display.set_color_lut_resource()
### Description
Sets the color Look-Up Table (LUT) resource for display purposes. Will raise a TypeError if arguments are not of the expected type.
### Method
Python function call
### Endpoint
N/A (Python API)
### Parameters
- **resource_identifier** (string) - Required - Identifier for the color LUT resource.
### Request Example
```python
import substance_painter.display
# Assuming 'my_lut_resource_id' is a valid resource identifier
# substance_painter.display.set_color_lut_resource('my_lut_resource_id')
```
### Response
None explicitly documented.
```
--------------------------------
### substance_painter.event.Dispatcher.connect_strong()
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Connects a callback function to the event dispatcher using a strong reference.
```APIDOC
## substance_painter.event.Dispatcher.connect_strong()
### Description
Connects a callback function to an event dispatcher with a strong reference, ensuring the callback is not garbage collected prematurely.
### Method
Python method call on an event dispatcher instance.
### Endpoint
N/A (Python API)
### Parameters
- **event_name** (string) - Required - The name of the event to connect to.
- **callback** (callable) - Required - The function to be called when the event is triggered.
### Request Example
```python
import substance_painter.event
def my_event_handler(event_data):
print(f"Event received: {event_data}")
# Assuming 'dispatcher' is an instance of substance_painter.event.Dispatcher
# and 'some_event' is a valid event name.
# dispatcher.connect_strong('some_event', my_event_handler)
```
### Response
None explicitly documented.
```
--------------------------------
### Resource Handling
Source: https://github.com/adobedocs/painter-python-api/blob/main/static/api_index.html
Functions for querying resource relationships.
```APIDOC
## substance_painter.resource.Resource.children()
### Description
Query the child resources of a given resource.
### Method
Call
### Endpoint
N/A (Python API)
### Parameters
None explicitly documented for this method call.
### Request Example
```python
# Example usage (assuming 'resource' is an instance of Resource)
# child_resources = resource.children()
```
### Response
A list or collection of child resources.
```
```APIDOC
## substance_painter.resource.Resource.parent()
### Description
Query the parent resource of a given resource.
### Method
Call
### Endpoint
N/A (Python API)
### Parameters
None explicitly documented for this method call.
### Request Example
```python
# Example usage (assuming 'resource' is an instance of Resource)
# parent_resource = resource.parent()
```
### Response
The parent resource.
```
--------------------------------
### Texture Sets and Stacks: `substance_painter.textureset`
Source: https://context7.com/adobedocs/painter-python-api/llms.txt
Enables iteration through all texture sets within a project and their associated stacks. It allows querying of texture set resolutions and the channels present within each stack.
```APIDOC
## Texture sets and stacks – `substance_painter.textureset`
Iterates all texture sets in a project and their stacks; queries resolution and active channels.
```python
import substance_painter.textureset as textureset
for ts in textureset.all_texture_sets():
print(f"Texture Set: {ts.name()}")
resolution = ts.get_resolution()
print(f" Resolution: {resolution}")
for stack in ts.all_stacks():
print(f" Stack: {stack}")
channels = stack.all_channels()
for channel_type, channel_info in channels.items():
print(f" Channel: {channel_type.name}")
# Get the stack currently visible in the viewport
active_stack = textureset.get_active_stack()
print(f"Active stack: {active_stack}")
```
```
--------------------------------
### Build Channel UI Element
Source: https://github.com/adobedocs/painter-python-api/blob/main/src/pages/guides/examples/blending_mode.md
Constructs a UI element for a specific layer channel, including a label and a dropdown for selecting the blending mode. It updates the dropdown with the current blending mode.
```python
def BuildChannelUI( Layer, ChannelType, ChannelInfo ) :
Parent = QtWidgets.QFrame()
Parent.setFrameStyle( QtWidgets.QFrame.Panel )
Parent.setFrameShadow( QtWidgets.QFrame.Raised )
Parent.setStyleSheet( FRAME_STYLE )
Layout = QtWidgets.QVBoxLayout()
Parent.setLayout( Layout )
Label = QtWidgets.QLabel( ChannelType.name )
Layout.addWidget( Label )
Dropdown = QtWidgets.QComboBox()
Dropdown.addItems( BLEND_MODES )
Layout.addWidget( Dropdown )
# Retrieve layer current blending mode
# and update dropdown to show it
BlendModeName = Layer.get_blending_mode( ChannelType ).name
BlendModeIndex = BLEND_MODES.index( BlendModeName )
Dropdown.setCurrentIndex( BlendModeIndex )
# Connect a function to be able to change the
# blending mode from our own UI
Dropdown.currentIndexChanged.connect(
lambda: ChangeBlendMode( Layer, ChannelType, Dropdown )
)
WIDGET_BLEND_LAYOUT.addWidget(Parent)
```