Toon Boom Harmony -- Python Interface

Introduction

Toon Boom Harmony's Python Document Object Model provides a Python respresentation of Harmony and its loaded project. This allows for custom tools and automation tasks both within Harmony's GUI and external to Harmony from a Python Interpreter.

Harmony's library is provided by a Python module that is imported either within Harmony's own Python interpreter or external to Harmony in a compatible interpreter.

Python

The Harmony Python module is compatible with Python 3.9 or above. Python 3.9 is required when using Python from within Harmony's GUI internally – other minor versions of Python 3 are incompatible when using the internal Python Console.

The Harmony Python module is compatible with other minor versions of Python 3 when loaded from an external Python interpreter.

The Python Console will only be available in Harmony if Harmony is launched in an environment that provides the Python libraries.
The Python libraries path can be specified by using the preference PYTHON_LIB_PATH or by making the python executable available from the PATH environment variable.

Windows

Ensure that Python 3.9 is installed on the system and available on the user's PATH or PYTHON_LIB_PATH in the system's environment.

macOS

On most modern macOSs, Python3 is available in the system already. If it is not available or cannot be found, please install Python 3.9+ and ensure that the Python 3.9 frameworks are also installed. For custom installation and environments, adding the path of Python's libraries to the DYLD_LIBRARY_PATH will make the console available within Harmony.

Linux

Ensure that Python 3.9's path can be found in the PATH or PYTHON_LIB_PATH environment variable of the system. Also, ensure that the path to Python3's dynamic libraries are available in the LD_LIBRARY_PATH.


Getting Started

Harmony's Python Console View

An integrated Python3 console is available in Harmony as a view available under Windows -> Python Script Console

This console behaves as any normal Python intrepreter and also provides access to the Harmony Python Module. The console and the instance of Python is persistent for runtime of the current Harmony session.
Once this module has been imported into the internal console, the application's current session and project will be available to the interpreter as a document object model representation.
The Python Script Console will also have access to the modules normally available to the loaded version of Python – these modules can be imported as needed.
Extending the available modules in Python is similar to any normal Python installation and can be done by using virtual environments, appending to the PATH environment or appending to the PYTHON_PATH environment.

From Harmony's Python Console View

from ToonBoom import harmony
current_session = harmony.session() #Fetch the currently active session of Harmony
project = current_session.project #The project that is already loaded.
print( "Current Project: %s"%(project.project_path) )
# Expected Result : "Current Project: Path/To/Project"


Harmony's python-packages also includes a copy of PySide6 that can be used to integrate with and extend Harmony's GUI. This allows Python scripts to create entirely new GUI elements and directly modify existing ones in Harmony's user interface.
If an already-existing PySide installation is available to Python elsewhere, ensure that the PySide library packaged with Harmony is loaded when interacting with the application.


External Python Interpreter

In an external Python Interpreter, the Python interpreter needs to import the harmony module library (and dependency libraries) from the installed path of Harmony.

On Windows: The Harmony python-package is found in the directory: $HARMONY_INSTALL/win64/bin/python-packages

On macOS The Harmony python-package is found in the directory: $HARMONY_INSTALL/Contents/tba/macosx/lib/python-packages

On Linux The Harmony python-package is found in the directory: $HARMONY_INSTALL/lnx86_64/lib/python-packages

Using an external Python interpreter allows for batch-processing of Harmony scenes without a GUI – directly in an external Python script.

From an External Python Consolde

import sys
sys.path.append( "path/to/harmony/python-packages" ) #Extend the environment's path, in order to find the installed Harmony Python module
from ToonBoom import harmony
harmony.open_project( "Path/To/Project/file.xstage" ) #Open an offline Harmony project
current_session = harmony.session() #Fetch the currently active session of Harmony
project = current_session.project #The project that is already loaded.
print( "Current Project: %s"%(project.project_path) )
# Expected Result : "Current Project: Path/To/Project"


Note
Loading a project in the external Python interpreter will behave in the same way as loading the project within the application. Changes that are made to the scene are not applied automatically and will need to be saved manually with the appropriate command in Python.

See HarmonyProject::save_all() for further information.



Document Object Model

As per a standard Document Object Model, all interactions in Harmony's Python Library are done directly to the object that represents it within the document-object-model. An object can be requested from the heirarchy in the object-model and modified directly with that object's properties. All interactions and changes are applied relative to the object upon which the change was applied.

All initial access to Harmony is made available via the loaded session in the Harmony module; subsequent objects can be accessed from the hierarchy of the corresponding objects in the scene.

from ToonBoom import harmony
current_session = harmony.session() #The actively loaded application session
project = current_session.project #The current project
scn = project.scene #The primary scene in the project
top = scn.top #The "top" group in the scene
nodes = top.nodes #The list of nodes in the top group
for node in nodes: #Loop on each node in the top group
print( "Node: %s"%node.path )
# Expected Result : The path of each node in the top group of the scene.

The allows for a true hierarchical representation of the project within Harmony.

See HarmonyModule for further information and access to the current Harmony Session. See HarmonyModule for further information.



Multi-Threading

Both Python and Harmony rely on a single primary-thread for much of its processing, but can also use secondary threads to help process data when it is safe to do so. These threads can be used for extra processing and other services but the actions of the threads must be synchronized with Harmony's primary at given moments.

Objects in the DOM that represent content in the scene can only be processed on the main thread, or with the tread locked – attempting to manipulate or access these objects without a valid lock will result in an error. The application object has two approaches to handle these locks. See OMH::Harmony::run_on_main() and OMH::Harmony::thread_lock() for more information.

Multi-Processing

Multiprocessing in Python on Windows requires that Python relaunches the application with the same arguments provided as the initial Python interpreter. This is can lead to issues when using multi-processing within Harmony's Python Console. The external processes won't have access to the Python DOM objects but can still be used for other external procesing. To ensure proper behaviour when using Multi-Processing, be sure to set the desired Python application's path, and the sys.argv to reflect the multiprocessing operation.

In Harmony's Python Console

from multiprocessing import Pool
import multiprocessing
import os
path_to_functions_file = r"c:\pathToFunctions"
import sys
sys.path.append( path_to_functions_file )
import functions
if __name__ == '__main__':
multiprocessing.set_executable( r"C:\Python39\python.exe" ) #This will be launched instead of Harmony
sys.argv = [ os.path.join( path_to_functions_file, "functions.py" ) ] #This is where the commands are found.
with Pool(5) as p:
print( p.map( functions.func, [1, 2, 3] ) )


File: functions.py

from multiprocessing import Pool
import multiprocessing
def func(x):
return x*x