Quick Start   (index)

Scripting in KeyShot unleashes the power of customization and control in the form of Python 3.4.

There are three ways of using scripting:
  1. Live scripting console
  2. Scripts in the script library
  3. Running KeyShot on the command-line: keyshot.exe (optional file to open) -script <python script file> (optional script arguments..)

The first two are accessed via the scripting console dialog, which is opened via the menu "Window → Scripting Console" or by clicking the scripting console button in the ribbon. In the dialog there are two tabs; "Console" and "Scripts".

Console

The console is a live python interpreter in which you can input python code to be evaluated

Script Library

The script library contains pre-made script bundled with KeyShot that are either directly useful or provides examples on how to do certain things. It is recommended to take a look in those files to learn more.

General help

In order to get help you can consult the online documentation or you can use the help function.

Additinally, whenever lines appear like the following:

>>> print("Hello, World!") # I am a comment
Hello, World!

Then it symbolizes interaction with the console where ">>>" means python code as input, and otherwise it is output written to the console. Notice the comment after the print. Comments are always ignored by the python interpreter.

There are two main modules for KeyShot scripting; lux and luxmath. To print help about the lux module, do the following:

>>> help(lux)
(several lines omitted..)

It can also be used on functions to understand what they do:

>>> help(lux.pause)
Help on built-in function pause in module lux:
pause(...)
    Pauses renderer.

In the following a series of tips will try to illustrate different aspects of scripting and how to accomplish the given tasks.

Tip #0: Using the console

This tip is an extremely short and simple crash course to using Python scripting in the console.

First a few examples:

>>> 1 + 2 * 3 / 4 # Use as a calculator.
2.5

>>> import math # Import module.
>>> math.sqrt(25) # And use members.
5.0
>>> math.cos(math.pi)
-1.0

>>> a = 1 # Define variables.
>>> b = 41
>>> a + b # And reference them.
42

>>> sum = 0
>>> for i in range(1, 100 + 1): # Loop with i in [1, 100].
>>>     sum += i
>>> sum
5050

>>> for elm in ["one", "two", "three"]: # Loop list elements.
        print(elm)
one
two
three

>>> def test(): # Define function.
        print("Hello, World!")
>>> test() # And invoke it.
Hello, World!

Note that you can input multi-line code in the console by using alt + enter, which is useful for functions, loops etc. Also pay attention to the indentation of the code because if code has wrong indentation levels then it results in syntax errors. In the console the tab key will provide the correct tab "size" (in spaces), and you can hit continually to advance or shift+tab to decrease indentation level.

It is recommended to just play around with the console if not so familiar with Python.

Another important tool is knowing how to use dir, which returns a list of elements and attributes of the input. A useful use case is finding out what a Python module contains:

>>> dir(luxmath)
['Matrix', 'Vector', '__doc__', '__loader__', '__name__', '__package__', '__spec__']

We find that it has Matrix and Vector, our two math constructs for calculations with matrices and vectors. This means we can reference the Matrix class with luxmath.Matrix, for instance. Most often you can ignore everything with "__" in front and/or after it because it's a special construct or private value.

This tip ends with a few invocations of functions from module lux:

>>> lux.getKeyShotVersion()
(6, 0, 154, '64 bit')
>>> lux.getOS()
'Mac OS X Yosemite 64 bit'
>>> lux.getCamera()
'Hero'
>>> lux.pause() # Pause renderer.
True
Tip #1: Import file

Importing models with scripting is easy:

>>> lux.importFile("/path/to/your/file.obj")

In this case we imported an OBJ file.

By default no dialog will be shown with importing options, but it can be done using the showOpts option:

>>> lux.importFile("/path/to/your/file.obj", showOpts = True)

For more advanced importing, a Python dictionary can be supplied using the opts option. An example could be to disable snap-to-ground, and not adjusting environment and camera look-at on import:

