Skip to content

06 Tutorials

Kiryha edited this page Feb 14, 2018 · 359 revisions

Tutorials

In this section located techniques, tips and tricks which are not related to Animation DNA directly.
First step in Animation DNA learning is Quick start tutorial
Quick start | Programming | Arnold | Cinematography | Houdini

Programming with Python for artists

If you think that you have an artistic brain and developing is beyond your possibilities, believe me, you're just lazy.

You don't need any unique knowledge or special brain structure. You just need a willingness to code and necessity of practical application of developing (and you defiantly have it if you work in Maya).

Coding could be hard only at the beginning, but the more you will practice the easy it will be for you. And finally, you will realize that coding is also an art

A) Theory
Intro | PyMel basics | Developing example | Art of good code | Classes | Documenting tools
B) Practice
General | Attributes | Objects | Files | Lists | Strings | Rendering | Interfaces | FTrack | Shotgun

Programming theory

Intro | PyMel basics | Developing example | Art of good code | Classes

Introduction to artistic developing

From the beginner point of view programming in Maya allow us to execute particular action with many objects at once.

For example, double the intensity of all lights in the scene.

Programs allow to automate a lot of processes and shift human work to computer shoulders. Also, a computer does not make mistakes, so the result of execution of correct code will be stable and predictable. Every action that you can express as a chunk of code should be scripted.

Starting point for all code

All you need to write the first block of code is open Python tab of Maya Script Editor and enter:

import pymel.core as pm

This is what all your code will always start from. After that line, you will place procedures which will solve particular tasks.

As an original example, lets write a procedure which will print: Hello World!

import pymel.core as pm
print 'Hello World!'

To run the code you can:

  • press ExecuteAll button of script editor
  • drag your code with MMB to a shelf and press the button that will appear
  • save code in a file and execute it in Maya from file
Running code from a file

If you need to run Python code from a file:

  • save your code as *.py file in the desired place (C:/hello.py)
  • let Maya know this place by running in script editor: sys.path.append('C:/')
  • import code import hello

The best option to setup path for your scripts and run them in Maya is using wrapper. In this tutorials, we will run code from Script Editor only.

Developing foundation

Lists and loops

Lists and loops are bases of coding. Loops allow us to do something (command) with a set of objects (list). So we have two major tasks in developing:

  • creating and editing lists
  • writing procedures (set of commands)

Loop allows to apply procedure to a list:

import pymel.core as pm
for eachObject in listOfObjects:
    command
Variables

When you create a list, you need to keep this list somewhere to use it later. Your store objects or data in variables. In this code list is a variable: list = [object_A, object_B, object_C] which contain set of 3 objects.

Coding algorithm.

Majority of projects executes with progressive workflow: from rough form in the beginning to refined result at the end. From general to specific. Writing code usually will flow vice versa:

  • you find a command that will do your action
  • you find a way to perform a particular command to a concrete object
  • you find a way to generalize code: obey the command to work with an input list of necessary objects in all possible cases
Developing with Google

Probably, all basic task you need to solve somebody already solved. Nowadays you can start to write your code just by asking google proper questions. The Proper question is a great part of the answer and this means you have to find correct question to get a necessary answer but google is smart and auto complete will help you even with that.

Sure, you have been familiar with python syntax and basic Maya commands and writing the first block of working code may take days but the more you will practice the faster you will code.

Sample developing

When you need to apply some action to an object or list, this basically means that you need to find a proper command that will execute a necessary action.

Maya has an awesome feature which allows starting writing code without any preliminary preparation. Anything you do in the interface is written as Maya Embed Language (MEL) code in Script Editor. The name of MEL command usually the same as the name of PyMel command, so often finding a command is an easy task.

Inspecting MEL messages is also a way to find an attribute to deal with.

Vocabulary of artistic developer

OBJECT — anything you need to deal with in your code (Maya scene, light, attribute etc).
LIST — set of objects.
COMMAND — action that you need to apply to object.
PROCEDURE — is a set of commands.
VARIABLE — container for data.

Artistic way of writing code

An artistic way of writing code is finding MEL commands and object attributes in Script Editor in conjunction with asking google how to do something in PyMel.

Step by step example

Lets try to double intensity of all lights in the scene.
Search for: select all lights pymel. Go to the first link, at the bottom of the page in example section you will find a command which contain "light":
pm.ls( geometry=True, lights=True, cameras=True )

pm.ls is a command which allow to create lists. This is command #1 your will deal with. Best friend for creating lists!

To store list of lights in a variable:

import pymel.core as pm
listOfLights = pm.ls(lights=True)

Now we just need to find a way to double light intensity with PyMel using coding algorithm.

Select any light in the scene, change intensity value and look into history window of Script Editor where you should find the result of your action in MEL language:
setAttr "spotLightShape1.intensity" 2;
It tells you: I set intensity attribute of spotLight to 2.

You have several syntax options to set attributes in PyMel. We can "translate" MEL above into Python:

import pymel.core as pm
pm.setAttr('spotLightShape1.intensity', 2)

So you have a list of objects, you know attribute to work with, you know the command. Now you need to formulate a task (double the intensity) as a PyMel procedure.

Double mean multiply by 2. E.g. you need to get intensity value of each light, multiply by 2 and finally set intensity value with a result of the multiplication.

Find intensity value possible with getAttr command:

import pymel.core as pm
pm.getAttr('spotLightShape1.intensity')

All this chunk of codes works separately with a particular object spotLightShape1. We need to join them and generalize to get the working program for any scene. Another way to get or set attributes is:

object.attribute.get()
object.attribute.set(value)

To formulate procedure we will use variable named "valueCurrent" for data exchange — store source intensity value for multiplication operation. Result of multiplication will store in variable "valueResult". You can give any name to variables but better use nice and descriptive names. The attribute we will operate is "intensity".

valueCurrent = object.intensity.get()
valueResult = valueCurrent*2
object.intensity.set(valueResult)

