### 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) ```