The examples use the print statement for output. To print to the Event Log instead, the lx.out() function can be used.

The current scene

Typically the very first thing to do is to import the modo python module and get the current scene object.

import modo
scene = modo.Scene()

Scene and Locators

Things that can be moved around in the scene are considered locators. They are compatible with the locator interface and can be transformed.

Here are some examples of common tasks involving locator-compatible items within a scene:

# Find a scene object by name
camera = scene.item('Camera')
print camera.type
# Result: camera

# The method scene.items() lists all locator type items of the scene
for item in scene.items(itype='locator', superType=True):
    print item

# To list a specific type, pass it as an argument
for item in scene.items(camera.type):
    print item

# A list of available type constants is available in the module modo.constants or it's shortcut modo.c
# They resolve to strings, so they can be used interchangeably
print scene.items(modo.c.MESH_TYPE)
# Result: [modo.item.Mesh('mesh002')]
print scene.items('mesh')
# Result: [modo.item.Mesh('mesh002')]

# Set all materials to double sided
for material in scene.items('advancedMaterial'):

# Rename all selected items, prefixing them with their type
for item in scene.selected:
        item.name = '%s_%s' % (item.type, item.name)

# Assume you want to arrange all mesh items' positions next to another, it could be done like so:
spacing = 1.5
for i, item in enumerate( scene.meshes ):
    item.position.set( (i*spacing, 0, 0) )

# Parent the light to the camera in the default scene, maintaining it's global transformation
import lx
light = scene.item('Directional Light')
lx.eval('item.parent %s %s inPlace:1' % (light.id, camera.id))

# Select the camera

This is to illustrate how many items are compatible with locators while still having their own unique type

print scene.item('Camera').TestType(modo.constants.LOCATOR_TYPE)
# Result: True

print scene.item('Camera').type
# Result: camera

Creating Morph Maps

    This example
    - creates a subdivided cube
    - generates three morph maps using vector math
    - creates a morph influence for each morph map and connects it to the mesh
    - sets keyframes to animate the influence of them over time


import lx
import math
import modo

    Create a subdivided cube
lx.eval('script.implicit "Unit Cube Item"')

    Subdivide it a few times
for i in range(3):
    lx.eval('poly.subdivide flat')

scene = modo.Scene()
cube, = scene.selected

    Move all points up by 0.5, so that the bottom rests on the ground.
    The 'with' statement is a context manager that implicitly applies the changes when leaving scope.
    Without the with statement one would need to apply the edits by calling cube.geometry.setMeshEdits()
with cube.geometry as geo:
    for v in geo.vertices:
        v += (0, 0.5, 0)

vmaps = cube.geometry.vmaps
num_verts = len(cube.geometry.vertices)

    Create a relative morph map
ballmap = vmaps.addMorphMap('Ball')

    Loop through all vertex positions and simply normalize them to turn the cube into a sphere.
for index in range(num_verts):
    # Get the original position from the mesh
    position = modo.Vector3(cube.geometry.vertices[index].position)
    position.y -= 0.5
    position = position.normal() * 0.5
    position.y += 0.5
    ballmap.setAbsolutePosition(index, position)


flatmap = vmaps.addMorphMap('Flat')

    For this map, we re-use the position values from the ball map,
    then flatten it and scale it up for of a squashy look
for index in range(num_verts):
    position = modo.Vector3(ballmap.getAbsolutePosition(index))
    position.y = 0
    position *= 2.0
    flatmap.setAbsolutePosition(index, position)


    The third map rotates every point by 90 degrees on the xz plane
    And the blends it back to the original position based on it's height
twistmap = vmaps.addMorphMap('Twist')