>>> opts = lux.getImportOptions()
>>> opts["snap_to_ground"] = False
>>> opts["adjust_environment"] = False
>>> opts["adjust_camera_look_at"] = False
>>> lux.importFile("/path/to/your/file.obj", opts = opts)

When updating geometry you will want to set the following options before importing:

>>> opts["update_mode"] = True
>>> opts["merge"] = True
>>> opts["new_import"] = False
Tip #2: Render image

Assuming you have already loaded a BIP file, or imported some geometry, and you want to render to an image with scripting then do the following:

>>> lux.renderImage("/path/to/save/image.png", width = 1200, height = 1000)

In this case we render the image to "/path/to/save/image.png" as a PNG file with dimensions 1200x1000 pixels. Notice that the format of the image is determined from the file extension.

As with importing, advanced options can be passed using the opts option. An example could be to setup for rendering with max time of 10 seconds:

>>> opts = lux.getRenderOptions()
>>> opts.setMaxTimeRendering(10)
>>> lux.renderImage("/path/to/save/image.png", width = 1200, height = 1000, opts = opts)

Another example could be to render using advanced rendering method with 64 samples, 8 threads, and 64 ray bounces:

>>> opts = lux.getRenderOptions()
>>> opts.setAdvancedRendering(64)
>>> opts.setThreads(8)
>>> opts.setRayBounces(64)
>>> lux.renderImage("/path/to/save/image.png", width = 1200, height = 1000, opts = opts)
Tip #3: Cancelling scripts

In general you can't cancel a script but certain operations can, such as importing a file or rendering an image, animator or VR. Cancelling in those situations means to cancel the dialog associated, like the rendering output window or import progress dialog.

The cancellable operations are:

Simply check the return value to see if it was False and therefore cancelled, or failed.

Especially if you're using a loop-structure you can do the following:

>>> frames = 10
>>> for frame in range(1, frames + 1):
        lux.setAnimationFrame(frame)
        if not lux.renderImage("/path/to/output.{}.png".format(frame)):
            break
Tip #4: Camera manipulation

Working with cameras in scripting is just like using the Camera tab in Project → Camera: You can create a camera, manipulate it to suit your needs, and save it.

>>> lux.newCamera("New")
True
>>> lux.setCameraLookAt(pt = (1, 1, 1)) # Example manipulation.
>>> lux.saveCamera()

Inspect the cameras available:

>>> lux.getCameras()
['New', 'last_active', 'default']

Choose another camera:

>>> lux.setCamera("default")

Remove the camera you just created:

>>> lux.removeCamera("New")
True
>>> lux.getCameras()
['last_active', 'default']

Note that if you remove the active camera then it will choose the "Free Camera".

You can also set a standard view:

>>> lux.setStandardView(lux.VIEW_TOP)

The following 7 standard views are available:

Setting a standard view manipulates the active camera so you could create a camera for each standard view, but remember to save them.

Tip #5: Custom input dialog

In scripts it is especially preferable to show a single dialog when multiple inputs are needed. For this you can use lux.getInputDialog(). The scripts provided by Luxion will use this.

As an example, if we wanted to write a video encoding script the following dialog might suffice:

>>> values = [("folder", lux.DIALOG_FOLDER, "Folder with frames:", None), \
              ("fmt", lux.DIALOG_TEXT, "Frame file format:", "frame.%d.jpg"), \
              ("start", lux.DIALOG_INTEGER, "Start frame:", 1, (1, 4096)), \
              ("end", lux.DIALOG_INTEGER, "End frame:", 10, (1, 4096)), \
              ("fps", lux.DIALOG_INTEGER, "FPS:", 10, (1, 1024)), \
              ("name", lux.DIALOG_TEXT, "Video name:", "video.mp4")]
>>> opts = lux.getInputDialog(title = "Encode Video", \ # Shows the dialog.
                              desc = "Put a description here.", \
                              values = values)
>>> opts
{'end': 10, 'fps': 10, 'fmt': 'frame.%d.jpg', 'name': 'video.mp4', 'start': 1, 'folder': ''}