We need to use loop to apply procedure of multiplication of intensity to each element of list (list contain set of all lights in your scene)

import pymel.core as pm
listOfLights = pm.ls(lights=True)

for object in listOfLights:
    valueCurrent = object.intensity.get()
    valueResult = valueCurrent*2
    object.intensity.set(valueResult)

When you code become more complex you will find useful creating "reports" of code execution. It could be done with print 'any data you need to see ' command which writes in script editor any data you need. Here we print message inside quotes print 'message' and replace {number} with variable values in brackets .format(value):

import pymel.core as pm
listOfLights = pm.ls(lights=True)

for object in listOfLights:
    valueCurrent = object.intensity.get()
    valueResult = valueCurrent*2
    object.intensity.set(valueResult)
    print 'object: {0} intensity: {1} >> {2}'.format(object, valueCurrent, valueResult)

Reducing code

import pymel.core as pm
listOfLights = pm.ls(lights=True)
for i in listOfLights:
    i.intensity.set(i.intensity.get()*2)

Creating a PyMel function from block of code with ability to change multiplication value:

def litDouble(value):
    listOfLights = pm.ls(lights=True)
    for i in listOfLights:
        i.intensity.set(i.intensity.get()*value)

Running a function:

litDouble(value)

If your will be able to build interface for this function you can consider you are PyMel developer.

Art of good code

When you will pass first painful steps and get awkward blocks of more or less working code you will need to move forward in developing. There are some rules which you need to obey to make next step:

  • Comment basic actions
  • Give nice and descriptive names
  • Clean up code from unnecessary data
Code characteristics

code scope = correctness*3 + design*2 + style

Classes

CLASS - template for creating objects.
OBJECT - instance of a class
METHOD - function defined in a class
ATTRIBUTE - variable bound to an instance of a class

Also, a CLASS can be defined as a new custom type (string, float, int etc) in Python.
Also, OBJECT can be defined as a sum of some data and code, manipulating this data. Encapsulation is a mechanism for joining(summing) data and code.

You can call class directly, or create an instance of a class. Attribute can be instance attribute (create an instance of a class) or class attribute (direct call). A class attribute (static variable) defined in a class outside of any function and without self. parameter. Refer to a static variable inside a class should be done within a self.

class ClassName:
    classAttribute = 'STATIC'
    def __init__(self):
        self.instanceAttribute = 'INSTANSE'

Direct call:

ClassName.classAttribute

Instance call:

instance = ClassName()
instance.instanceAttribute

Class call sugar syntax

from pythonFile import classA
instance = classA()

# instance.methodA() == classA.metodA(instance)

Creating documentation with Sphinx

Even the simplest tools require at least some sort of guidelines for the final users. There are plenty of options for creating technical documentation for your packages (like this GitHub wiki, for example). Another great option is Sphinx tool, which allows creating an HTML site with the desired structure. In general, the workflow is: first you build your wiki in the *.rst file format as a Sphinx project, then you compile it with Sphinx to *.html. You can distribute compiled HTML as an offline package, upload on the web server or create PDF document from it.

Install Sphinx

Considering you have Python in place, to install Sphinx you need to get PIP first:

  • Download get-pip.py, place in Python folder, e.g C:/Python27/
  • Run cmd and execute: python get-pip.py
  • Add pip path to a system environment variables: PATH = c:/Python27/scripts
  • Install Shinx: pip install Sphinx
  • Install sphinx_rtd_theme: pip install sphinx_rtd_theme. It will define the style of html similar to Ftrack API.
Create Shinx project

Each documentation is a separate Sphinx project.

  • Run in cmd: sphinx-quickstart and follow instructions.
  • Enter as a project root folder where you would like to create and keep the source files for documentation, e.g. D:/DOCS/AnimationDNA/sphinx/
  • Edit conf.py file in D:/DOCS/AnimationDNA/sphinx/source: replace html_theme = 'alabaster' line with:
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
Compile Sphinx documentation

To build .html documentation from .rst source run command line from D:/DOCS/AnimationDNA/sphinx/ and enter:

sphinx-build -b html D:/DOCS/AnimationDNA/sphinx/ <path to HTML help folder>

Programming practice

Here you will find procedures for most common tasks in Maya.

General | Attributes | Objects | Files | Lists | Strings | Rendering | Interfaces | FTrack

General

Find methods for a node:
import pymel.core as pm
nodeCheck = pm.PyNode('<nodeName>')
 
for i in dir(nodeCheck):
    print i

Attributes

Get objects by attribute
listAtr = pm.ls('*.nameOfAttribute')
for i in listAtr: 
    object = i.node()
Add string attribute to selected objects
def addString(attrName):
    sel = pm.ls( sl=1 )
    for i in sel:
        if not pm.attributeQuery( attrName, node = i, exists = True ):
            pm.addAttr(i, ln = attrName, nn = attrName, dt = 'string')
Add color attribute to selected objects
def addColor(attrName):
    sel = pm.ls( sl=1 )
    for i in sel:
        pm.addAttr(i, longName = attrName, niceName = attrName , usedAsColor = True, attributeType = 'float3' )
        pm.addAttr(i, longName='R' +  str(attrName), attributeType='float', parent=attrName )
        pm.addAttr(i, longName='G' +  str(attrName), attributeType='float', parent=attrName )
        pm.addAttr(i, longName='B' +  str(attrName), attributeType='float', parent=attrName )
Set string attribute for selected objects
def setString(attribute, value):
    sel = pm.ls(sl=1)
    for i in sel:
        i.attr(attribute).set(value)
Lock attribute of selected objects
def attrLock(attribute):
    sel = pm.ls(sl=1)
    for i in sel:
        i.attr(attribute).lock()

Objects

Get shapes of selected objects
listShapes =  pm.ls(dag=1,o=1,s=1,sl=1)
Create PyMel object by name
object = pm.PyNode('nameOfNode')
Unlock and delete selected nodes
def unlockAndDelete():
    sel = pm.ls( sl = True )
    for i in sel:
        i.unlock()
        pm.delete(i)
