Golaem Layout Python API

Golaem Layout is shipped with a Python API module. It is strongly advised to use the API provided by this file to handle Simulation Cache Layout File (.gscl)

Preparing your Environment Variables

The layout API will be reachable if the PYTHONPATH environment variable contains the [Golaem installation directory]/scripts folder

In addition, the PATH environment library must include the [Golaem installation directory]/bin folder : the layout python API requires the glmCrowdIO library to handle licenses

Including Golaem Layout API file in a project

The API needs an app running to be able to manipulate graphs in background, you can insure that by doing:

# Insure that we have an app before calling layout
from glm.Qtpy.Qt import QtWidgets
try:
   app = QtWidgets.QApplication([])
except:
   app = None

Then the Golaem Layout API can be added to a project, by importing the layout and devkit modules:

from glm.layout import *
from glm.devkit import *

Finally, a call need to be issued to inform that we are using the layout as a standalone API, before issuing any other layout API call. The call requires the path to the golaem_demo.lic containing directory. This file is used to create a PLE Licenses, thus the licDir path can be ignored if the user is sure to have a valid layout license, either from Maya Layout or Unreal.

currentDir = os.path.dirname(os.path.realpath(__file__))
licDir = os.path.join(currentDir, os.pardir, os.pardir, os.pardir, "plug-ins").replace("\\", "/")
setGolaemStandaloneProduct(licDir)

Using the API to load, modify and save layout files

Load an existing Layout File

It is done by loading a layoutFileHandle from a .gscl file :

layoutFileHandle = openLayoutFile(filePath)

The command takes a string filePath, and returns None on error, or a layoutFileHandle used to modify and save it.

Accessing/Modifying Layout

The cache data can be accessed and modified the following way:

Layout parameters

Layout parameters are parameters generic to the whole file, such as the next available node Id, the root Id of the main graph, the file version, the current file path. 

listLayoutParameters(fileHandle , parameterNameList) # parameterNameList must be an empty list() when calling, which will be fed by the function. Returns False on error
getLayoutParameter(fileHandle , parameterName) # returns None on error or the parameter value
setLayoutParameter(fileHandle , parameterName, parameterValue) # returns False on error

Root Id

Root Id  of the main graph, or a group graph if an integer groupNodeId is mentionned, can be get and set via those functions. 

getRootId(fileHandle , groupNodeId=None) # returns -1 on error or the rootId
setRootId(fileHandle , rootId, groupNodeId=None) # returns False on error

Getting Node from ID

getNodeById(fileHandle , nodeID) # returns None on error, or the node instance

Nodes Generic Properties

Some node properties can be set/get by specific functions. All these functions use a node instance, as returned by getNodeById(), or by a creation function createOperator() or createSelector(). On error, getter functions return None while setter functions return False.

Operator and selector shared accessors :

getNodeID(nodeInst) # returns the Node ID
getNodeActive(nodeInst) # returns 1 if enabled, 0 if disabled
getNodeName(nodeInst) # returns the name of the node
getNodePos(nodeInst) # returns an array of [X, Y] value in canvas
 
setNodeActive(nodeInst, active) # set active to 0 for disabled, or 1 for enabled
setNodeName(nodeInst, name)
setNodePos(nodeInst, pos) # set an array of [X, Y] value in canvas. You can use another node to deduce a position. Default node width in canva is 200 units and separation is an additional 100 units, node height varies depending on type.

Operator only accessors :

getNodeTypeName(nodeInst) # returns the operator type name
getNodeType(nodeInst) # returns the operator numeric type 
 

Selector only accessors :

getNodeEntities(nodeInst) # returns the "entities" string of the selector
setNodeEntities(nodeInst, entities) # sets the "entities" string of the selector

Operator Node Attributes

