Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Open Brush (or at least currently the experimental build that supports plugins) ships with a selection of plugins that are both useful in their own right, great examples of what is possible and hopefully good starting points for people that want to write their own plugins.
This page will list the included plugins along with a brief explanation of what each one does. It is aimed mainly at users. If you're interested in understanding how each plugin was written, then the source code is hopefully clearly written and contains enough comments to make it easy to follow.
Try different brushes. Plugins can have wildly different results for some brush types. Try the hull brushes in particular with some of the Tool Plugins. Also try various animated brushes such as Neon Pulse or Keijiro Brush
You can have more than one plugin type active at once and the combinations can be wild. Try combining a Symmetry Plugin with a Pointer Plugin. Or use a Background Plugin that animates a layer whilst drawing on it with an animated Pointer Plugin. Or maybe all three at once!
Try spinning the Symmetry Widget with a flick of your hand while drawing. A slow spin gives very different results to a fast spin.
(the rabbit/tortoise switch on your brush) is especially effective with some plugins. The smooth strokes you can with tortoise mode can combine beautifly with animated Pointer or Symmetry plugins.
Many of the Symmetry Plugins have additional parameters for color shift. These allow you to create anything from a wild rainbow of different colored strokes, down to subtle shifts of your base color that gives a more natural effect.
Don't forget to try different parameter values. The same plugin can look massively different based on the settings you choose.
Use the Copy button to copy the script to your Open Brush folder and then open it up in a text editor (ideally something like VS Code). Try changing some of the values or modifying a line of lua code here or there.
You can open the Scripts Panel from a button on the
We won't be using the buttons on the bottom row at the moment. These are related to the older which are are useful for controlling Open Brush remotely. However the new plugin scripts are much more powerful and can add new features and modify how Open Brush works interactively.
The other four rows of buttons each relates to a different type of plugin. From the top down they are , , and .
These are the simplest type of plugin. They can change how your brush works while you are painting brush strokes. They can't (currently) create new types of brush but they can change the motion, size, color of the stroke. And they can start and end strokes (for example to create dashed lines). They can even add extra strokes as you paint (for example to create a web of lines back to your starting point). Here are some of the included example Pointer Plugins.
Symmetry plugins supercharge the Mirror tool. Instead of simply adding a single extra stroke mirrored horizontally, Symmetry Plugins can create multiple copies of your brush stroke in any arrangement. The strokes can be mirrored, rotated, reflected or even scaled. They can be oriented around the mirror widget - or they can follow your brush like a flock of birds. Each stroke can have a different color, size or even use a different brush type. Here are some of the included example Symmetry Plugins.
The previous two types of plugin work in conjunction with the regular Brush Tool and simply change or add to the brush strokes you draw. Tool Plugins are are more powerful and can implement entirely new tools. They might create new objects in one go - or modify existing ones. Each plugin can behave in different ways so check each script's instructions to understand what it does. Here are some of the included example Tool Plugins.
The final type of plugin takes the flexibility one step further. Background plugins can do almost anything. They are more complicated to write but they have less restrictions. For a start they run all the time and can listen in to button presses, track controller movement and react accordingly. They can create repeating animations by moving layers around, they can perform actions based on timers or external signals. And you can have multiple Background Plugins active at once (You can only activate one of each of the other plugin types at any one time). Here are some of the included example Background Plugins.
For Pointer, Symmetry and Tool plugins, you can only have one of each type active at any one time. The large button on the left of each row activates and deactivates that plugin type.
The left and right arrow buttons cycle through each installed plugin.
The cog button opens up a panel with controls specific to the currently active plugin. These usually have sliders to change various parameters.
The final button will copy the currently selected plugin into your Plugins folder. You can then view the code, modify it or use it as a starting point for writing your own plugin.
Background Plugins have an extra button and work slightly differently in terms of activating and deactivating. The reason for this is that you can have multiple Background Plugins active at once. The large button on the left will enable or disable all currently active Background Plugins. To turn individual plugins on or off you can click the button with the eye icon when that plugin has been selected with the arrow buttons.
Plugins can define various parameters so you can easily change how they work without needing to edit the script. Each slider will show a tooltip when you hover over it.
Drag the slider to change it's value. If you need more precision you can:
While hovering you can
Move the thumbstick on your brush controller left and right to change the value
Click the small arrows at either end to apply a set increment to the value
When you've finished you can click the tick button at the bottom or simply move your hand away from the panel to close it.
Eventually we hope to have an "App Store" for plugins so you can browse and install ones that the community have created. In the meantime you can check our Plugins Discord channel for info on any plugins.
Place plugins inside your Open Brush/Plugins folder. Some plugins might use extra libraries. These should be placed in Plugins/LuaModules