Select instances of selected objects
def selectInstances():
    pm.select(pm.ls(ap = 1, dag = 1, sl = 1 ))
Create animated Arnold standin and set path
def addStandin():
    standin = pm.createNode('aiStandIn', name = 'STANDIN')
    standin.dso.set('D:/DNA/PROD/3D/cache/standin.####.ass')
    standin.useFrameExtension.set(1)
    pm.expression(ae = True, s = '{0}.frameNumber = frame'.format(str(standin)) )
Triger bounding box display of selected objects
def boundingBox():
    pm.pickWalk(d ='down')
    sel = pm.ls(sl = 1, shapes = 1, selection = 1)
    if (sel[0].overrideEnabled.get() == 0):
        for i in sel:
            i.overrideEnabled.set(1) 
            i.overrideLevelOfDetail.set(1)
    else:
        for i in sel:
            i.overrideEnabled.set(0) 
            i.overrideLevelOfDetail.set(0)

Files

Basic Maya scene file manipulations
pm.sceneName() # Get name of current scene
pm.importFile(fullPath) # Import file
pm.exportSelected(fullPath) # Export selected to a file
list files in directory
import glob
listExisted = glob.glob('D:/DNA/images/*.jpg')
Create reference
pm.createReference(fullPath, sharedNodes =('shadingNetworks', 'displayLayers', 'renderLayersByName') , ns = nameSpace )
Get full path of reference by namespace
fullPathRef = pm.FileReference(namespace = 'nameSpace')
Replace reference
fullPathRef.replaceWith(fullPathRefNew)
Export alembic
from maya.mel import eval
eval('AbcExport -j " -framerange {2} {3} -uvWrite -root {0} -file {1}"'.format(groupName, pathABC, frameStart, frameEnd))
Import alembic
from maya.mel import eval
eval(' AbcImport -ct  "{0}" "{1}" '.format(groupName, pathABC))
Write data to a file with JSON
import json
dataFile = 'D:/DNA/datFile.json'
dataTransfer = 'Information to write to a file'
json.dump(dataTransfer, open(dataFile, 'w'), indent = 4) # write
Read data from a file with JSON
import json
dataFile = 'D:/DNA/datFile.json'
dataTransfer = json.load(open(dataFile))

Lists

Create list by object name
list = pm.ls('nodeName') # With exact name
list = pm.ls('prefix_*') # With exact part
list = pm.ls('*:nodeName') # With namespaces
Cut last element from a list
list = ['A', 'B', 'C','D','E']
listLast = list.pop(-1)
Slice a list
a[start:end] # items start through end-1
a[start:]    # items start through the rest of the array
a[:end]      # items from the beginning through end-1
a[:]         # a copy of the whole array

a[-1]    # last item in the array
a[-2:]   # last two items in the array
a[:-2]   # everything except the last two items

a[start:end:step] # start through not past end, by step
Dictionary
dataShotDic = {'characters' : [ ], 'props' : [] , 'environment' : [] , 'EDA' : [] , 'endFrame' : []} 
dataShotDic['characters'].append( <assetName> )

String formatting

Replace string with variables
'variable A = {0}, variable B = {1}'.format(variable_A, variable_B) 
Replace number with padding
'{:03d}'.format(1) # result: 001
Divide string
string = 'D:/projects/DNA/3D/scenes'
split = string.split('/')
# Result: ['D:', 'projects', 'DNA', '3D', 'scenes']

Rendering

Set current render to Arnold
def setArnold(*args):
    if( pm.getAttr( 'defaultRenderGlobals.currentRenderer' ) != 'arnold' ):
        pm.setAttr('defaultRenderGlobals.currentRenderer', 'arnold')
Switch path in Arnold standIn
import pymel.core as pm
for i in pm.ls(sl=True): # select ai standIns
    old_value =  i.dso.get()
    new_value = 'C:/RedCow.ass'
    i.dso.set(new_value)
Create Arnold AOV
import mtoa.aovs as aovs
def addAOV(aovName):
    aov = aovs.AOVInterface().addAOV(aovName)
    return aov
Arnold export ass
# use `s = True` for selected
pm.arnoldExportAss( f = "D:/fileName.ass",  startFrame = 0, endFrame = 1 )
Asign material for selected object shapes
def matAsign(material):
    sel = pm.ls(sl=1)
    for i in sel:
        pm.sets(material, forceElement = i)
Get objects with material
def getObjects(materialName):
    material = pm.ls(materialName)
    SG = pm.listConnections(materialName, type='shadingEngine')
    listObjects = pm.listConnections(SG, type='shape')
    return listObjects
Get shading group and material with object shape
def getShading(objectShape):
    shadingGroup = pm.listConnections(objectShape, type='shadingEngine')
    shader = pm.ls(mc.listConnections(shadingGroup), materials = 1)
    return shader, shadingGroup
Get material from selected shading group
def getMat():
    SG = pm.ls( sl = 1, type = 'shadingEngine' ) 
    materials = pm.ls( pm.listConnections(SG),materials=1 )
    return materials
Get image files of material
imageFiles = pm.listHistory(shadingGroup, type='file')
Select shading groups of selected objects
def selSG():
    list =  pm.ls(dag=1,o=1,s=1,sl=1) 
    shadingGrps = pm.listConnections(list,type='shadingEngine')
    pm.select(clear = True)
    SGS = pm.select(shadingGrps, ne = 1)
Create shader, image file and shading group. Make shader connections.
shader = pm.shadingNode ('lambert', asShader = True, name = 'MATERIAL') 
imageFile = pm.shadingNode ('file', asTexture = True, n = 'TEXTURE' )
SG = pm.sets (renderable = True, noSurfaceShader = True, empty = True, name = shader + 'SG')
imageFile.outColor >> shader.color
shader.outColor >> SG.surfaceShader
Render settings for Arnold
# create an objects from render settings nodes
rgArnold = pm.PyNode('defaultArnoldDriver')
rgArnoldRO =  pm.PyNode('defaultArnoldRenderOptions')
rgCommon = pm.PyNode('defaultRenderGlobals')
rgRes = pm.PyNode('defaultResolution')

