Golaem Simulation Cache Python API

Golaem Crowd is shipped with a SWIG generated glm.devkit Python module. It is strongly advised to use the API provided by this file to handle Golaem Simulation Cache data. As the Python module has been generated from the Golaem Simulation Cache C++ API, all structures listed in this page can be inspected in the C++ API, especially the glmSimulationData.h and glmFrameData.h.

Including Golaem Simulation Cache API file in a project

The Golaem Simulation Cache API can be added to a project, by importing the module:

from glm.devkit import * 

Initialize/Uninitialize the Golaem API

Initialize the Golaem API

When using Licensing functions, it is required to specify the name of the product to check license for (available product names are GolaemForMaya, GolaemForKatana, GolaemForUnreal, GolaemForHoudini, Standalone) :

initGolaemProduct(productName, pleLicense) # set the second string to ''

Then the Golaem API can be initialized:

initGolaem(bool useThreadedFramedataDecompression) #by default, Golaem will use several threads to decompress frame data, but this can be set to monothreaded if needed

Uninitialize the Golaem API

Then the Golaem API can be uninitialized the following way

finishGolaem()

And the Golaem Product name:

finishGolaemProduct()

Using the API to load cache files, read and write data

Load an existing cache

Loading is done in 2 steps.

The first step is to load a GlmSimulationData from a gscs file. It will read shared data valid for all the cache frame, such as sizes and types. There is one gscs (simulation) file per CrowdField to load. These files are loaded via the command :

simulationData = createAndReadSimulationData('cacheFilePath/cacheFileName.gscs')
if (simulationData is not None):
    print('Success while opening Simulation Data file')

The command returns None on error.

Once a GlmSimulationData has been loaded, each frame data can be loaded via :

frameData = createFrameData(simulationData)
frameStatus = readFrameData(frameData, simulationData, 'cacheFilePath/cacheFileName.gscf')

The read command returns a status GSC_SUCCESS on success, or an error amongst:

GSC_SUCCESS No Error, everything went fine.
GSC_FILE_OPEN_FAILED File does not exist, or have access rights issues, or is already opened by another process
GSC_FILE_MAGIC_NUMBER_ERROR File header does not begin by 0x65CF or 0x65CF, this is not a Golaem Simulation Cache
GSC_FILE_VERSION_ERROR Incorrect version, could be a newer version of the Golaem Simulation Cache
GSC_FILE_FORMAT_ERROR Incorrect format, could be a newer version of the Golaem Simulation Cache
GSC_SIMULATION_FILE_DOES_NOT_MATCH The GlmSimulationData, used to read that frame file, does not match the one used at the export (the size of some items differ : entity count, etc.)

Accessing/Modifying Simulation and Cache data

The simulation data provides access to some global variables such as _entityCount, _entityIds, _characterIdx... The complete list can be accessed from glmSimulationData.h.
They can be accessed through accessors which return array pointers which can be cast in Python array the following way. Notice that each array type has its own cast function.

# as _entityIds is declared as int64_t pointer in glmSimulationData.h, use the relative cast function
entityIds = int64Array_frompointer(simulationData._entityIds)

The cache data can be accessed and modified the following way. The complete list can be accessed from the devkit.py module and the glmFrameData.h header:

getBonePositions(frameData)
getBoneOrientations(frameData)
getSnsValues(frameData)
getGeoBehaviorAnimFrameInfo(frameValue)

getPPFloatAttributeName(simulationData, attrIdx)
getPPFloatAttributeData(frameData, attrIdx)
getPPVectorAttributeData(frameData, attrIdx)
... more accessors in the devkit.py module

Those accessors return float array pointers which can be cast in a Python array the following way:

bonePos = (floatArray_frompointer(getBonePositions(frameData)))

Some accessors are also provided to help dealing with frame data

getEntityBoneCount(entityIndex, simulationData)
getEntityBoneOffsetIndex(entityIndex, simulationData)

Writing a cache 

Once cache data has been modified, it can be written back if required :

writeFrameData('cacheFilePath/cacheFileName.gscf', frameData, simulationData)

The write command returns a status GSC_SUCCESS on success, or an error from the above table

Clean-up allocated memory

Note that GlmFrameData can be kept allocated to edit several frames. Once done with reading./editing, each Simulation and frame data must be destroyed by calling the commands :

destroyFrameData(frameData, simulationData)

then

destroySimulationData(simulationData)

Basic usage sample code

simulationData = createAndReadSimulationData('cacheFilePath/cacheFileName.gscs')
if (simulationData is not None):
    print('Success while opening Simulation Data file')