Operator node attributes depend on the node operator type. Their list can be queried with a function, or the full description of nodes can be found in the [GolaemInstallDir]/scripts/glm/golaem_layoutNodes_definition.json file. Type depends on the attribute, and is always get/set with keyframe and keyValues arrays. Keyframe array can be empty if the attribute is not keyframed. In that occurence, the parameter value will be found in key value between double brackets [[ ]], meaning first value of first (default) frame. For more information about keyFrames and keyValues expected format, see the detailed description of the Simulation Cache Layout File (.gscl)

listNodeAttributes(operatorNode, attributeNameList) # takes an empty attributeNameList list() and feed it, or return False
getNodeAttribute(operatorNode, attributeName, keyFrames, keyValues) # returns None on error
setNodeAttribute(operatorNode, attributeName, keyFrames, keyValues) # returns False on error

Connections

Connections between nodes are requested and handled by those functions. User is responsible to link nodes which are part of the same graph (main graph or a group).

listIncomingConnections(fileHandle , nodeInst, incomingNodeIds) # incomingNodeIds must be an empty list, feed the list or return False
listOutgoingConnections(fileHandle , nodeInst, outgoingNodeIds)  #outgoingNodeIds must be an empty list, feed the list or return False
connect(fileHandle , fromNodeId, toNodeId) # return False on error
disconnect(fileHandle , fromNodeId, toNodeId) # return False on error

Nodes creation

Two methods allow to build new nodes. Nodes can be inserted after other nodes. In that case, a link is automatically done between 'insertAfterNode' and the new node. If the 'insertAfterNode' is root, the "root" is forwarded to the new node. If the new node is the first of a graph (group or main graph), it becomes root automatically. You can refer to the [GolaemInstallDir]/scripts/glm/golaem_layoutNodes_definition.json file for a complete layout nodes definitions.

createSelector(fileHandle , entities, insertAfterNode=None, parentGroupNodeId=None) # returns None on error, or the created selector node instance
listOperators(fileHandle, operatorsList) # feed the operatorsList list() with all the possible operator type names
createOperator(fileHandle , operatorTypeName, insertAfterNode=None, parentGroupNodeId=None) # returns None on error, or the created operator node instance

Nodes deletion

The deletion function takes only a single argument, the node to delete. If the node was the root of its graph, it is the user responsability to set a new root. Deleting a node will also delete all its incoming and outgoing connections.

deleteNode(nodeInstance)

Saving The Layout File

The modified layout can be saved in place, or by specifying a new path. 

saveLayoutFile(fileHandle, newPath = None)

Basic usage sample code

# example
 
# Insure that we have an app before calling layout
from glm.Qtpy.Qt import QtWidgets
try:
   app = QtWidgets.QApplication([])
except:
   app = None

from glm.layout import *
from glm.devkit import * 

setGolaemStandaloneProduct()

filePaths = list()
filePaths.append("C:/temp/aFirstLayoutFile.gscl")
filePaths.append("C:/temp/aSecondLayoutFile.gscl")

for filePath in filePaths:
   print("processing file {}".format(filePath))
   fileHandle = openLayoutFile(filePath)
   if (fileHandle is not None):

      # get current Root Node Id for main graph
      rootId = getRootId(fileHandle)

      # get current Root Node from Id (to be able to link next nodes)
      previousRootNode = getNodeById(fileHandle, rootId)

      # create a selector, linked after root and automatically set as root instead
      translateSelectorNode = createSelector(fileHandle, "*", previousRootNode)

      # create a Translate (case sensitive name) operator, linked after selector and automatically set as root instead
      translateOperatorNode = createOperator(fileHandle, "Translate", translateSelectorNode)

      # set the translate attribute of the Translate node, no keyframe, a single value [10, 0, 0] in the first value of the first (only) frame
      setNodeAttribute(translateOperatorNode, "translate", [], [[[10, 0, 0]]])
      
      currentFilePath = getLayoutParameter(fileHandle, "currentFilePath")
      currentFilePath2 = "{}_modified.gscl".format(currentFilePath)

      saveLayoutFile(fileHandle, currentFilePath2)