rgCommon.imageFilePrefix.set('D:/fileName') # Set image file path and name

rgArnold.aiTranslator.set('exr') # Set image format to EXR

rgArnoldRO.AASamples.set(12) # Set antialiasing samples

# Set resolution
rgRes.width.set(1998)
rgRes.height.set(1080)

Interfaces

Heads up display
import pymel.core as pm
from functools import partial
def data():
    shot = 'E010-S010'
    frame = pm.currentTime( query = True )
    out = '{0}  << {1}  >> '.format( shot, frame)
    return out
hud = pm.headsUpDisplay( 'HUD', section = 6, block = 1, blockSize = 'medium', label = 'INFO:', labelFontSize = 'large', command = partial(data), event='idle')
# DELETE HUD:  pm.headsUpDisplay( 'HUD', rem=True )
Window with text field and button.

Pressing a button prints value of text field.

def printValue(input):
    print 'Value in text field: {0}'.format(input.getText())
    
def baseUI(): 
    if pm.window('BASE', exists = 1):
        pm.deleteUI('BASE')
    baseWin = pm.window('BASE', t = 'Base Window', w = 280, h = 100)
    with baseWin:
        
        mainLayout = pm.columnLayout()
                    
        aLayout = pm.rowColumnLayout(nc = 2, parent = mainLayout)   
        stringField = pm.textField(tx = 'TEXT', w= 90, h = 40) 
        buttonPrint = pm.button(l = 'PRINT FIELD VALUE', w = 190)
        buttonPrint.setCommand(pm.Callback (printValue, stringField))
       
    baseWin.show()  
baseUI()
Confirm dialog

A good option to catch events and create variations of next steps.
For example: File exists > overrdide or save next verion.

confirm = pm.confirmDialog ( title = 'Title', message = 'Message', button=['OK', 'CANCEL'], cancelButton= 'CANCEL' )
if confirm == 'OK':
    print 'Pressed OK'    
else:
    sys.exit('Canceled!')

FTrack examples

See FTrack setup and glossary of codes sections in documentation. Used API is 3.3.1

Setup path to API
import os
os.environ['PYTHONPATH'] = 'C:/FTrack_API'
Login to FTRack
import os
os.environ['FTRACK_SERVER'] = 'https://<userAdress>.ftrackapp.com'
os.environ['FTRACK_APIKEY'] = '<API_key>'
os.environ['LOGNAME'] = '<userName>'
Get project data
import ftrack
projectFTrack = ftrack.getProject(<codeProject>)
Get asset data
import ftrack
dataAsset = projectFTrack.getAssetBuilds().find('name', <assetName>)
dataAssetMeta = dataAsset.getMeta() # Get asset metadata
Get shot data
import ftrack
dataShot = ftrack.getShotFromPath([<codeProject>,<codePart>,<codeSequence>,<codeShot>])
listShotLinks = dataShot.getPredecessors() # Get linked assets
frameEnd = dataShot.getFrameEnd() # Get end frame
shotMetaData = dataShot.getMeta() # Get shot metadata
Get data of assets linked to a shot
for i in listShotLinks :
    assetName = i.getName()
    assetCategory = i.getType().getName() # Environments, props, characters 
Add metadata to an asset
import ftrack
assetData = projectFTrack.getAssetBuilds().find('name', <assetName>)
assetData.setMeta( <key>, <value> ) 
Add metadata to a shot
import ftrack
shotData = ftrack.getShotFromPath([<codeProject>,<codePart>,<codeSequence>,<codeShot>]) 
shotData.setMeta( <key>, <value> )

Shotgun examples

Import Shotgun API
import os
os.environ['PYTHONPATH'] = 'C:/ShotGun_API'
import shotgun_api3
import sys
sys.path.append('C:/ShotGun_API')
import shotgun_api3
Create Shotgun API instance, script-based authentication
SERVER_PATH = "https://dna.shotgunstudio.com"
SCRIPT_NAME = 'dna'
SCRIPT_KEY = '06300256ffc46ac346d47ab9942c218b291939b0f131276d1d13c3fd554ec000'

sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY) 
Create Shotgun API instance, user-based authentication
sg = shotgun_api3.Shotgun("https://xxx.com", login="xxx", password="xxx")
Get shotgun project data
allProjects = sg.find("Project",filters=[],fields=['name'])
project = sg.find('Project', [['name', 'is', 'FUTURAMA']] ) 
episode = sg.find('Episode', [['code', 'is', 'REEL_01']] )
sequence = sg.find('Sequence', [['code', 'is', '010']] )
shot = sg.find('Shot', [['code', 'is', 'SHOT_010']]) 
Create a shot in shotgun
dataShot = {'project': project, 'sg_sequence': sequence, 'code':'SHOT_010' }
addShot = sg.create('Shot', dataShot) # Create a shot

Find a shot by SHOT NAME in shotgun

shot = sg.find('Shot', [['code', 'is', 'SHOT_010' ]]) # Option A