for index in range(num_verts):
    original = modo.Vector3(cube.geometry.vertices[index].position)

        The copy-method creates a new instance of the vector,
        rather than only creating a new name pointing to same object
        as python does otherwise.
    twisted = original.copy()

        Rotate the vector on the xz plane by 90 degrees
    original.rotateByAxisAngle((0, 1, 0), math.radians(90))

        Blend the two positions based on the y position
    t = original.y
    blended = twisted * (1.0-t) + (original * t)

    twistmap.setAbsolutePosition(index, blended)


    Create a shortcut for FrameToTime for convenience
svc = lx.service.Value()
frame = svc.FrameToTime

    Now that the maps are created, let us create a Morph Influence deformer for each and animate it
for index, morph_map in enumerate([ballmap, flatmap, twistmap]):

        First we add a new item of type 'morphDeform' and give it the same name as our map.
        Then we create a connection in the 'deformers' graph from the deformer to the cube mesh using the >> operator.
        We could disconnect it again using the // operator.
    influence = scene.addItem('morphDeform', name=morph_map.name)
    influence >> cube.itemGraph('deformers')

        This sets the 'mapName' channel to the name of our morph map, so that the influence deformer knows what morph
        shape to use as input.

        Next we look for the 'strength' channel and insert keyframes to it's animation envelope.
    f = index * 20

    ch_strength = influence.channel('strength')

    ch_strength.set(0.0, time=frame(f +  0), key=True)
    ch_strength.set(1.0, time=frame(f + 10), key=True)
    ch_strength.set(0.0, time=frame(f + 20), key=True)

    The deformers will only update in the 3d viewport if the corresponding setting is active,
    so we activate it for the sake of this demonstration.
lx.command('view3d.enableDeformers', state=True)


# This example shows how to create a group, and add or remove items from it.

import modo
scene = modo.Scene()

# Get all objects of superType locator (the ones shown in the item view)
locators = scene.items(itype='locator', superType=True)

# Create a new Group item
grp = scene.addGroup(name='Myitems')

# Add all locator items to the group
# grp.addItems(locators) - currently broken
for item in locators:

# Test if the first locator is a member of the group
# Result: True

# Remove that locator from the group

# Print the contained items
print grp.items
# Result: [modo.Camera('camera003'), modo.DirectionalLight('sunLight013')]


import modo
camera = modo.Camera('Camera')

# Print the names of all available channels
print camera.channelNames

# Get a tuple of all channels with a name that starts with 'f'
for channel in camera.channels('f*'):
    print 'Name: {0:<10} type: {1}'.format(channel.name, channel.evalType)

# Set and get the value of the focal length channel
print camera.channel('focalLen').get()

# Read the value of a matrix channel
matrixObject = camera.channel('worldMatrix').get()
matrix = modo.Matrix4(matrixObject)
print matrix

# Link one translation channel to another
camera.position.x >> camera.position.y

# Query the channel that links to the y channel
print camera.position.y.revLinked
# Result: [modo.Channel('pos.X', modo.Item('translation004'))]

# Unlink the channel connection
camera.position.x // camera.position.y

Gradient Channels

# Create a sphere
import lx
lx.eval('script.implicit "Unit Sphere Item"')

import modo
scene = modo.Scene()
sphere = modo.Mesh('Sphere')

# Add a masking group item
mask = scene.addItem('mask')

# Parent it in the shader tree at index 1, beneath the base shader
mask.setParent(scene.renderItem, index=1)

# Connect the shadeLoc graph of the mask to the one of the mesh.
# This sets the 'Item' attribute found in 'Group' under the 'Texture Layers' tab.
# This way, our gradient will only affect that mesh.
mask.itemGraph('shadeLoc') >> sphere.itemGraph('shadeLoc')

# Create a gradient item and parent it to the mask item
gradient = scene.addItem('gradient')

# Change the value of channel 'param' to 'distanceX', so that the gradient takes 
# the distance in x of it's texture locator to the origin as sampling input.

# Print the type of channel 'color.R'. It returns 'gradstack', indicating that
# it is a gradient channel.
print gradient.channel('color.R').evalType

