.. _pme-scripting: ========= Scripting ========= PME allows for advanced customization and automation using Blender's `Python API `_. This article provides an overview of PME's scripting capabilities and explains the built-in global variables and functions. .. NOTE: Not necessary for furo or book themes. .. .. contents:: .. :local: .. :depth: 2 .. :class: this-will-duplicate-information-and-it-is-still-useful-here .. important:: Standard Command / Custom fields and menu Poll fields take **one line of code**. Move longer or reusable logic into an external Python file and keep a short call in the field. AI-generated code must also follow the format of its destination field. See :doc:`command_text` for field-specific syntax and execution rules. This page is the API reference for functions, variables, and arguments. --------- Tutorials --------- - **Video**: `Introduction to Scripting with Python in Blender (vimeo.com) `_ - **Video**: `Task Automation with Python Scripting in Blender (youtube.com) `_ - `Python for Non-Programmers (python.org) `_ - `Blender Python API `_ - `Blender/Python Quickstart `_ ---------------- Global Variables ---------------- Variables available within each PME slot editor. .. list-table:: :header-rows: 1 :widths: 25 75 * - **Variable** - **Description** * - ``menu`` - Name of the active menu * - ``slot`` - Name of the active slot * - ``C`` - `bpy.context `_ * - ``D`` - `bpy.data `_ * - ``O`` - `bpy.ops `_ * - ``T`` - `bpy.types `_ * - ``P`` - `bpy.props `_ * - ``L`` - Current `UILayout `_ object .. code-block:: python L.box().label(text="My Label") * - ``E`` - Current `Event `_ object .. code-block:: python E.ctrl and E.shift and message_box("Ctrl+Shift Pressed") * - ``U`` - `pme.UserData <#pme.UserData>`_ instance for user data storage .. code-block:: python U.foo = "value" U.update(foo="value1", bar="value2") U.foo U.get("foo", "default_value") ---------------- Global Functions ---------------- Functions available within PME slot editors. Different functions are available in Command tab and Custom tab. .. _pme-common-functions: Common Functions **************** .. py:function:: execute_script(path, **kwargs) Execute an external Python script. :param str path: Script file path. Relative path (from ``pie_menu_editor`` folder, recommended) or absolute path. :param kwargs: Additional keyword arguments passed to the script. :return: ``return_value`` from the script, or ``True`` by default. .. warning:: - Only place and execute scripts from trusted sources - Review contents before execution and verify in a backup or test environment if necessary - Scripts may contain operations that affect your environment, such as file operations or settings changes **Variables available in script**: ``kwargs``, ``__file__``, ``return_value``, all PME global variables **Examples**:: # Basic execution and return value execute_script("scripts/hello_world.py", msg="Hello World!") message_box(execute_script("scripts/get_message.py")) # scripts/hello_world.py message_box(kwargs["msg"]) # scripts/get_message.py return_value = "Hi!" # Processing with parameters # scripts/process_data.py kwargs = locals().get("kwargs", {}) result = my_function(kwargs.get("param1"), kwargs.get("param2", "default")) return_value = result # Call result = execute_script("scripts/process_data.py", param1=200, param2="Hello") # UI drawing in Custom tab # scripts/custom_ui.py msg = kwargs.get("msg", pme.context.text or "Default Message") box = L.box() box.label(text=msg, icon=pme.context.icon, icon_value=pme.context.icon_value) # Call execute_script("scripts/custom_ui.py", msg="Custom message") .. py:function:: props(name=None, value=None) Get or set the value of a PME Property. :param str name: Name of the property. :param value: New value of the property. :return: PME property container if ``name`` is ``None``, property value if only ``name`` is given, ``True`` if setting a value. **Example**:: # Get property value using string notation value = props("MyProperty") # Alternative: get property using attribute notation value = props().MyProperty # props() returns property container # Set property value using string notation props("MyProperty", value) # Alternative: set property using attribute notation props().MyProperty = value # props() returns property container .. _pme-tool-props: .. py:function:: tool_props(operator, *, tool, space_type=None, mode=None, context=None) Retrieve the specified tool's operator properties from ``context.workspace``. Tools and modes are unchanged. Omitted values resolve from the context at call time. :param str operator: Operator identifier, such as ``'sculpt.mesh_filter'``. :param str tool: Expected tool identifier. Checked against the tool ID for the source editor and mode. :param space_type: Editor type (``str | None``). ``None`` uses ``context.area.type``. :param mode: Source mode (``str | None``). ``None`` follows the table below. An explicit value selects the source regardless of the current mode. :param context: Blender context. ``None`` uses PME's script context. :return: Operator properties, or ``None`` if the workspace or source cannot be resolved, the tool reference is missing, or its tool ID does not match. :rtype: bpy.types.OperatorProperties | None :raises ValueError: ``space_type='NODE_EDITOR'`` with a ``mode`` other than ``None``. **Supported editors and default modes** .. list-table:: :header-rows: 1 :widths: 40 60 * - ``space_type`` - ``mode=None`` * - ``'VIEW_3D'`` - ``context.mode`` * - ``'IMAGE_EDITOR'`` - ``context.space_data.mode`` * - ``'NODE_EDITOR'`` - No mode distinction * - ``'SEQUENCE_EDITOR'`` - ``context.space_data.view_type`` Omitting ``mode`` for Image Editor or Sequencer requires ``context.space_data.type`` to match the source editor. Editor types not listed in the table return ``None``. **Retrieval conditions** With ``mode='SCULPT'``, properties can be retrieved even in Object Mode if the Sculpt Mode tool ID matches. Selecting another tool within the same mode changes that ID, so the function returns ``None``. This does not mean the stored values were deleted from Blender. After resolving the source, an unregistered ``operator`` propagates Blender's error. Retrieve the returned RNA reference again when it is needed. **Example** .. code-block:: python props = tool_props( 'sculpt.mesh_filter', tool='builtin.mesh_filter', space_type='VIEW_3D', mode='SCULPT', ) if props is not None: filter_type = props.type .. py:function:: paint_settings() Retrieve the context-sensitive paint settings. :return: The current paint settings or ``None`` if not in a paint mode. **Example**:: ps = paint_settings(); ps and L.template_ID_preview(ps, 'brush') .. py:function:: find_by(collection, key, value) Find the first item in ``collection`` where ``key`` equals ``value``. :return: Collection item if found, otherwise ``None``. **Example**:: m = find_by(C.active_object.modifiers, "type", 'SUBSURF') .. py:function:: setattr(object, name, value) Same as Python's built-in :func:`setattr`, but returns ``True`` after setting. :return: ``True`` .. _pme-command-tab-functions: Command Tab Functions ********************* .. py:function:: open_menu(name, slot=None, **kwargs) Open menu, pie menu, popup dialog or execute a stack key, sticky key, modal operator, or macro operator by name. :param str name: Name of the menu. :param slot: Index or name of the slot for Stack Key execution. :param kwargs: Arguments for Modal / Macro Operators used as local variables. :return: ``True`` if the menu exists and is currently available. Returns ``False`` when the target is missing, disabled, poll-blocked, or the requested slot is not found. **Example**:: # Open the menu depending on the active object's type: open_menu("Lamp Pie Menu" if C.active_object.type == 'LAMP' else "Object Pie Menu") # Call "My Stack Key" slot depending on Ctrl modifier: open_menu("My Stack Key", "Ctrl slot" if E.ctrl else "Shift slot") .. py:function:: toggle_menu(name, value=None) Enable or disable a menu. :param str name: Name of the menu. :param bool value: ``True`` to enable, ``False`` to disable, ``None`` to toggle. :return: ``True`` if the menu exists, ``False`` otherwise. .. py:function:: tag_redraw(area=None, region=None) Redraw UI areas or regions. :param str area: The :attr:`Area.type ` to redraw. Redraw all areas if ``None``. :param str region: The :attr:`Region.type ` to redraw. Redraw all regions if ``None``. :return: ``True`` .. py:function:: close_popups() Close all popup dialogs. :return: ``True`` .. py:function:: overlay(text, **kwargs) Draw an overlay message. :param str text: Message to display. :param kwargs: - ``alignment``: One of ``['TOP', 'TOP_LEFT', 'TOP_RIGHT', 'BOTTOM', 'BOTTOM_LEFT', 'BOTTOM_RIGHT']``. Default is ``'TOP'`` . - ``duration``: Duration in seconds. Default is ``2.0`` . - ``offset_x``: Horizontal offset. Default is ``10`` px. - ``offset_y``: Vertical offset. Default is ``10`` px. :return: ``True`` **Example**:: overlay('Hello PME!', offset_y=100, duration=1.0) .. py:function:: message_box(text, icon='INFO', title="Pie Menu Editor") Show a message box. :param str text: Message to display. :param str icon: Icon name (e.g. 'INFO', 'ERROR', 'QUESTION', etc.). :param str title: Window title. :return: ``True`` .. py:function:: confirm_box(message, func=None, icon='QUESTION', width=0) Show a confirmation dialog from a Command slot. :param str message: Message to display. :param func: Optional callback accepting one boolean argument: ``True`` on OK, ``False`` on cancellation. :param str icon: Blender icon name. Default is ``'QUESTION'``. :param int width: Dialog width in pixels. ``0`` uses Blender's default width. :return: ``None``. The function returns before the user confirms or cancels. Put the action inside the callback. Code after ``confirm_box()`` continues without waiting; its return value is not the user's answer. **Example 1: close the current area after confirmation**:: confirm_box( "Close this area?", func=lambda ok: bpy.ops.screen.area_close() if ok else None, ) **Example 2: run a PME Macro after confirmation**:: confirm_box( "Run this macro?", func=lambda ok: open_menu("My Macro") if ok else None, ) Replace ``My Macro`` with the name of an existing, enabled Macro. Put this Command outside the target Macro: the confirmation does not pause subsequent Macro steps, and calling the same Macro would invoke it again. Both examples do nothing on cancellation. The action runs in the context available to the callback; normal operator and menu poll requirements apply. Open only one confirmation dialog at a time, as callbacks are shared between dialogs. .. py:function:: input_box(func=None, prop=None) Show an input box. :param func: Function to call with the input value. :param str prop: Path to the property to edit. :return: ``True`` **Example**:: # Rename object: input_box(prop="C.active_object.name") # Display input value: input_box(func=lambda value: overlay(value)) .. _pme-custom-tab-functions: Custom Tab Functions ******************** .. py:function:: draw_menu(name, frame=True, dx=0, dy=0) Draw a popup dialog inside another popup dialog or a pie menu. :param str name: Name of the menu (popup dialog). :param bool frame: Whether to draw a frame. :param int dx: Horizontal offset. :param int dy: Vertical offset. :return: ``True`` if the menu exists and is currently available. Returns ``False`` without drawing when the target is missing, disabled, or poll-blocked. .. py:function:: operator(layout, idname, text="", icon='NONE', emboss=True, icon_value=0, **kwargs) Similar to :meth:`UILayout.operator() `, but allows filling operator properties. :param layout: A :class:`UILayout ` instance. :param str idname: Identifier of the operator. :return: :class:`OperatorProperties ` object. **Example**:: operator(L, "wm.context_set_int", "Material Slot 1", data_path="active_object.active_material_index", value=0) # Same as: # op = L.operator("wm.context_set_int", text="Material Slot 1") # op.data_path = "active_object.active_material_index" # op.value = 0 .. py:function:: custom_icon(filename) Get the integer value associated with a custom icon. :param str filename: Icon filename without extension, located in ``pie_menu_editor/icons/``. :return: The integer value of the custom icon. **Example**:: L.label(text="My Custom Icon", icon_value=custom_icon("p1")) .. py:function:: panel(pt, frame=True, header=True, expand=None, area=None, root=False, poll=True, layout=None) Draws a panel by its ID. :param pt: Panel class or panel class name string. If string, the corresponding class is searched from ``bpy.types``. :type pt: Union[str, Type] :param bool frame: Controls whether to frame the panel. If ``True``, uses ``layout.box()``. If ``False``, uses ``layout.column()``. :param bool header: Controls panel header display style. :param expand: Controls initial expansion state of panel. ``True``: start expanded, ``False``: start collapsed, ``None``: retain previous state. :type expand: Optional[bool] :param area: :attr:`Area.type ` the panel should be drawn against (e.g. ``'VIEW_3D'``, ``'PROPERTIES'``). Useful when drawing an editor-specific panel (such as ``VIEW3D_PT_*``) from a popup dialog or a different editor, so the panel's ``poll`` / ``draw`` can resolve the expected ``space_data``. Use ``None`` or ``'CURRENT'`` to keep the current context. :type area: Optional[str] :param bool root: If ``True``, draws the panel directly on the current ``pme.context.layout`` without wrapping it in an extra ``box()`` / ``column()``. When ``True``, the ``frame`` and ``layout`` parameters are ignored. Use this to avoid an extra layer of nesting in tightly controlled layouts. :param bool poll: Controls whether to execute the panel's ``poll`` method. If ``True``, checks the panel's display conditions. :param layout: Specify a custom layout. :type layout: Optional[Any] :return: True :rtype: bool **Example**:: panel("MATERIAL_PT_context_material", True, True, True) # Change panel size L.scale_x = 0.8; panel("USERPREF_PT_interface", layout=L.box()) # Draw a 3D View panel from a popup dialog panel("VIEW3D_PT_tools_meshedit_options", area='VIEW_3D') # Draw without an extra wrapping box/column panel("MATERIAL_PT_context_material", root=True) ---- --------------------- Auto-run Scripts --------------------- PME can execute Python scripts automatically when Blender starts. ``autorun`` uses ``exec(...)`` in PME's execution namespace rather than ordinary Python module imports. It searches two locations: - System: bundled ``assets/scripts/autorun`` - User: ``scripts/autorun`` under :ref:`user_resources` Startup scans the **system location first, then the user location**. The user location accepts: - Direct ``.py`` files - Folders containing scripts - Symbolic links .. note:: ``pme`` and ``bpy`` are already injected as globals in autorun scripts. Scripts intended for PME's ``autorun`` or ``execute_script()`` normally do not need ``import pme`` or ``import bpy``. Add ordinary imports as needed if the file must also run independently in Blender's Text Editor or as a Python module. .. warning:: - Only place and execute scripts from trusted sources. - Review their contents; use backups or a test environment when appropriate. - Scripts may change files, settings, or other parts of your environment. ---------------------------- Add Custom Global Functions ---------------------------- A common use of autorun is to register helper functions at startup for reuse in Command and Custom: 1. Place a ``.py`` file in ``scripts/autorun`` under :ref:`user_resources`. 2. Register the functions with ``pme.context.add_global()``. Minimal example: .. code-block:: python def hello_world(): message_box("Hello World") pme.context.add_global("hello", hello_world) The registered ``hello()`` is available in: - Command - Custom - External files run with ``execute_script()`` A more practical example: .. code-block:: python def active_object_name(default="No Active Object"): obj = C.active_object return obj.name if obj else default def show_active_object_name(): overlay(active_object_name()) return True pme.context.add_global("active_object_name", active_object_name) pme.context.add_global("ao_name", active_object_name) # Register a short alias pme.context.add_global("show_active_object_name", show_active_object_name) After registration, call these functions from PME scripts: .. code-block:: python # Command tab show_active_object_name() .. code-block:: python # Custom tab L.label(text=ao_name(), icon='OBJECT_DATA') .. seealso:: - :ref:`user_resources` - :ref:`boot_options` for external scripts that need ``import pme``. ------------------- PME Components ------------------- PME maintains a global context that provides access to commonly used functions, variables, and user-defined additions. This context is accessible through two main interfaces: .. py:class:: pme.context .. py:attribute:: globals :type: dict Access PME's global context dictionary. Contains: - Built-in shortcuts (``C``, ``D``, ``O``, ``L``, etc.) - Registered custom functions and values - User data storage (``U``) .. code-block:: python from pie_menu_editor import pme # Access globals from external scripts g = pme.context.globals props = g.get('props') user_data = g.get('U') .. py:method:: add_global(key, value) Register a custom function or value in the global context. :param str key: Name for accessing the item :param value: Function or value to register :rtype: None .. code-block:: python # Register a function def my_tool(): bpy.ops.mesh.select_all(action='TOGGLE') pme.context.add_global("toggle_select", my_tool) # Register a constant pme.context.add_global("MAX_ITEMS", 10) # Access from PME menus via Command tab: # toggle_select() # MAX_ITEMS .. py:class:: pme.UserData Flexible storage for user-defined data that persists during the Blender session. .. py:method:: get(name, default=None) Get a stored value. :param str name: Data key :param default: Value to return if key doesn't exist :return: Stored value or default .. py:method:: update(**kwargs) Update multiple values at once. .. code-block:: python U = pme.context.globals['U'] # Get UserData instance U.update(tool_state="active", count=5) print(U.tool_state) # "active"