filters = [['project','is',{'type': 'Project', 'id': 106}],['code', 'is', 'SHOT_010']]
shot = sg.find_one('Shot', filters) # Option B
Modfy shot in shotgun
data = { 'description': 'Hello, world!', 'sg_status_list': 'ip' } 
result = sg.update('Shot', shot['id'], data)
Get shotgun shot links
filters = [['shot','is', {'type':'Shot','id':1169}]]
fields = ['asset','shot','sg_continuity_note']
linkedAssets = sg.find('AssetShotConnection',filters, fields)
Get asset list in shotgun
getAssets = sg.find( "Asset", [( 'project.Project.name', 'is', 'FUTURAMA' )], [ 'code' ])
getAssetNames = sorted([asset.get('code', "(no name)") for asset in getAssets])
Get shotgun asset by name
asset = sg.find("Asset", [["project", "is", project], ["code", "is", 'BENDER']])
Shotgun USERs
dicUsersAll = sg.find ('HumanUser',  [ ], ['name', 'login', 'department'])
dicUserProject = sg.find('Project', [['name', 'is', 'FUTURAMA']], ['users'] )[0]['users']
userByName = sg.find('HumanUser', [[ 'name', 'is', 'Kiryha Krysko' ]])
Create Task for shot in shotgun
data = {
    'project': {'type':'Project', 'id':106},
    'content': 'Color',
    'entity': {'type':'Shot', 'id':shot['id']}
    }