# The envelope and it's keyframes can be accessed from the channel.
# A gradient channel contains one constant keyframe at time zero by default.    
# This sets values of the keyframes of the red and green channels are set to zero
gradient.channel('color.R').envelope.keyframes[0] = (0, 0.0)
gradient.channel('color.G').envelope.keyframes[0] = (0, 0.0)

# This adds an extra keyframe to the blue curve. 
# The first value of add() is the value and the second is the time
keys = gradient.channel('color.B').envelope.keyframes
keys[0] = (0.5, 0.0)
keys.add(1.0, 1.0)

# Then, obtain that textureLocator item that got created along with the gradient
# item by looking forward in the 'shadeLoc' graph. 
textureLocator = gradient.itemGraph('shadeLoc').forward(0)

# Finally set a few keyframes and set the current time so we see the gradient
textureLocator.position.x.set(value=1.0, time=0.0, key=True)
textureLocator.position.x.set(value=0.0, time=1.0, key=True)
lx.eval('select.time 0.5')

# Lastly, this command opens a preview so we can see the result
lx.eval('layout.createOrClose Preview iView_layout width:500 height:300')

Matrix Channels

# This examples reads a matrix from a matrix channel
# and multiplies it with another

import modo

# Create a locator and set some initial transform values
locator = modo.Scene().addItem('locator')
angles = modo.Vector3(10, 20, 30)
locator.position.set((0.1, 0.2, 0.3))
locator.rotation.set(angles, degrees=True)

# The matrix channel returns an lx.object.Unknown COM object
matrixObject = locator.channel('worldMatrix').get()

# It can be casted to either a core SDK Matrix COM object or
# a TD SDK Matrix one, we choose the latter here.
matrix = modo.Matrix4(matrixObject)

# This converts the matrix back to euler values and tests against the initial values
print matrix.asEuler(degrees=True, order='zxy') == angles
# Result: True

# Create a temporary translation matrix to multiply with, translating by 1 unit in x
matTranslated = modo.Matrix4(position=(1, 0, 0)) * matrix

# Output the position and rotation to a new locator
locatorTranslated = modo.Scene().addItem('locator')

Loading and storing Poses

# Storing and loading poses
# This example will create three finger bones and create a curl pose.
# Afterwards the pose is altered and stored again.
# Note that this example relies on commands. This is because this functionality 
# is not yet exposed in Modo's core SDK and hence not available in the TD SDK.
# Using commands is perfectly fine and even required in many cases.

import modo
import math

scene = modo.Scene()

# Create four joints
joints = [scene.addJointLocator() for i in range(4)]

# Name, position and parent them to appear like a finger
for i, joint in enumerate(joints):
	# Rename
	joint.name = 'finger_jnt_%i' % i
	# Set radius
	# Set position
	if i != 0:
	# Parent to previous joint
	if len(joints) > i > 0:

# Before creating the actor, the last joint is discarded
# because it is not supposed to be part of the pose.

# Create the actor and select it in the scene
actor = scene.addActor(name='Finger', items=joints)

# Rotate each by 100 degrees into the curled pose
for joint in joints:

# Create the new pose using the group.poseCreate command
poseName = 'Curled'

# To test the pose, un-curl the finger rotations
for joint in joints:

# And apply the pose again.
lx.command('group.poseActive', item=poseName)
lx.command('group.poseApply', item=poseName)

# If we decide to change the pose, we need to use the command pose.update
for joint in joints:


Instancing a Mesh

import modo
from random import random as rand

# Create a cylinder
lx.eval('script.implicit "Unit Cylinder Item"')
mesh = modo.Mesh('Cylinder')

# Create an instance of the cylinder
instance = modo.Scene().duplicateItem(mesh, instance=True)

print instance.isAnInstance is True

# Move the cylinder out a little bit

# This lambda function returns a tuple of three random values
randpos = lambda distance: [rand() * distance for i in range(3)]