A great way to get comfortable with how plugin scripting works - especially if you've never coded before - is to look at existing scripts and modify how they work in small (or maybe not so small!) ways.
Any of the example plugins can be copied to your Open Brush plugins folder simply by clicking the copy button for that plugin type:
(If this button isn't visible then a script of that name already exists in your plugins folder - you've probably already clicked the button for that particular plugin).
Once a plugin script does exist in your plugins folder you can edit it. Any changes you make will take effect immediately - there's no need to restart either Open Brush or the plugin itself. Any syntax errors might stop the plugin working. If so these are displayed on the console on the back of your brush controller. They will tell you which script and which line number has the problem.

Tool Plugins can do many different things but they are often used for drawing a lot of brush strokes all in one go. For example you could draw an entire spiral shape whenever the user clicks the trigger. Or you could use the positions: where they first pressed the trigger in and where their brush controller was when they released the trigger - to get the size of a region and create a shape that filled that space.
To create a Tool Plugin name your script with the prefix "ToolScript". For example: ToolScript.Circle.lua
Tool Scripts should usually return a list of transforms. If they do then this defines an entire brush stroke that is then created for you.
We previously started by showing the simplest possible plugin script - one that does nothing. Because a tool plugin does nothing by default that would be an empty file! But let's put a bit of scaffolding in so you have a starting point for a plugin that actually does something.
function Main()
if Brush.triggerPressedThisFrame then
return {}
end
endIt checks to see if the user pressed the trigger and if so it returns an empty list. So this plugin does nothing and it does it every time the user presses the trigger. Great, huh?
Let's make it do something:
This returns a path with 5 points forming a square. A square has 4 corners so why 5 points? The last point is the same as the first so that the square closes on itself.
If you try this, you'll notice the square isn't very square. This is because Open Brush tries to smooth brush strokes. It expects you to be hand-drawing smooth curvy lines in space - not carefully placing precise geometric paths.
We can fix this by adding extra points:
This time we create a Path object instead of just using a list of transforms. This allows us to use any of the handy methods that are provided by Path - in this case it's the method. This modifies the path by inserting as many new points as we ask for in each path segment. In this case we ask for 5 new points so the square that previously had 5 points (and thus 4 segments) will now have 4 x 5 = 20 points. (I'll let you work out how many segments!)
Next let's draw a circle:
There's a few new things here:
a loop that begins for...do, this is a standard lua construct and works similarly to loops in other languages.
we first calculate a 2d vector using Vector2:PointOnCircle(angle). A Vector2 is very similar to the Vector3 that we used previously for specifying a position. The difference is that it only has two coordinates: x and y.
We convert the Vector2
When you paint freehand in Open Brush, the application is constantly recording your hand's position and orientation. Therefore internally a stroke is defined as a list of transforms. Scale isn't used but the position and rotation are key in defining the shape of the resulting stroke.
There are several situations when you might need to define a stroke when writing a plugin:
The most common use-case is with Tool Plugins. The value you return from the Main() function in a Tool Plugin is drawn as a brush stroke
You can paths explicitly using Path:Draw(), PathList:Draw() or the draw methods provided by classes such as SVG
In both these cases you must define the path as a list of transforms. Both the position and rotation are used to define the shape of the stroke. It matches what happens when you draw a stroke by hand - at each point in time you control both the position of the brush and it's orientation. Orientation doesn't matter for all brushes (a tube brush looks much the same whatever the rotation of the brush controller at each point is) but other brushes such as flat stroke brushes do make use of the rotation values you provide.
In the simple case where you are returning a value from the Main function of a Tool Plugin, you can return a normal lua array (or "table" as they are also called) containing two or more Transform instances:
It's simple to use a lua list like this: {} but there is also a class specifically for definition a list of transforms and it's called . The advantage of using this class is that also has many useful methods for modifying paths.
(The various Draw() methods insist that you use a Path object - so you can't use a simple lua array when you use those.)
Open Brush usually assumes a brush stroke is drawn by hand. Some simplification is done depending on your settings but a stroke is still usually made up of a lot of points forming a smooth curve as the app samples your controller position and orientation many times a second.
When you draw paths via code in a plugin script, you often send only the exact points you want. For example if you were drawing a square then you'd simply define the four points that make up the corners of the square (although you usually repeat the first point at the end if you want a closed shape)
The problem is that by default Open Brush tries to smooth the path you give it so your nice precise square becomes a rounded squiggle. The end result actually varies depending on the brush you use - different brushes use different rules for how to smooth the input points.
But generally speaking - if you want to draw a precise geometric shape then you need to add extra points - and the Path object has some useful methods to do this.
function Main()
if Brush.triggerPressedThisFrame then
return {
Transform:Position(-1, -1, 0),
Transform:Position(1, -1, 0),
Transform:Position(1, 1, 0),
Transform:Position(-1, 1, 0),
Transform:Position(-1, -1, 0)
}
end
endVector3OnZ()Vector3Vector2OnXY()We are using triggerReleasedThisFrame instead of triggerPressedThisFrame. In this example we wait for the user to release the trigger before we do anything. This means we know both the start point (where they pressed the trigger) and the end point (where they released it). These two points are used to determine how the path we return is scaled and rotated. You don't need to do this scaling and rotating yourself - it happens automatically if you return a value representing a brush stroke.
origin = Transform:New(Vector3:New(0, 0, 0), Rotation.forwards)
return {origin, origin:TranslateBy(2, 2, 0)}origin = Transform:New(Vector3:New(0, 0, 0), Rotation.forwards)
path = Path:New({origin, origin:TranslateBy(2, 2, 0)})
path:RotateBy(0, 45, 0)
return pathfunction Main()
if Brush.triggerPressedThisFrame then
myPath = Path:New({
Transform:Position(-1, -1, 0),
Transform:Position(1, -1, 0),
Transform:Position(1, 1, 0),
Transform:Position(-1, 1, 0),
Transform:Position(-1, -1, 0)
})
return myPath:SubdivideSegments(5)
end
endfunction Main()
if Brush.triggerReleasedThisFrame then
points = Path:New()
for angle = 0, 360, 10 do
position2d = Vector2:PointOnCircle(angle)
points:Insert(Transform:Position(position2d:OnZ()))
end
return points
end
endSpeed: The speed of the missile
Stops and restarts the stroke at regular intervals as you draw resulting in a dashed line.
Frequency: How many dashes there are in a given distance. Shorter values gives a result closer to a dotted line.
Spacing: Controls the spacing between dashes
If you use this in conjunction with Lazy Input Mode then you'll need to draw very slowly on the default settings Dashes.
The script callsBrush:ForcePaintingOn and Brush:ForcePaintingOn based on the value of Brush:DistanceDrawn
Locks movement of the pointer to either the x, y or z axis depending on which direction your hand is mostly moving.
Speed: How fast the pointer can move as it tries to keep up with your hand movements
Frames Between Changes: To avoid creating weird unintended artifacts, the plugin ignores changes of direction that happen too soon after the last one. You can control how many frames a direction is "locked in" here.
Draw slowly and deliberately. It can be tricky to get the hang of initially.
The pointer continues moving in the direction you were pointing when you initially pressed the trigger. Pew pew...
Speed: The speed of the beam
You definitely want to try this in conjunction with Symmetry Plugins - especially if you also spin the mirror widget.
The pointer moves around a circlular path with your current hand position as it's center.
Speed: The speed of the pointer
Radius: The size of the circle it moves around
Control your pointer with multiple waveforms to create patterns
X Waveform Type: The waveform used along the x axis
X Frequency: The frequency for the x axis waveform
Y Waveform Type: The waveform used along the y axis
Y Frequency: The frequency for the y axis waveform
Y Phase: The phase of the y waveform relative to x
Radius: A scaling factor applied to both axes
Tips
The values for waveform type are as follows:
0=Linear
1=Cosine
2=Triangle
3=Sawtooth
4=Square
5=Pulse(0.8)
6=Pulse(0.2)
7=Exponent
8=Power(2)
9=Power(0.5)
10=Parabolic
11=Exponential Sawtooth
12=Perlin Noise
13=White Noise
14=Brown Noise
15=Blue Noise
The brush draws a path around your current hand position similar to [Loops](example-pointer-plugins.md#loops) - except the path is a polygon instead of a circle.
Points: How many sides the polygon has
Size: The outer radius of the polygon
At regular intervals, ends a stroke and starts a new one with a different color. The effect is similar to dashes but with the smallest possible gap between sections of the stroke.
Rate: The number of frames between color changes.
Hue Shift Frequency: How fast to cycle the hue
Hue Shift Amount: How much the hue changes
Moves the pointer in a simple wave pattern as you draw.
Frequency: How close together the peaks of the waveform are
Amplitude: The height of the waveform
Creates spherical patterns around the initial point you start drawing. The distance you move affects the patterns progress but the position is always centered around the initial point.
U Scaling: The rate of change of the pattern horizontally around the sphere
V Scaling: The rate of change vertically around the sphere
Radius: The size of the sphere
The brush stroke moves in a circle but the radius increases the longer you keep the trigger pressed.
Speed: Controls the tightness of the spiral via changing the rate at which the pointer rotates around the brush position
Radius: The overall size of the spiral
Draw a spirograph pattern as you move your brush. Ignores your hand position and simply uses the distance you've moved.
Additional lines are drawn from the initial point you started drawing to your current brush position.
Rate: How many frames to wait before drawing another connecting line
As you draw you only control the x and z position of the stroke. The y position (the height) is determined by a noise function that maps out hills and valleys.
Scale: The scale of the terrain. Larger values spread out hills and valleys more, smaller values make them smaller
Height: The height of the terrain. (Scales everything in the vertical direction)
Offset: The starting distance of the terrain off the floor
If you want to use a hull brush then draw small patches or else any valleys will be filled in. For hull style results you will probably be better off using the low poly landscape Tool Plugin
As you draw the position is controlled by your hand as normal. However the orientation of the stroke spins around by itself
Speed: How fast to spin around the axis
Try this with a broad flat brush like Paper or a brush that has an interesting profile shape such as Faceted Tube
This spins around the z-axis. i.e. the direction your brush is pointing. You will only see an effect of you draw strokes forwards or backwards along this axis.
The brush position cycles back and forth between your brush hand and your other hand. Paint with both hands for a change...
Frequency: The speed to oscillate between the brush and the wand
Amplitude: Controls how much to move. 0 is just the midpoint between both hands, 1 will reach both hands, 2 will overshoot both hands
Move your hands close together or further apart to see different effects
The brush stroke wanders off in random directions while you hold the trigger
Speed: How fast the pointer should move
Frames Per Path: After this many frames, the stroke is restarted from the current brush position
Randomizes the brush position
Wiggle Amount: The amount of randomness to add to the brush position
Like Wiggle but uses a smooth noise function
Position Amount: The amount the noise affects the brush position
Rotation Amount: The amount the noise affects the brush orientation
Frequency: The scale of the noise function (higher = more jagged)

In theory you could use any text editor to write and edit your plugin scripts: Notepad on Windows or TextEdit on MacOS.
However an editor that is designed for writing code has many useful features to make your life easier. You might already have a favourite editor. However one thing to consider it whether it supports Lua properly - and also if allows you to use the autocomplete hints that we automatically provide.
These hints will allow your editor to suggest method names, offer tooltips for parameters and even spot errors in the types you're using in your code.
If you're not sure then the simplest thing to do is just use Visual Studio Code:
If you look in your Documents/Open Brush/Plugins folder there is a subfolder called LuaModules and in there are a few commonly used libraries, that will provide some useful features.
However one file is different. It contains empty definitions for all available API methods and properties along with special comments that can be used by the EmmyLua plugin to give you working autocomplete, intellisense and tooltips. This makes writing scripts and finding bugs much easier.
If you're using Visual Studio Code then follow these steps: 1. Launch the Plugins build of Open Brush at least once so that it creates the Open Brush/Plugins folder.
Launch Visual Studio Code.
Click on the button on the left that shows the Extensions sidebar:
Search for "Lua" and look for the sumneko extension:
Click to install it and wait for it to finish installing
Click the small cog icon and then open the Extension Settings:
Optionally you can hide a lot of spurious warnings by disabling "lowercase-global" warnings under Lua> Diagnostics: Disable: (In our plugins we're using upper and lower case initial letters to distinguish your stuff from the API supplied-things but standard lua style is to use it to differentiate local from global. Don't worry for now...)
As soon as you type the period you should see a list of suggestions that match the "App" part:
If not - check you've followed all the steps above.
Now you've got a code editor installed and a plugin to help make things easier, you can move on to tweaking the example plugins and maybe even writing your own plugins from scratch.
Plugins are written in the scripting language which is designed to be simple to learn and easy to understand. There's plenty of tutorials online and Lua is widely used in games and applications such as , , , and many others.
If you've written scripts in any other language then you'll find it easy to pick up.
If you've never programmed at all then Lua is a great place to start. Copy an example script to your Open Brush folder (there's a button next to each Plugin type that does this for you), open it up in a text editor and try changing things.
As soon as you save your changes, Open Brush will load the new version. If you've made a mistake then the console on the back of your brush hand will tell you what line the error is on.
An example of drawing procedurally on startup and then using shader parameters to animate the strokes it has created
An example of drawing continuously. It draws random straight lines for as long as the script is active.
Randomly downloads a panorama from Openverse and sets it as the current skybox. Not every skybox is valid so you might need to toggle the script a few times. Also - they are quite large files. If you're short of space - make sure you have a clear out occasionally!
















In addition to the API commands you can use most of the Lua Standard Library. Another included library is Lume which you can use via require "lume"
The following realtime values from the sketch are examples of values that are available to use in your scripts. There are many more. Here is a full list of methods and properties for each type.
Brush.position: The current position of the brush pointer relative to the canvas
Brush.rotation: The current orientation of the brush pointer relative to the canvas
Brush.colorRgb: The current brush color
Brush.colorHsv: The current brush colour converted to HSV
Brush.size: The current brush size
Brush.pressure: The current pressure of the brush pointer (how hard the trigger is being pressed)
Brush.type: The current brush name
App.time: The time in seconds since Open Brush was launched
App.currentScale: The current scale of the canvas
Sketch.strokes A list of strokes in the current sketch
Scripts can specify parameters that appear as UI widgets such as sliders. These appear in a popup when you click the "3 dots" button on the right of each row. For example you might want a script to have sliders to control "speed" or "wigglyness".
You define the widgets for each parameter in your script. Here's an example:
Here we have defined two widgets. Both are floats (rather than integers) and we have defined a minimum, a maximum and a default value. You can set the ltext abel that appears when you hover over each slider.
The name of each widget (here "speed" and "radius") are then available to the script as variables.
By default each script type works relative to an origin and has a rotation that makes sense for each of the three types. Pointer and Tool Scripts are relative to the user's brush hand and Symmetry Scripts are relative to the Symmetry Widget.
You can override this. For example, here's a PointerScript that is relative to the canvas. We can then position the pointer so it is always at y=0 (the floor) but still tracks the pointer in the x and z directions:
Note that we have to manually specific pointer.position.x and pointer.position.y. If we were using space="pointer" (the default) then we wouldn't need to as coordinates are automatically relative to pointer.position
Valid spaces are currently "pointer" or "canvas".
Parameters = {
speed={label="Speed", type="float", min=0.01, max=32, default=16},
radius={label="Radius", type="float", min=0.01, max=5, default=1},
}
function Main()
angle = App.time * speed
r = Brush.pressure * radius;
pos = Vector3:New(Math.Sin(angle) * r, Math:Cos(angle) * r, 0)
return Transform:New(pos)
endSettings = {space="canvas"}
function Main()
return Transform:New(
Vector3:New(Brush.position.x, 0, Brush.position.z)
)
endScroll down to Lua> Workspace: Library and add the path to your LuaModules folder:
Now we've finished with settings we want to make sure it all works. We will start a simple script to check the code completion is working. Create a new file in your Documents/Open Brush/Plugins folder (not inside LuaModules) and name it PointerScript.Test.lua Then start typing:





Like spinning the mirror by hand but with precise control
Speed: How fast to spin
Angle X: The axis tilt in the X direction
Angle Y: The axis tilt in the Y direction
A flock of pointers follows your hand using simple rules to control how they fly
Number of copies: How many boids in the flock
Separation Amount: How strongly each boid tries to stay away from others
Alignment Amount: How closely the boids try and follow the flocks path
The pointers follow your brush position but each one moves towards a point where your hand was further back in time
Copies: The number of strokes to draw at once
Delay per copy: How far back in time (in frames) for each copy
Amount: How far between the current and past position to move towards. 0.5 is the midpoint between past and current
Copies of your stroke forming an ellipse - with optional color shifts
Copies: The number of strokes to draw at once
Eccentricity: How elliptical the shape is
Axis Consistency: Controls how much the elliptical axis follows your brush position
A simple frame of points following your brush position
Like except the widget always moves to where you start drawing
Copies: The number of strokes to draw at once
Linear copies of your stroke with optional color shifts
Copies: The number of strokes to draw at once
Distance: How far each copy is from the next
Radial copies of your stroke with optional color shifts
Copies: The number of strokes to draw at once
Works like activating Multimirror but the extra pointers spin around your brush position
(todo)
Multiple copies of your brush spaced between your left and right hand positions
Copies: The number of strokes to draw at once
Copies of your stroke forming an polygon
Copies: The number of strokes to draw at once
Sides: The number of sides of the polygon
Copies of your stroke forming an rectangle
Number of points along width: How many points along the sides
Number of points along height: How many points along the top and bottom
Spacing: The distance between each point
Exterior Only: Whether to create copies just around the perimeter or also fill in the middle
Multiple copies of your brush spinning around your actual brush position
Copies: The number of strokes to draw at once
Speed: How fast the extra pointers are rotating
Radius: The radius of the circle they are rotating around
The previous stroke you drew is used as a template and multiple copies of the pointer are spread along it as you draw
Point Spacing: The distance to space out the copies along the stroke
Copies of your stroke forming a
Copies: The number of strokes to draw at once
n: The parameter that controls the overall shape of the superellipse
Eccentricity: How elliptical to make the shape
An example of using an SVG file as a template for symmetry patterns
Similar to but centered around the Symmetry Widget
Point Spacing: The distance between each pointer around the shape
Symmetry Plugins are similar to Pointer plugins with a few differences:
They can return a list of transforms that represent multiple pointers.
They can modify the color, size and brush type for each of the strokes separately.
They have access to the Symmetry Widget which can be used as a point of origin for each pointer. If you've used the Mirror or MultiMirror features in Open Brush then the Symmetry Widget is like the Mirror Widget that controls the reflection plane and center for those mirrors.
Bear in mind - the number of pointers you generate cannot change once a brush stroke has begun, but can change between each brush stroke.
Make sure you name your symmetry plugin scripts with the prefix "SymmetryScript". For example SymmetryScript.MyMirror.lua
Let's start with the simplest possible Symmetry Plugin:
In lua you can define a list of items by enclosing them in curly braces. This example is almost identical to the simplest possible Pointer plugin except that we wrap the transform in curly braces.
You might expect that it behaves the same - i.e. nothing changes and you paint as normal. However if you try it, you'll find that's not the case. In fact - you can't paint at all - your pointer remains stubbornly stuck to the center of the Symmetry Widget.
Why is this? By default Pointer Plugins and Symmetry Plugins use a different . This is how we describe how where the origin is and how translations and rotations are interpreted. Pointer plugins default to using the current brush controller position as the origin so all coordinates are relative to that. Symmetry plugins on the other hand default to using the Symmetry Widget as the origin. So returning a transform that does nothing means that the pointer is stuck at the widget's center.
You can change the default coordinate space by adding a value to the script settings:
This plugin will now behave the same as the "do nothing" pointer plugin example. Using space="pointer" in a Symmetry Plugin is useful if you want to ignore the widget and have all the pointers move relative to the brush controller.
Let's do something more interesting in our Symmetry Plugin:
Here we are returning a list of 4 transforms so we will have four separate pointers all creating a stroke at the same time.
Transform.Lerp is a method that takes two transforms as input and blends them by an amount given by the third number. So Transform.Lerp(a, b, 0.5) will return a transform that is exactly half-way between a and b. It's position will be at the midpoint of a and b and will have a rotation and scale exactly halfway between those of a and b.
What are we passing into Transform.Lerp? origin is set to Transform.identity which is our do-nothing transform. As mentioned before - in this case it will be the center point of the symmetry widget.
brushOffset is a with it's position set to the value ofSymmetry.brushOffset. This is a which represents the position of the user's brush controller relative to the symmetry widget. It is a special value that only makes sense in the context of a Symmetry plugin and you won't use it in other types of plugin script.
Usually the value of the user's brush controller is given byBrush.position. This is because by default, symmetry scripts use coordinates centered on the symmetry widget and you'd have to do a some complicated calculations to get from there to the current brush position. If you use symmetry.brushOffset this is done for you.
So this script will create 4 pointers that are spaced evenly between the symmetry widget and the user's brush controller as they paint. Give it a try!
Finally - let's look at one last symmetry script.
Some things to note here:
Instead of creating a list of transforms using curly braces we are creating a Pathobject and then using Insert to add pointers to it one at a time. Path is just a class that stores a list of transforms. It has some useful methods for transforming and modifying itself so is often more useful than just a simple list.
We are using a loop: for i = 1, copies do
You should have come across similar constructs in other scripting languages. Any lua tutorial will explain some small differences with lua loops but in general they should behave as you would expect.
Background Plugins run all the time when activated. They are useful for animating layers or continually generating new shapes. You can still check for user actions such as pressing the trigger and change the action accordingly. They are very versatile but because they run continuously in they background, they can affect performance (especially if you run several at the same time).
Always use one of the other script types in preference if it is more suitable.
Unlike other plugin types, Background Plugins don't expect you to return a value and they don't do anything with any values you do return.
In contrast other plugin types use return values as follows:
Pointer Plugins use the returned value to change the pointer position, rotation or scale.
Symmetry Plugins use the returned values to create multiple new pointers.
Tool Plugins can use the returned value as the path of a new brush stroke which is drawn immediately.
You can of course draw in a Background Plugin but it's left to you to do so explicitly:
There are some potentially new details to be aware of here:
We are defining our own function to generate a random position around the current brush position
We are using the Random class to generate a point that is never more than a certain distance away from the origin. We then add that to the current brush position
We are manually drawing the path with myPath:Draw()
Pointer Plugins can modify the pointer position and/or rotation every frame. You can get the current position and rotation so you can simply add an offset to those - or you could create an entirely new position or rotation based on any of the other context variables that are available to the script.
Name a Pointer Script with the prefix "PointerScript". For example: PointerScript.MyPlugin.lua
Assuming your script is valid (i.e. it has no syntax errors) then giving it a valid prefix is all you need for it to appear in the Scripts Panel.
By default a Pointer Script returns a which represents how the pointer moves relative to it's normal position (which in this case is wherever the user moves their brush hand)
The simplest possible pointer plugin would be something like this:
This does nothing at all. It returns a transform called "identity" which means "no change at all". There are several other ways to express the same thing:
There's no difference between these - it's just sometimes more convenient or clearer to write things in different ways.
function Start()
print (App.angleStepis always 36 in this case but if you wanted to,copies could be a slider that the user sets so we calculate how many degrees to add to the angle for each copy of the pointer.You don't have to calculate the position of each pointer around the circle. This is done automatically for you. The final position is calculated based on the symmetry widget's transform and the rotation value you return for each pointer. This saves you from having to do some pretty gnarly maths yourself in symmetry plugins.
function Main()
if Brush.triggerPressedThisFrame then
myPath = Path:New()
position = getRandomPosition()
myPath:Insert(Transform:New(position))
myPath:Insert(Transform:New(position))
myPath:Draw()
end
end
function getRandomPosition()
return Brush.position + Random.insideUnitSphere
endfunction Main()
return {Transform.identity}
endSettings = {space="pointer"}
function Main()
return {Transform.identity}
endfunction Main()
origin = Transform.identity
brushOffset = Transform:New(Symmetry.brushOffset)
return {
Transform:Lerp(origin, brushOffset, 0.2),
Transform:Lerp(origin, brushOffset, 0.4),
Transform:Lerp(origin, brushOffset, 0.6),
Transform:Lerp(origin, brushOffset, 0.8)
}
endfunction Main()
copies = 10
pointers = Path:New()
angleStep = 360 / copies
for i = 1, copies do
angle = i * angleStep
pointer = Transform:New(Symmetry.brushOffset, Rotation:New(0, angle, 0))
pointers:Insert(pointer)
end
return pointers
end











Draws a cube centered on the position you first press the trigger with the size and orientation controlled by where you release the trigger.
Point Spacing: The distance between control points for the strokes that make up the cube
Inset Amount: How much to inset each face towards it's center
Draws tiles that follow a hilly landscape as you hold the trigger.
Scale: The scale of the landscape. Smaller values make hills and valleys closer together
Height: The height of the landscape (Controls the vertical scaling)
Offset: The distance from the floor to position the landscape
Grid Size: The size of the grid used. Bigger values are more "low poly"
Creates wireframe brushstrokes for any of the 5 Platonic solids (Tetrahedron, Cube, Octahedron, Dodecahedron, Icosahedron)
(todo)
Calls an API to generate a random SVG icon using the MultiAvatar library
Draws lines from the position you start drawing to your current position.
Spacing: How often to draw a new stroke
Switches to the Hull Brush and draws cubes of random size and color as you move your brush",
Maximum Size: Controls how big the cubes can be
Spread: Larger values allow cubes to be placed further from your brush position
Amount: 0.5 gives a random 50/50 chance of creating a new cube each frame. Smaller values produce less cubes, larger values more.
Draws a conical spiral.
Number of turns:
Number of steps per turn:
Draws a spherical spiral.
Steps:
Turns:
Draws a supershape using the Super Formula
Symmetry:
n1:
n2:
n3:
Draws a heart shape using an SVG Path
Point Spacing:
Draws a brush stroke forming a variety of interlocking knot shapes known as Torus Knots
(todo)
Draws a blocky landscape (best used with a hull brush).
Horizontal Spacing:
Verticle Spacing:
Draws regular blocks in space as you draw (best used with the hull brush)
Grid Size:
Draws words that follows your brush. Tries to access the clipboard so try copying in some text.
Size:
Spacing:

Vector3.up is another shorthand - this time it's shorthand for Vector3:New(0, 1, 0) this is a vector with:
x is 0 (no change in the left/right direction) y is 1 (a shift of 1 in the up direction) z is 0 (meaning "no change in the forward/back direction")
So - this plugin tells Open Brush "the user's pointer should be moved up by 1 unit from whereever they are painting". If you try it you should see your pointer is about 10cm above wherever you move your brush hand.
So this is not very useful - but we at least we can see we are changing something now.
Let's try something slightly more interesting.
App.time is a property of the App class where you can access various properties of Open Brush app itself. As you might have guessed App.time measures time - specifically the number of seconds since you launched Open Brush.
This plugin script will result in your strokes being painted somewhere way higher than the user's brush hand. How high depends on how long since they opened the app! Let's rein things in a bit.
That single return line is getting a bit complicated. Let's split things over two lines to make it easier to read:
If you've done any programming or scripting before, you should recognise what we've done here. "y" is a variable. We chose "y" as the variable name but we could call it anything we want.
We've assigned a value to "y" which is the result of calculating a waveform shaped like a triangle
The output of the Waveform:Triangle method takes two parameters as input time and frequency. It returns a value that varies between -1 and +1. If you've configured your editor as explained in Getting Started then you should have seen a hint pop up when you typed the first open bracket:
So we now have a plugin that moves the pointer up and down over time. You can change the second parameter to control the speed.
Let's go one step further - allow the user the control the speed without needing to edit the plugin script:
We've now told Open Brush that this plugin should show a slider in it's Parameters popup which changes the frequency that the pointer moves.

function Main()
return Transform.identity
end-- Explicitly returns zero for position, rotation and scale.
return Transform:New(Vector3.zero, Rotation.zero, 0)
-- If you miss out rotation and/or scale they are assumed to be zero
return Transform:New(Vector3.zero)
-- Vector3.zero is itself shorthand for this:
return Transform:New(Vector3:New(0,0,0))
-- You can create a transform with only a position in a slightly shorter way:
return Transform:Position(0,0,0)function Main()
return Transform:New(0,1,0)
end
-- or
function Main()
return Transform:Vector3.up
endfunction Main()
return Transform:Position(0, App.time, 0)
endfunction Main()
return Transform:Position(0, Waveform:Triangle(App.time, 1), 0)
endfunction Main()
y = Waveform:Triangle(App.time, 1)
return Transform:Position(0, y, 0)
endParameters = {
frequency={label="Frequency", type="float", min=0.01, max=10, default=2}
}
function Main()
y = Waveform:Triangle(App.time, Parameters.frequency)
return Transform:Position(0, y, 0)
end








