Introduction



The scripting interface in Moho (Anime Studio) is divided into three "modules":

LM is the lowest-level module, and includes very basic objects like vectors and colors.

LM.GUI is a user interface module that is built on top of Moho's cross-platform user interface library. Scripts can use the user interface widgets in this module to build dialog boxes, and to set up custom interfaces for toolbar buttons.

MOHO is the module that is a direct interface into Moho itself. The objects and functions in the MOHO module allow scripts to create and manipulate layers, vector artwork, bone systems, and more.

Getting started with scripting


Lua


Moho scripts are written in Lua. This describes the basics of Lua: Programming in Lua

If you're already familiar with block-structured languages then you shouldn't have any real problems with Lua.

You will find many descriptions of constants (e.g. Channel Codes, Layer Types ...) in this website. They include the identifier and its value. Note that the value can (and, indeed, on occasion does) change between different Moho versions. When writing code, always use the identifier and never use the value. (The value is provided only to help decipher the contents of moho data files.)

A word about Global variables.
-------------------------------------------------
Rule 1 in the Moho context: if you MUST use a global, then prefix it with some unique to you identifier ... see Script Structure. E.g. Moho uses LM_; other examples are HS_ , syn_, SZ_, AE_ ... It's best to use local variables by default except where necessary -- such as all your script functions that Moho expects to find (e.g. function HS_Myscript:Name() ).

A cautionary word about creating Moho data objects.
-------------------------------------------------------------------------------------
Most of the Moho data objects will be of Lua type "userdata". e.g. as shown below the code
local vec = LM.Vector2:new_local()
creates "vec" as an object of class LM_Vector2
Even though the Moho data type includes the word 'local' it is vital to add the Lua 'local' tag.
if you write:
vec = LM.Vector2:new_local()
(i.e. not expressly defining vec as a local Lua item) this will create a Global variable vec


Lua Versions


Note that the recent versions of Moho (up to and including version 13.5.x) use Lua version 5.2. The language reference manual is here.

Moho 14 uses Lua version is 5.4. There are some significant differences in the math.xxx library and in bitwise operations. The language reference manual for Lua 5.4 is here.


If your script only uses Lua functions that are present and unchanged in both the two versions there is no need to take any specific action. Similarly, if your script is NOT intended to run in both environments unchanged, no action is necessary.
If your script uses Lua functions that were in 5.2 but are now changed / deprecated in 5.4 and you want your script to be usable in both Lua 5.2 and 5.4 environments (i.e. in Moho 13.5 and earlier as well as in Moho 14) there are mechanisms to achieve this.
One approach is:
>> write the script using Lua 5.2 functions
>> include a call to HS_LuaCompatibility () -- ideally this should be in any "run once" code (e.g. LoadPrefs or Run) but others such as IsRelevant will also work.
>> download HS_LuaCompatibility.lua and copy it to the Custom Content / Scripts / Utility folder.
This is a transitionary service and will be deprecated once there is no significant user base for Lua 5.2 (i.e. Moho 13.5 and earlier versions). (See the script download page for more details.)





Interfacing with Moho


You're already in the right place for understanding how to get Moho and your scripts talking to each other...

A good place to start is to take a quick look (don't try to learn them!!) at the "core" classes in Moho : AnimChannel, M_Bone, M_Curve, M_Mesh, M_Point, M_Shape, M_Skeleton, MohoDoc, MohoLayer, ScriptInterface and get a feel for what's in them and how they fit together.

The "mesh" family


Here's a summary of the mesh / point / shape / curve relationships

>> Mesh "holds" all the points. points are numbered in the mesh from 0 ... mesh:CountPoints()-1

>> Mesh also holds "curves" - which are ordered lists of points. Curves are numbered form 0 ... mesh:CountCurves()-1

>> A path (a curve) is made up of points. Points are ALSO numbered in a curve from 0 ... curve:CountPoints()-1 Note that these numbers are different -- e.g. Curve(1): Point(3) might be Mesh:Point(44)

>> A curve is made up of segments. They are numbered 0 ... curve:CountSegments() -1. Generally the start point of segment (x) is point in curve (x). If the curve is closed (i.e. a loop) segments = points; if it's not, there's one more point than segments.

>> A shape has edges; they are numbered 0 ... Shape:CountEdges()-1. An edge is a segment of a curve. One segment can be in many shapes.


Writing your script


This scripting documentation has a Script Structure page that sets out the fundamental elements for menu, tool and layer scripts. There's also template generator accessible from the "tools" button.

If you fancy trying something before starting on a "serious" script, you might like to set yourself a simple challenge - e.g. draw a circle - centre is mouse click, radius is where the "drag" ends. (You could use the LM_Shape tool as a crib sheet for that if you get stuck.) Then build on that to get the circle to glide across the screen ... etc

a code snippet


Most Moho data objects don't need to be explicitly created -- calling something that "returns" a class gives you the userdata object: (e.g.) an M_Point class is returned by M_Mesh:Point(n) so:
local x = 5                                  -- assign a value
local vec = LM.Vector2:new_local() -- LM_Vector2
local mesh = moho:Mesh() -- ScriptInterface:Mesh
local pt = mesh:Point(x) -- M_Mesh:Point
vec:Set(pt.fPos) -- M_Point

sets vec to be the position of point "5"


Creating objects


As above, when a script creates new objects in Moho, it will most often not create them directly, but call a function to do the job. For example, a script never creates a new layer object directly, but tells Moho to insert a new layer:
-- *** Ask Moho to create a new vector layer ***
local layer = moho:CreateNewLayer(MOHO.LT_VECTOR)


Typically the only time a script might need to directly create a new object is for very simple object types, such as points, vectors, and colors. For example, to create a new 2D vector object, a script might do the following:
-- *** Create a new local variable ***
local vec = LM.Vector2:new_local()


-- *** Create a new global variable ***
XX_globalVec = LM.Vector2:new()


Common Problems


Your script throws an error such as "attempt to call method 'XXXXX' (a nil value)"


This is often found where you have the correct method but not the correct class of object.
e.g. myLayer:Layer(0) requires myLayer to be of class GroupLayer and the error will be generated if myLayer has not been cast as such by using ScriptInterface:LayerAsGroup (or, of course, is not any type of layer object)

Generic classes used as the foundation for specific layer types are MohoLayer and AnimChannel

Your vector assignment looks right, but it doesn't work?!


You've defined local MyVec = LM.Vector2:new_local()
You know that mouseEvent.vec is where the mouse event took place.
But MyVec = mouseEvent.vec just doesn't work properly!?

That's because MyVec = mouseEvent.vec creates an instance of mouseEvent.vec -- and that means that MyVec will keep jumping to the mouse position.

To get the value, it's necessary to set each element separately:
MyVec.x = mouseEvent.vec.x
MyVec.y = mouseEvent.vec.y

or use the LM_Vector2:Set(vec2) method: MyVec:Set(mouseEvent.vec)


Similarly for other moho vectors (LM_Vector3, LM_ColorVector ...)



Moho crashes but your code looks right!


Check that you've correctly called a method. e.g. moho:CountPoints() and not moho.CountPoints()

function t:f(arg) has a implicit first arg of self so the definition is actually the same as function t.f(self, arg).
In other words moho:CountPoints() is just shorthand for moho.CountPoints(moho). Generally, scripters will use Class:Method() rather than Class.Method(Class)

Note that attributes (properties) are properly referenced by Class.Attribute