# Loop through all vertices to displace them
for vertex in mesh.geometry.vertices:
    position = modo.Vector3(vertex.position)
    vertex.position = position + randpos(distance=0.1)


Creating an Assembly

# This example creates a re-usable assembly group
# It will have two matrix input channels and one output matrix channel.

import lx
import modo
from random import random

scene = modo.Scene()

# This is a utility function to create a user channel
def addChannel(item, name, chType='matrix', direction='input'):
    lx.eval('channel.create name:%s type:%s item:%s' % (name, chType, item.id))
    lx.eval('assembly.assignChannel type:%s channel:{%s:%s}' % (direction, item.id, name))

# Create an assembly group
assembly = scene.addGroup(name='My Matrix Assembly', gtype='assembly')

# Create a matrix compose node. What it does is simply multiplying it's two input matrices.
matrixCompose = scene.addItem('cmMatrixCompose')
# This adds the node to the assembly group. We could add more items if we wanted.

# In this block we create the input channels for the assembly and connect them to the node
for inMatName in ['MatrixA', 'MatrixB']:
    addChannel(assembly, inMatName)
    assembly.channel(inMatName) >> matrixCompose.channel('matrixInput')

# Conversely, this block takes care of the output channel and it's connection
outMatName = 'MatrixOut'
addChannel(assembly, outMatName, direction='output')
matrixCompose.channel('matrixOutput') >> assembly.channel(outMatName)

# At this point we have a self contained assembly that multiplies two input matrices and outputs the result.
# The next section creates some locators to put it to effect

# Create three locators and hook them up to the assembly
locators = [scene.addItem('locator') for i in range(3)]

for loc, name in zip(locators, ('Space Local', 'Space Main', 'Driven Locator')):
    loc.name = name

locStart, locEnd, locBlend = locators

locStart.channel('localMatrix') >> assembly.channel('MatrixA')
locEnd.channel('localMatrix') >> assembly.channel('MatrixB')

# Set the last locator to display as rhombus to better distinguish it from the others

# This adds a new transform item or 'layer' into the locator's transform stack to connect to.
# By doing this, the ability to animate the item is preserved.
offsetTransform = locBlend.transforms.insert('position', 'pre', name='MyOffset')
assembly.channel('MatrixOut') >> offsetTransform.channel('matrix')

# Start moving the locators around to see the effect

Event Listener Callbacks

# This is a minimal example of a listener that sets random wireframe colors every time a camera is added to the scene.
# Note that the listener class only ever emits command calls rather than calling methods of the TD SDK directly.
# Attempting the latter would fail, because the listener class provides no undo context.

import lx
import lxifc
import modo

from random import random

# Defining a function that applied the desired changes to an item. It only takes the ident name as a string.
def colorCameraRandom(item):

    # We assume the item can be found in the currently active scene, otherwise silently bail out
        it = modo.Scene().item(item)
    except LookupError:

    if it.TestType(modo.constants.CAMERA_TYPE):

        # This is essentially the same as pressing the 'Add draw options' button in the 'Display' tab,
        # adding more channels to the item by assigning a package

        # This enables the use of a custom color and sets random values
        it.channel('wireColor').set((random(), random(), random()))

# This 'blesses' or registers a command and passes the colorCameraRandom function to it
# We are using the makeQuickCommand to be short and concise, for any non-trivial scenarios I would advise to
# make use of the  pre-made class templates found in the scripting layout drop down menus or the documentation.
modo.util.makeQuickCommand('tdsdkExamples.colorCameraRandom', colorCameraRandom, [('item', 'Name')], 'Color my camera random')