result = sg.create('Task', data)
Find a Task by TASK ID
task = sg.find('Task', [['id', 'is',  8082 ]]) 
Find a Task by TASK NAME and SHOT ID
ds_filters = [ ['project', 'is', {'type': 'Project', 'id': '106}] , ['sg_status_list', 'is', 'wtg'] ]
fields = ['sg_internal_rounds', 'sg_status_list']
task = sg.find('Task', ds_filters, fields)
Modify Task
data = { 'sg_description': 'Hello, WORLD!', 'sg_internal_rounds': 0 } 
result = sg.update('Task', 8481, data)
Find all tasks with upstream_tasks != None
ds_filters = [  ['project', 'is', {'type': 'Project', 'id': 106}] ] # Get ALL tsks in project 
fields = ['upstream_tasks', 'sg_status_list'] # Filter dictionary keys
listTasks = sg.find("Task", ds_filters, fields)
for i in listTasks:
    if i['upstream_tasks'] != None:
        print 'Task id {0} has {1} upstream_tasks'.format(i['id'], i['upstream_tasks'] )
List tasks with STATUS = WTG, which are downstream of eventEntity
eventEntity = {'type': 'Task', 'id': 8481}   # Task where STATUS was switch to DONE     
ds_filters = [ ['upstream_tasks', 'is', eventEntity ], ['sg_status_list', 'is', 'wtg'] ]
fields = [ 'sg_internal_rounds' ]
listTasks = sg.find("Task", ds_filters, fields)
print listTasks
Upload thumb
sg.upload_thumbnail('Shot', 1234, 'C:/XXX/pamela_01.JPG')
Create version linked to a shot
filters = [ ['project', 'is', {'type': 'Project', 'id': 106}],
            ['entity', 'is',{'type':'Shot', 'id': 1234}],
            ['content', 'is', 'Color'] ]
task = sg.find_one('Task', filters)   

data = { 'project': {'type': 'Project','id': 106},
         'code': 'NEW_VERSION',
         'sg_status_list': 'rev',
         'entity': {'type': 'Shot', 'id': 1234},
         'sg_task': {'type': 'Task', 'id': task['id']} }
result = sg.create('Version', data)
Set all project tasks
dicSetup = {'upstream_tasks' : None, 'sg_status_list' : 'wtg'}
project = {'type': 'Project', 'id': 106}

def batchSetTask( field, value ):
    tasksAll = sg.find('Task', [ ['project', 'is', project] ], [ field ])
    for i in tasksAll:
        if i[field] != value:
            sg.update("Task", i['id'], data = { field : value })

for field, value in dicSetup.iteritems():
    batchSetTask( field, value )
    print '{0} : {1}'.format(field, value)

Create shotgun group

dataCreate = { 'code': 'GROUP_ART', 'users': [ { "type": "HumanUser", "id": 86 },{ "type": "HumanUser", "id": 225 } ] } 
grpCreate = sg.create('Group', dataCreate)
Find shotgun group
sg_group = sg.find('Group', [['code', 'is',  'GROUP_ART' ]], ['users'])
Modify shotgun group
listUsersOld = sg_group[0]['users']
listUsersAdd = [{'type': 'HumanUser', 'id': 222}]
listUsersNew = listUsersOld + listUsersAdd

dataUpdate = {'code': 'GROUP_ART', 'users': listUsersNew } 
grpUpdate = sg.update('Group',  grpCreate['id'], dataUpdate)

Add thumbnail and billboard image to a shotgun project

def addImages(project):
    '''
    Upload and set Thumbnail and Billboard images to the project
    '''
    imageBillboard = 'C:/images/billboard.jpg'
    imageThumbnail = 'C:/images/thumb.jpg'
    # Add Thumbnail
    sg.upload_thumbnail('Project', project['id'], imageThumbnail)
    # Upload billboard
    sg.upload("Project", project['id'], imageBillboard, field_name='billboard')
    # Get billboard data
    billboard = sg.find_one("Project", [['id', 'is', project['id']]], ['billboard'])
    # Set billboard image
    sg.update("Project", project['id'], {"billboard": billboard['billboard']})

Run custom python scripts from Shotgun interface

You can create your own Python scripts(packages) and execute them from Shotgun web interface. Necessary components to create and run Custom Scripts from Shotgun are:

  • Shotgun Python API. Allow python scripts communicate with Shotgun database
  • Action Menu Item in Shotgun (AMI) to run custom scripts from Shotgun dashboard.
  • Windows registry key to setup path to the script which will handle AMI calls.
  • Python script (or package) to produce required actions with SG data or project files.
Shotgun Python API

Download an place Shotgun Python API on your HDD: C:/shotgunAPI

Action Menu Items

There are two types of AMI you can create: HTTP URLs and Custom protocol handlers. We use custom protocol handler to run a custom script. Custom browser protocol is a link between a browser and application (python script).

Create Action Menu Items for you script: SG Menu > [ + ] > Action Menu Item. Title = My Action, URL = shotgun://myAction

Registry key

To register a protocol in Windows we need to create a registry key. It will handle Action Menu Item requests:

  • Run Registry Editor: [ WIN ] > regedit
  • In HKEY_CLASSES_ROOT right-click > New > Key. Name = shotgun
  • Setup shotgun registry key (add %1 after myAction.py to pass an arguments from AMI):
[HKEY_CLASSES_ROOT\shotgun]
(Default)= URL:shotgun Protocol
URL Protocol =
[HKEY_CLASSES_ROOT\shotgun\shell]
[HKEY_CLASSES_ROOT\shotgun\shell\open]
[HKEY_CLASSES_ROOT\shotgun\shell\open\command]
(Default) = C:\Python27\python D:\PIPE\myAction.py %1

Save this code in text file as myAction.key and run — it will create window registry key.

Now right-click on any Shotgun entity and select My Action, myAction.py should run.

When you run the script from Shotgun, some data is passed to this script as an argument. To catch and sort this data into dictionary run in your script:

arguments = sys.argv[1:] # Get arguments from AMI

def urlParse(arguments):
    # Build a dictionary of arguments parsed to this script from Shotgun
    protocol, fullPath = arguments[0].split(":", 1)
    path, fullArgs = fullPath.split("?", 1)
    action = path.strip("/")
    params = urlparse.parse_qs(fullArgs)
    # print pprint.pformat((action, params))
    return action, params

action, params = urlParse(arguments)
# In this case, action = "myAction"

You can create any number of AMIs with different URLs, catch them (action variable) and run different scripts with the same registry key.

if action == "myAction"
    import myScript as script
    script.run()

Python for Nuke

Import python nuke module:

import nuke

Nuke as a Python Module in external editor

  • Create usrlocal.pth in C:\Python27\Lib\site-packages
  • Add C:\Program Files\Nuke 9.0v7\lib\site-packages line to a usrlocal.pth

Create nodes

reader = nuke.createNode ('Read', 'name {0} file {1}'.format(<nodeName>, <pathToFile>))
reader = nuke.nodes.Read(name = 'READER', file = 'P:/DNA/E000_S010_001.%04d.exr' )   

Basic nodes manipulations

selectedNode = nuke.selectedNode()
node = nuke.toNode( '<nodeName>' )
print node.knob('file').value()
cam = nuke.toNode('<cameraName>') 
print cam['xpos'].value()

Print to console

nuke.tprint('<<  HELLO {}!  >>'.format('WORLD'))

Add string knob to the Project Settings

# Add knob ('name', 'interfaceName')
nuke.root().addKnob(nuke.Multiline_Eval_String_Knob('userData', 'User Data'))
nuke.root()['userData'].setValue('XXX') # Set value
listRootKnobs = nuke.root().knobs().keys() # List all knobs
getUserData = nuke.root()['userData'].value() # Get custom knob value

PySide

Possible to run example code in default Python enviroment (you will need to install PySide) or in Maya and Nuke, than you will need to delete app = QApplication([]) and app.exec_() lines.

event (signal) >>> handler (slot)

Install PySide

Run command prompt, enter: pip install PySide

Compile QT Designer UI file

Create *.bat file:

set FILE=%1
set DIR=%~dp$PATH:1
set FILENAME=%~n1
set NEW_NAME=%DIR%%FILENAME%.py

CALL C:\Python27\Scripts\pyside-uic.exe %FILE% -o %NEW_NAME%

Drag and drop QT Designer *.ui file on the *.bat file to compile Python UI file.

PySide Ctr + Shift button click

from PySide.QtGui import *
from PySide.QtCore import *

class ABC(QWidget, ui.Ui_UI):
    def __init__(self):
        super (ABC, self).__init__()
        self.setupUi(self)

        self.BUTTON.clicked.connect(self.handleClick)

    def handleClick(self):
        modifier = QApplication.keyboardModifiers()
        if modifier == Qt.ShiftModifier:
            print 'Button Pressed with SHIFT'
        elif modifier == (Qt.ControlModifier | Qt.ShiftModifier):
            print 'Button Pressed with Ctr + SHIFT'
        else: # Click
            print 'Button Pressed'

Base PySide Window widget

from PySide.QtGui import *

class window_A(QWidget):
    def __init__(self):
        super(window_A, self).__init__()
        layout = QHBoxLayout() # Create horisontal layout
        self.setLayout(layout) # Use horisontal layout to place widgets in window
        self.resize(300,100) # resize window

if __name__ == '__main__':
    app = QApplication([])
    widget = window_A()

    widget.show()
    app.exec_()

Base PySide Main Window

from PySide.QtGui import *

class windowMain_A(QMainWindow):
    def __init__(self):
        super(windowMain_A, self).__init__()
        # Add Window widget to a Main Window
        self.widget = QWidget(self)
        self.setCentralWidget(self.widget)

if __name__ == '__main__':
    app = QApplication([])
    widget = windowMain_A()

    widget.show()
    app.exec_()

PySide Main window with widgets

from PySide.QtGui import *

class windowMain(QMainWindow):
    def __init__(self):
        super(windowMain, self).__init__()
        # CREATE MAIN WINDOW
        self.widget = QWidget(self) # Create Window widget
        self.setCentralWidget(self.widget) # Add Window widget to a Main Window
        self.setWindowTitle('TEMPLATE') # Title Main window
        self.resize(200,100) # Resize Main Window
        self.setFocus() # Set active widget Main window
        self.LAY_MAIN = QVBoxLayout(self.widget) # Create layout for Window

        # CREATE MAIN WIDGETS
        self.label_A = QLabel('Enter text and press ANY button')
        self.input_A = QLineEdit()
        self.button_A = QPushButton('WIN')
        self.LAY_MAIN.addWidget(self.label_A)
        self.LAY_MAIN.addWidget(self.input_A)
        self.LAY_MAIN.addWidget(self.button_A)

        # CREATE MENU BAR
        # Create menu bar widgets
        self.menu_bar = QMenuBar()
        self.setMenuBar(self.menu_bar)
        self.menu_A = QMenu('Menu')
        self.action_A = QAction('Run Action', self)
        # Add menu widgets to MENU
        self.menu_bar.addMenu(self.menu_A)
        self.menu_A.addAction(self.action_A)

        # CREATE STATUS BAR
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        # CONNECT FUNCTIONS
        self.action_A.triggered.connect(self.function_A)
        self.button_A.clicked.connect(self.function_B)


    # CREATE FUNCTIONS
    def function_A(self):
        input = self.input_A.text()
        message = '>> HELLO, {}!'.format(input.upper())
        self.status_bar.showMessage(message)

    def function_B(self):
        input = self.input_A.text()
        self.status_bar.showMessage('>> ENTERED: {}'.format(input))

    def runWindow(self):
        input = self.input_A.text()
        self.winA = window_A(input)
        self.winA.show()

if __name__ == '__main__':
    app = QApplication([])
    widget = windowMain()

    widget.show()
    app.exec_()

PySide Main Window with widgets and Layouts

from PySide.QtGui import *

class windowMain(QMainWindow):
    def __init__(self):
        super(windowMain, self).__init__()
        # CREATE MAIN WINDOW
        self.widget = QWidget(self) # Create Window widget
        self.setCentralWidget(self.widget) # Add Window widget to a Main Window
        self.setWindowTitle('TEMPLATE') # Title Main window
        self.resize(200,100) # Resize Main Window
        self.LAY_MAIN = QVBoxLayout(self.widget) # Create layout for Window

        # CREATE TOP BLOCK
        # Create vertical TOP layout
        self.layout_A = QVBoxLayout()
        # Create widgets for the TOP layout
        self.label_A = QLabel('Enter text and press OK button')
        self.input_A = QLineEdit()
        # Add widgets to TOP layout
        self.layout_A.addWidget(self.label_A)
        self.layout_A.addWidget(self.input_A)
        # Add TOP layout to MAIN LAYOUT
        self.LAY_MAIN.addLayout(self.layout_A)

        # CREATE BOTTOM BLOCK
        # Create horizontal BOTTOM layout
        self.layout_B = QHBoxLayout()
        # Create widgets for the BOTTOM layout
        self.button_A = QPushButton('OK')
        self.button_B = QPushButton('CANCEL')
        # Add widgets to BOTTOM layout
        self.layout_B.addWidget(self.button_A)
        self.layout_B.addWidget(self.button_B)
        # Add BOTTOM layout to MAIN LAYOUT
        self.LAY_MAIN.addLayout(self.layout_B)

        # CREATE MENU BAR
        # Create menu bar widgets
        self.menu_bar = QMenuBar()
        self.setMenuBar(self.menu_bar)
        self.menu_A = QMenu('Menu')
        self.action_A = QAction('Run Action', self)
        # Add menu widgets to MENU
        self.menu_bar.addMenu(self.menu_A)
        self.menu_A.addAction(self.action_A)

        # CREATE STATUS BAR
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

        # CONNECT FUNCTIONS
        self.action_A.triggered.connect(self.function_A)
        self.button_A.clicked.connect(self.function_B)
        self.button_B.clicked.connect(self.close)

    # CREATE FUNCTIONS
    def function_A(self):
        input = self.input_A.text()
        message = '>> HELLO, {}!'.format(input.upper())
        self.status_bar.showMessage(message)

    def function_B(self):
        input = self.input_A.text()
        self.status_bar.showMessage('>> ENTERED: {}'.format(input))


if __name__ == '__main__':
    app = QApplication([])
    widget = windowMain()

    widget.show()
    app.exec_()

PySide separate UI and functions

from PySide.QtGui import *

# TOOL UI`s
# Main toolkit UI
class UI_toolkit_MAIN(object):
    def setupUi(self, mainwin):
        # CREATE MAIN WINDOW
        self.widget = QWidget(mainwin) # Create Window widget
        self.setCentralWidget(self.widget) # Add Window widget to a Main Window
        self.setWindowTitle('TEMPLATE') # Title Main window
        self.resize(200,100) # Resize Main Window
        self.setFocus() # Set active widget Main window
        self.LAY_MAIN = QVBoxLayout(self.widget) # Create layout for Window

        # CREATE TOP BLOCK
        # Create vertical TOP layout
        self.layout_A = QVBoxLayout()
        # Create widgets for the TOP layout
        self.label_A = QLabel('Enter text and press OK button')
        self.input_A = QLineEdit()
        self.input_A.setPlaceholderText('TYPE')
        # Add widgets to TOP layout
        self.layout_A.addWidget(self.label_A)
        self.layout_A.addWidget(self.input_A)
        # Add TOP layout to MAIN LAYOUT
        self.LAY_MAIN.addLayout(self.layout_A)

        # CREATE BOTTOM BLOCK
        # Create horizontal BOTTOM layout
        self.layout_B = QHBoxLayout()
        # Create widgets for the BOTTOM layout
        self.button_A = QPushButton('SUB TOOL')
        self.button_B = QPushButton('SHOW')
        # Add widgets to BOTTOM layout
        self.layout_B.addWidget(self.button_A)
        self.layout_B.addWidget(self.button_B)
        # Add BOTTOM layout to MAIN LAYOUT
        self.LAY_MAIN.addLayout(self.layout_B)

        # CREATE MENU BAR
        # Create menu bar widgets
        self.menu_bar = QMenuBar()
        self.setMenuBar(self.menu_bar)
        self.menu_A = QMenu('Menu')
        self.action_A = QAction('Run Action', self)
        # Add menu widgets to MENU
        self.menu_bar.addMenu(self.menu_A)
        self.menu_A.addAction(self.action_A)

        # CREATE STATUS BAR
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)

# Subtool UI
class UI_subTool_A(object):
    def setupUi(self, win):
        self.LAY_MAIN = QHBoxLayout(win)
        self.resize(100, 50)

        self.label_A = QLabel()
        self.LAY_MAIN.addWidget(self.label_A)

# TOOL FUNCTIONALITY
# Main toolkit functionality
class toolkit_MAIN(QMainWindow, UI_toolkit_MAIN):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setupUi(self)

        # CONNECT FUNCTIONS
        self.action_A.triggered.connect(self.function_A)
        self.button_A.clicked.connect(self.runWindow)
        self.button_B.clicked.connect(self.function_B)

    # CREATE FUNCTIONS
    def function_A(self):
        input = self.input_A.text()
        message = '>> HELLO, {}!'.format(input.upper())
        self.status_bar.showMessage(message)

    def function_B(self):
        input = self.input_A.text()
        self.status_bar.showMessage('>> ENTERED: {}'.format(input))

    def runWindow(self):
        input = self.input_A.text()
        self.subtool = subTool_A(input)
        self.subtool.show()

# Subtool functionality
class subTool_A(QWidget, UI_subTool_A):
    def __init__(self,input):
        QWidget.__init__(self)
        self.setupUi(self)
        self.label_A.setText(input)

# RUN THE TOOL
if __name__ == '__main__':
    app = QApplication([])
    tool = toolkit_MAIN()
    tool.show()
    app.exec_()

File inspector

# Save this code as Python file on HDD
# Top Window: list of files in current directory
# Bottom Window: content of selected in Top Window file

import os
from PySide.QtGui import *

# Get path to this file on HDD
path = os.path.dirname(__file__)


class windowTree(QWidget):
    def __init__(self):
        super(windowTree, self).__init__()
        self.LY = QVBoxLayout(self)

        self.list = QListWidget()
        self.txt = QTextBrowser()

        self.LY.addWidget(self.list)
        self.LY.addWidget(self.txt)

        self.fillList()

    def fillList(self): # Fill Top Window with a list of files in the current directory
        def updateText(item): # Show selected file content in Bottom Window
            # Read selected file content
            text = open(os.path.join(path, item.text())).read() 
            self.txt.setText(text) # Show content in Bottom List

        for i in os.listdir(path):
            self.list.addItem(i) # Add file to a Top Window

        # Run updateText procedure when file is selected in Top Window
        self.list.itemClicked.connect(updateText) 

if __name__ == '__main__':
    app = QApplication([])
    widget = windowTree()

    widget.show()
    app.exec_()

Drag and drop basic setup

# Drag and drop events

from PySide.QtGui import *
from PySide.QtCore import *

class window_DD(QListWidget):
    def __init__(self):
        super(window_DD, self).__init__()
        self.setWindowFlags(Qt.WindowStaysOnTopHint) # Always on top
        self.setDragDropMode(QAbstractItemView.DropOnly) # Turn on DROP

    # Create proc for drag events (dropEvent + dragEnterEvent + dragMoveEvent)
    # Names fo procedures should be exactly like this
    def dropEvent(self, event):
        mimedata = event.mimeData() # Get drag content
        if mimedata.hasUrls(): # Check if content is FILES
            print 'DROP'
            for i in mimedata.urls():
                print i.toLocalFile()

    def dragEnterEvent(self, event):
        mimedata = event.mimeData() # Get drag content
        if mimedata.hasUrls(): # Accept event only if user drag FILES
            event.accept()
            print 'ENTER'
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        mimedata = event.mimeData() # Get drag content
        if mimedata.hasUrls(): # Accept event only if user drag FILES
            event.accept()
            print 'MOVE'
        else:
            event.ignore()


if __name__ == '__main__':
    app = QApplication([])
    widget = window_DD()

    widget.show()
    app.exec_()

Arnold rendering techniques

Arnold linear workflow for Maya 2016 ext 2 (2016.5)

Turn on color management in Maya settings, set all to 1.0 in Gamma Correction section of Arnold render setting.

Setup per object attributes

We can use tokens to set individual values for the same attribute on different objects. Tokens, in general, can be useful if you want to get some information from the shape of objects during the rendering process, e.g. set individual texture placement attributes with the same place2dTexure node, set a unique color for each object etc.

Great example of using tokens is Multitexture material

Multitexture material

You can reduce the number of shaders in the scene and speed up look development with one material and individual textures on each object. With such setup, object shapes share the same material but have their own textures.

Method:

  • Create string attribute on object shape with the name mtoa_constant_<attrName>
  • Enter attribute token < attr:<attrName> > in Image Name field of file node with full absolute path to texture.
    For example: D:/DNA/3D/sourceimages/<attr:mColor>
    It become relative immediatley (sourceimages/<attr:mColor>) but if you enter relative path from the beginning it would not work.
  • Enter desirable texture name in mtoa_constant_<attrName> attribute of each object shape.

You can create and set Arnold custom attributes on object shapes with Attribute Manager

Object and material AOVs

You can create a special path with selection mask of objects or shaders for compositing software.

Material AOV

To create AOV for any shader we use aiWriteColor node.

Object AOV

To create AOV for any object:

  • create a custom color attribute on the desired object
  • create AOVs in render settings
  • create aiUserDataColor and plug it in default shader slot of AOV

Create AOVs automatically with Render Manager

Kick ass with command line

Go with explorer to: C:\solidangle\mtoadeploy\2015.
Replace in address bar C:\Windows\System32\cmd.exe with cmd (it will run cmd window with proper path).
Enter kick.exe -i <path to *.ass file> -l C:\solidangle\mtoadeploy\2015\shaders and run.

Material creation techniques

In this section, we will explore how to make surfaces looks like particular materials — wood, metal, skin etc with Arnold aiStandard shader.

Lighting > Shading

Cinematography

Types of shots

List of shots by field size:

  • Extreme close-up
  • Close-up
  • Medium close-up
  • Medium
  • Full
  • Wide
  • Extreme wide

List of shots by placement:

  • Cut-in
  • Cutaway
  • Point of view
  • Areal
  • Over the shoulder
  • Reverse
  • Two shot

Framing rules

  • Rule of thirds
  • Empty space
  • Do not cut joints

Abstract thinking

Abstract > Specific

You can’t perform that action at this time.