# read entityIds and characterIds

entityIds = int64Array_frompointer(simulationData._entityIds)
characterIds = int32Array_frompointer(simulationData._characterIdx)


frameData = createFrameData(simulationData)

if (readFrameData(frameData, simulationData, 'cacheFilePath/cacheFileName.gscf') == GSC_SUCCESS):
    print('Success while opening Frame Data file')
else:
    print('Error while opening Frame Data file')
    destroyFrameData(frameData, simulationData)
    destroySimulationData(simulationData)
    return


# get bone position array
bonePos = floatArray_frompointer(getBonePositions(frameData))

# set root bone height (Y) to 0
entityCount = simulationData._entityCount
for iEntity in range(0, entityCount):
    # get the bone count for this entity
    boneCount = getEntityBoneCount(iEntity, simulationData)
    # get the bone offset for this entity

    boneOffset = getEntityBoneOffsetIndex(iEntity, simulationData)
    # iterate for each bone of this entity
   
for iBone in range(0, boneCount):
        # each position is made of 3 float (*3), and we access the Y (+1)
        bonePos[(
boneOffset + iBone) * 3 + 1] = 0

# write frame data
if (writeFrameData('cacheFilePath/cacheFileName.gscf', frameData, simulationData) == GSC_SUCCESS):
    print('Success while writing Frame Data file')

# read float attribute names and data available in the cache
for iFlAttr in range(simulationData._ppFloatAttributeCount):
    print('Name of attribute {} is {}'.format(iFlAttr, getPPFloatAttributeName(simulationData, iFlAttr)))
    attrValue = floatArray_frompointer(getPPFloatAttributeData(frameData, iFlAttr))
    for iEntity in range(simData._entityCount):
        print(attrValue[iEntity])
 
# clean memory
destroyFrameData(frameData, simulationData)
destroySimulationData(simulationData)

Taking Layout into account

The above example only works with raw simulation and frame data files. A higher level API is available when using Golaem Layout files. This API matches the Golaem Simulation Cache C++ API. It uses internally a Simulation Cache Factory object which needs to be initialized with the following code:

initSimulationCacheFactory()

Once it is initialized, the following steps are needed to setup the Simulation Cache Factory :

Load the Golaem Character files

loadSimulationCacheFactoryCharacters(characterFiles)

The characterFiles parameter is a single string. Multiple files can be specified using the ";" delimiter.

Load the terrain files (if required):

loadSimulationCacheFactoryTerrain(sourceTerrainFile, destinationTerrainFile)

Load the Golaem Layout files:

loadSimulationCacheFactoryLayoutFiles(layoutFiles)

The layoutFiles parameter is a single string. Multiple files can be specified using the ";" delimiter.

After this step, you can obtain a reference to a Cached Simulation through an index:

cachedSimulationIndex = getCachedSimulationIndex(cacheDir, cacheName, simulationName)

To obtain the GlmSimulationData and GlmFrameData that were modified by the Layout files, use the following functions:

getFinalSimulationData(cachedSimulationIndex)
getFinalFrameData(cachedSimulationIndex, frameValue, interpolateBetweenFrames)
# frameValue: float, interpolateBetweenFrames: bool

The unmodified simulation and frame data can also be obtained with:

getSrcSimulationData(cachedSimulationIndex)
getSrcFrameData(cachedSimulationIndex, frameValue, interpolateBetweenFrames) 
# frameValue: float, interpolateBetweenFrames: bool

The GlmSimulationData and GlmFrameData objects that are obtained this way cannot be modified, as they belong to the Simulation Cache Factory, which is also responsible for cleaning the memory of the simulation and frame data objects - the destroyFrameData and destroySimulationData functions are not needed.

To obtain a modifiable frame data, use:

createAndCopyFrameData(srcFrameData, simulationData)
# this will need cleanup with destroyFrameData

The cache data can be accessed with the same functions as described in the previous example.

At the end of the script, the Simulation Cache Factory must be uninitialized the following way:

finishSimulationCacheFactory()

Sample code with Golaem Layout

This sample prints out the position of the root bone of each entity in the cache

import glm.devkit as devkit