# Now for the listener class we leave the TD SDK world for a moment and set up a very minimal listener class
class MyItemListenerClass(lxifc.SceneItemListener):

    # We need the undo service, to check if it is safe to execute commands
    undoService = lx.service.Undo()

    def __init__(self):
        # Fetch the listener service and ask it to create a new listener server for us
        self.listenerService = lx.service.Listener()
        self.COM_object = lx.object.Unknown(self)

    def __del__(self):

    # For this example we only override the sil_ItemAdd method.
    # The full method list can be found in extra/Python/Modules/lxifc.py or the SDK documentation.
    def sil_ItemAdd(self, item):

        # Bail out if there is no undo context. This should always be checked when calling commands from servers
        if not MyItemListenerClass.undoService.State() == lx.symbol.iUNDO_ACTIVE:

        # Cast the unknown object to an lx.object.Item object
        item = lx.object.Item(item)
        # If the item is a camera, we call our command and pass the item's ident name on to it
        if item.TestType(modo.constants.CAMERA_TYPE):
            lx.eval('tdsdkExamples.colorCameraRandom %s' % item.Ident())

listener = MyItemListenerClass()

Flying Polygon Example

Mesh editing

Next, let us create a new polygonal mesh

# Add a new mesh object to the scene and grab the geometry object
mesh = scene.addMesh('PolyBird')
geo = mesh.geometry

# Add four vertices in anti-clockwise fashion, so that the resulting polygon normal will face up
v1 = geo.vertices.new((0,0,-0.5))
v2 = geo.vertices.new((0,0, 0.5))
v3 = geo.vertices.new((1,0, 0.5))
v4 = geo.vertices.new((1,0,-0.5))

# Create the polygon from our vertices
geo.polygons.new((v1, v2, v3, v4))

# Appending a second polyon
v5 = geo.vertices.new((-1,0, 0.5))
v6 = geo.vertices.new((-1,0,-0.5))

# Passing the indices directly this time
geo.polygons.new((1, 0, 5, 4))

# Changing the y position of vertices 2 to 5
for vertex in geo.vertices[[2,3,4,5]]:
    vertex += (0, -0.2, 0)

# To see the changes made to the geometry, we need to update the mesh

# This creates a new morph map and scales all points by two
mo = geo.vmaps.addMorphMap('Scaled')

for i in range(len(geo.vertices)):
    pos = geo.vertices[i].position
    mo.setAbsolutePosition(i, [value*2.0 for value in pos])

# Select the mesh

Assigning a material to individual polygons

# continuing from the previous example ...

# We will assign the material per polygon, so we need to use a mask item
mask = scene.addItem('mask', name='Blue Mask')

# Create a new material with a blue diffuse color
mat = scene.addMaterial(name='Blue')
mat.channel('diffCol').set((0.0, 0.0, 1.0))

# Set all materials to double sided
for material in scene.items(mat.type):

# Tell the mask to use our 'Blue' material

# Parent the material to the mask
mat.setParent(mask, index=1)

# This places the mask item below the base shader in the hierarchy of the Shader Tree
mask.setParent(scene.renderItem, index=1)

# Assign the matertial only to the first polygon
geo.polygons[0].materialTag = 'Blue'

# Update the mesh to see the result


# continuing from the previous example ...

import lx
import math

mesh = scene.item('PolyBird')
geo = mesh.geometry

# Add a normalizing folder to the deform tree
normalizeFolder = scene.addItem('deformGroup')

# Connect it to the mesh
normalizeFolder >> mesh.itemGraph('deformers')

# Add a root joint and two wing joints
joint_names = ('root_joint', 'l_joint', 'r_joint')
joint_results = [normalizeFolder.createJointLocator(name) for name in joint_names]
joints = [j[0] for j in joint_results]
weights = [j[2] for j in joint_results]

root_joint, l_joint, r_joint = joints

# Parent the wing joints to the root

# Set the skin weights for the wings for the respective vertices to 100%
with geo as g:
    for weightMap, indices in zip(weights, ((0, 1), (4, 5), (2, 3))):
        for i in indices:
            weightMap[i] = 1.0

# Add an actor that contains the joints
actor = scene.addActor(name='PolyBird Actor', items=joints)

# Add an action
action = actor.addAction(name='Flapping')
action.active = True

