Designer Plugins
Plugins for Disguise Designer software.
Plugins for Disguise Designer software.
This is a set of API endpoints allowing Python 2.7 scripts to be executed in a Designer session remotely. This new functionality allows programmers to control and automate Designer using Python and the d3
module, opening up a wide range of possibilities for show control, automation and integration with other systems.
The API consists of two endpoints:
execute
- This endpoint allows users to execute a block of Python code in the current Designer session. No state is preserved between calls to this endpoint.registermodule
- This endpoint allows users to register a Python module that can be used in subsequent calls to the execute
endpoint by referencing the module name.The execute endpoint is the primary method for running code in Designer however when more complex scripts are required, the registermodule
endpoint can be used to preload modules into the environment that can be referenced in the execute
endpoint.
This API has not been optimised for performance-related tasks such as executing on every frame or providing telemetry metrics, users are encouraged to use other tools such as the Live Update API for these tasks.
For the avoidance of doubt, this endpoint MUST NOT be polled, and is for functional changes such as object creation.
The above diagram shows a high level overview of the Python execution architecture.
When a request is made to the execute
endpoint, the stringified Python 2.7 code is unpacked into a internal runner
function. The runner
function is then executed with the Designer embedded python in a specific module namespace if a moduleName
is provided, or in a default namespace if no module name is provided. More information on these namespaces can be found in the following sections.
The return value of this runner
function is then converted to JSON and returned in the response body along with any logging information.
The wrapping of the user code into a runner
function means that all code is executed in a function scope, this means that global variables cannot be created in the execute
endpoint. This also allows for some additional guardrails to be put in place to prevent stability issues such as:
However some statements are not valid in the context of a function body, such as from [package] import *
and users should avoid using these statements in their code.
The registermodule
endpoint allows users to register a Python module that can be used in subsequent calls to the execute
endpoint. This is useful for more complex scripts that require multiple functions or classes to be defined but also acts as a namespace to contain plugins from each other.
These registered modules are managed by Designer and when used in the execute
endpoint are used as the execution namespace, meaning all variables, methods and classes defined in the module are immediately accessible without needing an import statement.
When no moduleName
is provided in the execute
endpoint, the code is executed in the default plugin namespace. This namespace can’t be added to but has some additional utility functions to help with common tasks. More information on these utility functions can be found on the Execution Utility Functions page.
The request body is structured as follows:
{
"moduleName": "string", # Optional
"script": "string"
}
moduleName - The registered module the script will be executed in. For one-off scripts, this field can be omitted or an empty string.
script - The Python code to be executed. This needs to be valid Python 2.7 code, using the correct indentation and stringified.
{
"status": {
"code": 0,
"message": "string",
"details": [
{
"type_url": "string",
"value": "string"
}
]
},
"d3Log": "string",
"pythonLog": "string",
"returnValue": "string"
}
Note that calling this endpoint too frequently or during a show is not a supported workflow - this is intended for show programming tasks, not during production.
An example of using the execute
endpoint is shown below using Python:
import requests
response = requests.post(
url="http://localhost/api/session/python/execute",
json={
"script": "print('Hello Disguise Designer!')"
}
)
The response to this statement would look as follows:
{
"status": {
"code": 0,
"message": "",
"details": []
},
"d3Log": "Hello Disguise Designer!\nPython script took 37.352000000000004ms\n",
"pythonLog": "Hello Disguise Designer!\n",
"returnValue": "null"
}
{
"moduleName": "string",
"contents": "string",
}
moduleName - This is the name the module will be referred to in Python. This string will need to be parsed in the execute
request in the moduleName
field in order to load the module. See the Module naming section for more information on valid module names.
contents - This is the contents of the Python module in a single string. Classes, functions and variables can be defined here to be used in the execute
.
{
"status": {
"code": 0,
"message": "string",
"details": [
{
"type_url": "string",
"value": "string"
}
]
}
}
An example of registering a Python module using the registermodule
endpoint is shown below:
import requests
response = requests.post(
url="http://localhost/api/session/python/registermodule",
json={
"moduleName": "screen_utils",
"contents": """
def createLEDScreen():
# Create a new LED screen resource
screen = resourceManager.loadOrCreate(
'objects/ledscreen/ledscreen_1.apx',
LedScreen
)
# Set the physical size in meters
screen.scale = Vec(1, 2, 0)
# Set the resolution in pixels
screen.resolution = Vec(1920, 1080)
# Center the screen at origin
screen.offset = Vec(0, 0, 0)
return screen
"""
}
)
Giving the following response:
{
"status": {
"code": 0,
"message": "",
"details": []
}
}
If errors are experienced whilst compiling the given module, errors will be given in the message
field.
To use the above module in the execute
, the module name must be passed into the moduleName
field, the request would look as follows:
import requests
response = requests.post(
url="http://localhost/api/session/python/execute",
json={
"moduleName": "screen_utils",
"script": "createLEDScreen()"
}
)
The d3
package an interface to Designer object types, resources and session state. A full breakdown of the different classes and functions can be found on the Python Reference page.
All of the d3
package is always imported into the execute
environment, therefore a d3
import statement is not required.
Some elements of the d3
package are not available through the plugin system, such as GUI elements, to prevent stability issues. For more information, please read the Python Restrictions page.
It is possible to return values from the execute
endpoint. As mentioned in the Architecture section, all execution code is run within the runner
function, by adding a return
statement the script will return values from the runner
function.
The returned values will be serialised as JSON using the Python json
package.
An example of returning a value is shown below
import requests
response = requests.post(
url="http://localhost/api/session/python/execute",
json={
"script": "return {'a': [], 'b':2}"
}
)
print(response.json())
Gives the response:
{
"status": {
"code": 0,
"message": "",
"details": []
},
"d3Log": "Python script took 3.1447ms\n",
"pythonLog": "",
"returnValue": "{\"a\": [], \"b\": 2}"
}
As always logging is important but it is even more crucial when running commands remotely. To help with this, two logging fields are provided in the response.
d3Log
- The D3 log field captures the console log for the time the Python script is executing, recording any output generated by running a Python command, including the time taken to execute the Python command. As this captures the Designer console output, it is possible other threads may write to this output during this period causing additional irrelevant output.
pythonLog
- This output field is the pure Python output, recording any print statements or warnings occurring during the execution of the script. This output will also appear in the d3Log
field, however, it may be mixed with other application output.
The execution of Python scripts through the execute
occurs on the main application thread and causing the application to pause whilst the script executes. To avoid cases where scripts run longer than expected, a timeout interrupt has been implemented. The length of this timeout is dictated by the experimental option pythonApiExecutionTimeout
which can be set through the d3Manager window. When a script exceeds this value, a KeyboardInterrupt
is executed, and the Python execution stops and returns a TimeoutError
in the response.
Scripts sent to the execute
endpoint can be written out using the experimental pythonApiOutputRequestsToProject
flag through d3Manager. When this is set to True
all scripts sent to the execute
endpoint will be written to the project internal\\PythonApiHistory
directory. This can be useful for debugging issues as well as keeping a record of the scripts executed in a project. However, this should be used with caution as executing many requests will cause the project size to grow quickly.
Undo is supported when executing scripts through the execute
endpoint.
As plugins are imported as Python modules, plugin names need to follow the same naming conventions:
Registering a Python plugin adds the defined module to the internal Designer Python system, meaning these modules can be used inside other Python plugin modules. Modules, when registered, are prepended with user_module_
to protect core modules. To use a registered module in another module, this would need to be added to the module name.
Here’s an example of creating another plugin module:
import requests
response = requests.post(
url="http://localhost/api/session/python/registermodule",
json={
"moduleName": "serialise_helpers",
"contents": """
def serialiseLED(ledScreen):
return {'name':ledScreen.path , 'uid': ledScreen.uid}
"""
}
)
This could then be used when executing in another module namespace by importing the module:
import requests
response = requests.post(
url="http://localhost/api/session/python/execute",
json={
"moduleName": "screen_utils",
"script": """
from user_module_serialise_helpers import serialiseLED
return serialiseLED(createLEDScreen())
"""
}
)
Another option for loading modules is to have the modules registered when the Designer application is started. This can be done by adding the modules to the project {project_root}/plugins
directory. These will be loaded using the following rules:
plugins
directory with a __init__.py
will be loaded as a plugin using the directory name as the plugin name.plugins
directory will be loaded using the file name as the plugin name.