def displayCacheInfo():
    characterFiles = "./characters/CasualMan_Light.gcha"
    layoutFiles = "./cache/layout.gscl"
    cacheDir = "./cache"
    cacheName = "pythonAPISample"
    simulationName = "crowdField1"
    currentFrame = 400

    devkit.initGolaemProduct("Standalone", None)
    devkit.initGolaem()

    devkit.initSimulationCacheFactory()
    devkit.loadSimulationCacheFactoryCharacters(characterFiles)
    devkit.loadSimulationCacheFactoryLayoutFiles(layoutFiles)

    cacheIndex = devkit.getCachedSimulationIndex(cacheDir, cacheName, simulationName)

    simulationData = devkit.getFinalSimulationData(cacheIndex)
    if simulationData is None:
        print("Could not get simulation data!")
    else:
        frameData = devkit.getFinalFrameData(cacheIndex, currentFrame)
        if frameData is None:
            print("Could not get frame data for frame " + str(currentFrame) + "!")
        else:
            entityCount = simulationData._entityCount
            print("Found " + str(entityCount) + " entities:")
            entityIds = devkit.int64Array_frompointer(simulationData._entityIds)
            # get bone position array
            bonePos = devkit.floatArray_frompointer(devkit.getBonePositions(frameData))
            for iEntity in range(0, entityCount):
                entityId = entityIds[iEntity]
                # get the bone offset for this entity
                boneOffset = devkit.getEntityBoneOffsetIndex(iEntity, simulationData)
                bonePosStr = str(bonePos[boneOffset * 3]) + " " + str(bonePos[boneOffset * 3 + 1]) + " " + str(bonePos[boneOffset * 3 + 2])
                print(str(entityId) + " pos [" + bonePosStr + "]")

    devkit.finishSimulationCacheFactory()
    devkit.finishGolaem()
    devkit.finishGolaemProduct()


displayCacheInfo()

Using the API to fetch Character File data

Function Description
getBoneNames(characterFile) Returns the bone names or a Character File
getSortedBones(characterFile) Returns the sorted bone ids of a Character File
getParentBones(characterFile) Returns the parent bone ids of a Character File (non sorted)
getShadingGroupNames(characterFile) Returns the Shading Group names of a Character File
getShaderAssetNames(characterFile) Returns the Shader Asset names or a Character File
getRenderingTypeNames(characterFile) Returns the Rendering Type names of a Character File

 

Basic Usage Sample Code

characterFile = 'N:/CrowdMan.gcha'
sortedBoneIds = intArray_frompointer(getSortedBones(characterFile))
boneNames = getBoneNames(characterFile).split(';')
parentIds = intArray_frompointer(getParentBones(characterFile))

# get sorted bone names
for iBone in range(0, len(boneNames)-1):
    print('Bone Name is {}'.format(boneNames[sortedBoneIds[iBone]]))
    print('Parent Bone Name is {}'.format(boneNames[parentIds[sortedBoneIds[iBone]]]))

Using the API to query and edit Golaem Fur Cache files (gfc)

Load a Golaem Fur Cache File

The bone names or a Character File can be accessed the following way:

furData = createAndReadFurData(furFile)

Accessing/Modifying Fur Data

As the Python module has been generated from the Golaem C++ API, the fur file structure can be inspected in the C++ API, especially the glmFurData.h. There are also some predefined functions:

setFurWidths(furData, baseWidth, tipWidth)

Write a Golaem Fur Cache File

Once fur data has been modified, it can be written back if required :

writeFurData(furFile, furData)

Clean-up allocated memory

Note that the fur data can be kept allocated to edit attributes. Once done with reading./editing, each the fur data must be destroyed by calling the commands :

destroyFurData(furData)

Basic Usage Sample Code

from glm.devkit import *

# variables
mainFurIn = 'N:/inFurFile.gfc'
mainFurOut = 'N:/outFurFile.gfc'

# read gfc data
furData = createAndReadFurData(mainFurIn)

# check data
print('Number of curve sets: ' + str(furData._curveGroupsCount))
if furData._curveGroupsCount:
  furGData = furData.getCurveGroup(0)
  print('Number of curve in set 0: ' + str(furGData._curveCount))
  numVerts = uint32Array_frompointer(furGData._numVertices)
  print('Number of control points in curve 0: ' + str(numVerts[0]))
  # query first curve
  cPos = floatArray_frompointer(asFloatPointer(furGData._positions))
  cWidths = floatArray_frompointer(furGData._widths)
  dWidths = []
  for iV in range(numVerts[0]):
    dWidths.append(cWidths[iV])
  print(dWidths)

# set widths
setFurWidths(furData, 1, 0.5)

# write out
writeFurData(mainFurOut, furData)
destroyFurData(furData)

Using the API to fetch License Info

Fetch License Information

The type of license can be checked the following way:

usingGolaemPLELicense()
usingGolaemFullLicense()
usingGolaemLayoutLicense()

The license information can be fetched the following way:

getGolaemLicenseString()
getGolaemVersionString()