lx.eval('select.item %s set' % actor.id)
lx.eval('layer.active %s type:actr' % action.id)

# The default time unit in modo is seconds. This creates a shortcut to a function that converts a frame number to seconds.
frame = lx.service.Value().FrameToTime

# Insert rotation keyframes and set the post behaviour of the animation envelope to repeat itself
l_joint.rotation.z.set(value=math.radians(-30), time=frame( 0), key=True, action='Flapping')
l_joint.rotation.z.set(value=math.radians( 30), time=frame( 5), key=True, action='Flapping')
l_joint.rotation.z.set(value=math.radians(-30), time=frame(10), key=True, action='Flapping')
l_joint.rotation.z.envelope.postBehaviour = lx.symbol.iENV_REPEAT

# Instead of setting the same keyframes for the other wing, let's just link the channels.
# However, the value must be negated for symmetry so we use a basic math channel modifier for that.
mathModifier = scene.addItem('cmMathBasic', 'InvertWing')

# Change the channel 'operation' to  multiply. It will multiply the values of
# the channels 'input1' with 'input2' and the output can be read from channel 'output.

# We want to connect from the left joint to the modifier to the right joint
l_joint.rotation.z >> mathModifier.channel('input1')
mathModifier.channel('output') >> r_joint.rotation.z

# Set the value to multiply with to -1.0

# Adding some up and down motion for the y position
root_joint.position.y.set(value=0,    time=frame( 2), key=True, action='Flapping')
root_joint.position.y.set(value=0.15, time=frame( 7), key=True, action='Flapping')
root_joint.position.y.set(value=0,    time=frame(12), key=True, action='Flapping')
root_joint.position.y.envelope.postBehaviour = lx.symbol.iENV_REPEAT

# Be sure to enable deformers for your viewport properties under 'Drawing and Control' (bottom)

PySide Qt

Render Button

# This example creates a custom view using PySide Qt and adds a simple render button to it.
# Either run this code from the script editor or save it to a file inside a lxserv server in the user scripts- or
# your kit's directory.
# Then set any viewport to use the "My Render Button" custom view server

import lx
import lxifc
import PySide
from PySide.QtGui import *

def onClicked():

# To create our custom view, we subclass from lxifc.CustomView
class MyRenderButton(lxifc.CustomView):

    def customview_Init(self, pane):

        if pane is None:
            return False

        custPane = lx.object.CustomPane(pane)

        if not custPane.test():
            return False

        # get the parent object
        parent = custPane.GetParent()

        # convert to PySide QWidget
        widget = lx.getQWidget(parent)

        # Check that it succeeds
        if widget is not None:

            # Here we create a new layout and add a button to it
            layout = PySide.QtGui.QVBoxLayout()
            renderButton = QPushButton("RENDER!")

            # Increasing the font size for the button
            f = renderButton.font()

            # This connects the "clicked" signal of the button to the onClicked function above

            # Adds the button to our layout and adds the layout to our parent widget
            layout.setContentsMargins(2, 2, 2, 2)
            return True

        return False

# Finally, register the new custom view server to Modo
lx.bless(MyRenderButton, "My Render Button")

Web Display

# This example create a new custom view server displaying a website using QtWebKit

import PySide
from PySide.QtWebKit import *

# Subclassing from lxifc.CustomView
class FoundryCommunityServer(lxifc.CustomView):

    def customview_Init(self, pane):

        if pane is None:
            return False

        custPane = lx.object.CustomPane(pane)

        if not custPane.test():
            return False

        # get the parent object
        parent = custPane.GetParent()

        # convert to PySide QWidget
        p = lx.getQWidget(parent)

        # Check that it suceeds
        if p is not None:
            layout = PySide.QtGui.QVBoxLayout()

            # Creating a QWebView widget
            web = QWebView()
            return True

        return False
