Skip to content

Python Plugin Development

crisprintsstuff edited this page Nov 5, 2026 · 3 revisions

Python Plugin Development

Starting with v0.5.0-alpha, OxyBrush includes an in-app Python plugin and scripting engine. You can write custom automation scripts, procedural mesh generators, pipeline exporters, and custom sculpting tools using standard Python 3.


Architecture Overview

OxyBrush uses an out-of-process, zero-overhead IPC (Inter-Process Communication) architecture:

+------------------------------------+          TCP Socket (JSON-RPC)
|      OxyBrush Desktop App          | <------------------------------------+
|  (Rust / wgpu / egui UI)           |                                      |
|  - In-app "Plugins" menu           |                                      |
|  - Spawns background worker process|                                      |
|  - Injects OXYBRUSH_IPC_PORT       |                                      |
+------------------------------------+                                      |
                                                                            |
+-----------------------------------------------------------------------+   |
| Python Plugin Script (*.py)                                           | --+
| - Pure Python 3 (zero non-standard dependencies)                      |
| - import oxybrush                                                     |
| - session = oxybrush.attach()                                         |
| - session.add_primitive(...) / session.apply_stroke(...)               |
+-----------------------------------------------------------------------+

Why this design?

  • Crash Isolation: If a Python script throws an exception or enters an infinite loop, OxyBrush remains completely responsive and stable.
  • Pure Python 3 SDK: The oxybrush client library uses only the Python standard library (socket, json, dataclasses, sys). No C-extensions or external wheels are required.
  • Any Python Environment: Works with your system Python 3 (python3 / python), Anaconda, Pyenv, or virtual environments.

Plugin Directory Locations

OxyBrush automatically scans for .py scripts in the user plugins folder upon startup:

Platform Plugins Directory Path
Linux ~/.local/share/oxybrush/plugins/
macOS ~/Library/Application Support/oxybrush/plugins/
Windows %APPDATA%\oxybrush\plugins\ (e.g. C:\Users\<User>\AppData\Roaming\oxybrush\plugins\)
Local Workspace ./plugins/ relative to current working directory

You can quickly open this folder at any time from inside OxyBrush by clicking Plugins > Open Plugins Folder.


Writing Your First Plugin

A plugin is simply a .py file placed inside your plugins directory. OxyBrush reads top-of-file metadata comments to populate the Plugins menu.

Example 1: mesh_stats_reporter.py

Create a file named mesh_stats_reporter.py in your plugins folder:

#!/usr/bin/env python3
# Name: Mesh Stats Reporter
# Description: Prints vertex count, triangle count, and piece metrics
# Author: Your Name
# Version: 1.0.0

import os
import sys
from pathlib import Path

# Auto-locate provisioned OxyBrush SDK if running standalone
if "oxybrush" not in sys.modules:
    for candidate in [
        Path(os.environ.get("LOCALAPPDATA", "")) / "OxyBrush" / "python",
        Path(os.environ.get("APPDATA", "")) / "OxyBrush" / "python",
        Path.home() / "AppData" / "Local" / "OxyBrush" / "python",
        Path.home() / ".local" / "share" / "oxybrush" / "python",
        Path.home() / "Library" / "Application Support" / "oxybrush" / "python",
    ]:
        if (candidate / "oxybrush" / "__init__.py").is_file() and str(candidate) not in sys.path:
            sys.path.insert(0, str(candidate))
            break

import oxybrush

def main():
    # Automatically attach to the running OxyBrush instance
    session = oxybrush.attach()
    
    # Query mesh statistics
    stats = session.get_mesh_stats()
    
    # The first line printed appears directly in the OxyBrush status bar:
    print(f"{stats.piece_count} piece(s) | {stats.triangle_count:,} triangles | {stats.vertex_count:,} vertices")
    print(f"Bounds: min {stats.bounding_box_min} .. max {stats.bounding_box_max}")

if __name__ == "__main__":
    main()

Click Plugins > Reload Plugins in the top menu bar. Your plugin will appear under the Plugins menu. Clicking it will run the script and display the output!


Example 2: Procedural Spiral Generator (spiral_generator.py)

This plugin creates an organic Fibonacci spiral of primitives and applies an initial clay stroke:

#!/usr/bin/env python3
# Name: Spiral Generator
# Description: Procedurally generates an organic spiral of spheres
# Author: OxyBrush Team
# Version: 1.0.0

import math
import oxybrush

def main():
    session = oxybrush.attach()
    
    num_spheres = 18
    print(f"Generating spiral with {num_spheres} spheres...")
    
    for i in range(num_spheres):
        angle = i * 0.45
        radius_curve = 0.8 + (i * 0.15)
        x = math.cos(angle) * radius_curve
        y = (i * 0.2) - 1.5
        z = math.sin(angle) * radius_curve
        
        sphere_size = 0.25 + (i * 0.04)
        session.add_primitive(
            primitive_type="sphere",
            position=(x, y, z),
            radius=sphere_size
        )
    
    # Connect them into a single seamless organic volume
    print("Unifying geometry with voxel remesh...")
    session.voxel_remesh(voxel_size=0.03)
    print("Spiral generation complete!")

if __name__ == "__main__":
    main()

Python SDK Reference (oxybrush)

Install or copy the oxybrush package into your Python environment:

pip install /path/to/OxyBrush/python
# or install in editable mode for development:
pip install -e /path/to/OxyBrush/python

Connection Methods

oxybrush.attach(timeout: float = 5.0) -> Session

Connects to the currently running OxyBrush application using the environment variable OXYBRUSH_IPC_PORT. If the variable is not set, it attempts to connect to the default port 9876.

oxybrush.connect(port: int, host: str = "127.0.0.1", timeout: float = 5.0) -> Session

Explicitly connects to an OxyBrush application instance running on a specific host and port.


Session Methods

session.get_mesh_stats() -> MeshStats

Returns a MeshStats dataclass containing:

  • vertex_count: int
  • triangle_count: int
  • piece_count: int
  • subdivision_level: int
  • bounding_box_min: tuple[float, float, float]
  • bounding_box_max: tuple[float, float, float]

session.add_primitive(primitive_type: str, position: tuple[float, float, float] = (0,0,0), radius: float = 1.0) -> dict

Spawns a new primitive into the scene.

  • primitive_type: "sphere", "cube", or "cylinder".
  • position: (x, y, z) center coordinates.
  • radius: bounding scale radius.

session.apply_stroke(brush: str, points: list[tuple[float, float, float]], radius: float = 0.2, strength: float = 0.5) -> dict

Simulates an automated sculpting stroke across a list of 3D world-space coordinates.

  • brush: Name of brush (e.g. "Clay", "Smooth", "Inflate", "Draw", "Carve").
  • points: List of (x, y, z) coordinate tuples defining the stroke path.
  • radius: Stroke influence radius.
  • strength: Stroke deformation intensity.

session.voxel_remesh(voxel_size: float = 0.02) -> dict

Triggers an immediate voxel remesh pass on the active piece with the specified voxel resolution.

session.decimate(target_triangles: int) -> dict

Reduces the polygon count of the active mesh down to target_triangles using quadric error decimation.

session.export_mesh(path: str, format: str = "glb") -> dict

Exports the current scene or active piece to disk.

  • Supported formats: "glb", "gltf", "obj", "stl", "ply".

session.call_tool(_tool_name: str, **kwargs) -> dict

Low-level escape hatch to invoke any registered MCP or IPC tool directly by name with arbitrary keyword arguments.

Clone this wiki locally