If you click "OK" without changing any values then you will get the Python dictionary above. Each key is associated with the value from the dialog.

Note that the "\" characters above instructs a continuation of the line so it is not split into several lines but understood as one line.

For scripts that are run often it is convenient to have KeyShot remember the last values for when the dialog is displayed again, and that is accomplished by using a unique value for the id option:

>>> opts = lux.getInputDialog(title = "Encode Video", \
                              desc = "Put a description here.", \
                              values = values, \
                              id = "something_unique_goes_here")

If the unique value is already used by some other script then you will get undesired results. However, keep in mind that your script must use the same unique value each time to retrieve your values.

Tip #6: Accessing scene nodes

The elements of the scene can be accessed via lux.getSceneTree() with each scene tree node being of the type lux.SceneNode. It is useful because for each node you can hide/show, lock/unlock, select/deselect, change material, apply transformations, duplicate, move and more. Try help(lux.SceneNode) for more information.

Let's say you wanted to hide all nodes having "Cord" as part of their name:

>>> root = lux.getSceneTree()
>>> for node in root.find(name = "Cord"):
        node.hide()

Another example could be that you wanted to access all groups called "Padding" and selecting all child nodes (in the scene with outline) whose name is "Ear Pad":

>>> for node in root.find(name = "Padding", types = lux.NODE_TYPE_GROUP):
        for ch in node.getChildren():
            if ch.getName() == "Ear Pad":
                ch.select()

If you have hidden some parts and want to simply show everything again:

>>> root.show()

Note that the root node is itself a lux.SceneNode so the same functions can be invoked on it as with child nodes.

It is also possible to change materials. To simulate ambient occlusion you could do the following:

>>> lux.setEnvironmentImage("All White.hdr")
>>> for node in root.find(""):
        node.setMaterial("Matte White")

A trick is to use lux.SceneNode.find("") to simply find everything.

Nodes can be moved to other groups, just like in the scene tree with drag-and-drop. In the following we find the group "Headphone #1" and move all nodes to it that are using a material with "Padding" in it.

>>> grp = root.find(name = "Headphone #1")[0] # Take first node of set.
>>> for node in root.find(mat = "Padding"):
        node.moveToGroup(grp)
True
Tip #7: Transforming scene nodes

Nodes can be translated, scaled, rotated etc. via scripting. It is possible by using 4x4 transformation matrices of type luxmath.Matrix. There are two ways to transform: relative or absolute. A relative transform means to apply the transform locally, i.e. it is multiplied last in the chain of existing transforms. That means that if you relatively translate by (1,0,0) then it will simply move the node one by the x-axis according to its position. In contrast, absolute transforms are not chained in the same fashion and absolutely translating by (1,0,0) will always position the node at (1,0,0).

When doing transforms it is important that an identity matrix (1s on the diagonal) is used as the base matrix. It is obtained in the following way:

>>> M = luxmath.Matrix().makeIdentity()
>>> print(M.dump())
| 1.00  0.00  0.00  0.00 |
| 0.00  1.00  0.00  0.00 |
| 0.00  0.00  1.00  0.00 |
| 0.00  0.00  0.00  1.00 |

To translate a node by (5,0,1):

>>> M = luxmath.Matrix().makeIdentity().translate(luxmath.Vector((5, 0, 3)))
>>> node.applyTransform(M)
>>> print(M.dump()) # Let's have a look at the matrix.
| 1.00  0.00  0.00  0.00 |
| 0.00  1.00  0.00  0.00 |
| 0.00  0.00  1.00  0.00 |
| 5.00  0.00  3.00  1.00 |

If instead an absolute position adjustment is intended, then do the following:

>>> M = luxmath.Matrix().makeIdentity().translate(luxmath.Vector((5, 0, 3)))
>>> node.applyTransform(M, absolute = True)

Note that only object nodes can be have absolute transforms applied!

For further information about what transforms are possible have a look at the documentation for luxmath.Matrix.

Copyright by Luxion