if( not lx.service.Platform().IsHeadless() ):
    lx.bless(FoundryCommunityServer, "The Foundry Community")

Accessing Skin Weights

# This example demonstrates how to read or write skin weight values through the TD SDK
# Place this file into an lxserv folder
# Example command usage:
#   example.printJointWeights locator:"Skeleton_Joint (4)" mesh:MyCube

import lx
import lxifc
import lxu.command
import modo

def printJointWeights(jointLocatorName, meshName):

    # Get skinned mesh by name
    mesh = modo.Mesh(meshName)

    # Get the locator by name
    locator = modo.Locator(jointLocatorName)

    # This returns the deformer that this locator connects to, this should return the influence deformer item
    influence = locator.itemGraph('deformers').forward(0)

    # This line is an awkward way to return the name of the weightmap
    mapname = lx.object.WeightMapDeformerItem(influence).GetMapName(modo.Scene().chanRead)

    # Now look up the weightmap by it's name
    weightMaps = mesh.geometry.vmaps.weightMaps
    filtered_result = [map for map in weightMaps if map.name == mapname]
    for weightMap in filtered_result[:1]:

        for vertex_index, weight in enumerate(weightMap):
            print vertex_index, weight

            # As an example, set the weight value to it's inverse
            weightMap[vertex_index] = 1.0-weight[0]


class CmdPrintJointWeights(lxu.command.BasicCommand):
    def __init__(self):

        # Add two string arguments
        self.dyna_Add('locator', lx.symbol.sTYPE_STRING)
        self.dyna_Add('mesh', lx.symbol.sTYPE_STRING)

    def cmd_Flags(self):
        return lx.symbol.fCMD_MODEL | lx.symbol.fCMD_UNDO

    def basic_Enable(self, msg):
        return True

    def basic_Execute(self, msg, flags):
        if self.dyna_IsSet(0) and self.dyna_IsSet(1):

            locatorName = self.dyna_String(0, None)
            meshName = self.dyna_String(1, None)

            printJointWeights(locatorName, meshName)

    def cmd_Query(self, index, vaQuery):

lx.bless(CmdPrintJointWeights, "example.printJointWeights")

Matrices and rotations

Have a locator aim at a given position

# Example method for turning a direction vector into an euler rotation
# There is a similar example using the API for this by Lukasz Pazera: https://gist.github.com/lukpazera/5994547
# This function really does the same, using the mathutils of the TD SDK

import modo

def lookAt( directionVector=modo.Vector3(1,0,0), upVector=modo.Vector3(0,1,0), axes='xyz', rotOrder='zxy', asDegrees=True):
    '''Retuns an euler rotation with x aligned to the given direction vector and y to the optional up vector.

    :param Vector3    directionVector: Direction to aim at
    :param Vector3    upVector:        Direction the twist of the rotation should aim at (what is considered up)
    :param basestring rotOrder:        Rotation order to use for the resulting euler rotation.
    :param bool       asDegrees:       Outputs the rotation as degrees if True, as radians otherwise.

    # Get up and normal vectors perpendicular to the direction vector by crossing
    normalVector = directionVector.cross(upVector).normal() 
    upVector = normalVector.cross( directionVector ).normal()

    # Create a transformation matrix from the three vectors
    matrix = modo.Matrix4( (directionVector.values, upVector.values, normalVector.values) )

    # Return as euler rotation values
    return matrix.asEuler(degrees=asDegrees, order=rotOrder)

# Example usage, creating locators:
scene = modo.Scene()

directionVector = modo.Vector3( 0.123, 0.456, 0.789 )
directionLocator = scene.addItem('locator', name='Direction')

upVector = modo.Vector3(0.34, -0.84, -0.16)
upVectorLocator = scene.addItem('locator', name='UpVector')

rotationLocator = scene.addItem('locator', name='Result')
eulerRotation = lookAt(directionVector, upVector, axes='yxz')
rotationLocator.rotation.set( eulerRotation, degrees=True)