-
Notifications
You must be signed in to change notification settings - Fork 31
06 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
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
Intro | PyMel basics | Developing example | Art of good code | Classes
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.
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
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.
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
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.
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
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.
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.
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.
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.
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.
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 scope = correctness*3 + design*2 + style
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)
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.
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.
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
: replacehtml_theme = 'alabaster'
line with:
import sphinx_rtd_theme
html_theme = "sphinx_rtd_theme"
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
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>
Here you will find procedures for most common tasks in Maya.
General | Attributes | Objects | Files | Lists | Strings | Rendering | Interfaces | FTrack
import pymel.core as pm
nodeCheck = pm.PyNode('<nodeName>')
for i in dir(nodeCheck):
print i
listAtr = pm.ls('*.nameOfAttribute')
for i in listAtr:
object = i.node()
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')
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 )
def setString(attribute, value):
sel = pm.ls(sl=1)
for i in sel:
i.attr(attribute).set(value)
def attrLock(attribute):
sel = pm.ls(sl=1)
for i in sel:
i.attr(attribute).lock()
listShapes = pm.ls(dag=1,o=1,s=1,sl=1)
object = pm.PyNode('nameOfNode')
def unlockAndDelete():
sel = pm.ls( sl = True )
for i in sel:
i.unlock()
pm.delete(i)
def selectInstances():
pm.select(pm.ls(ap = 1, dag = 1, sl = 1 ))
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)) )
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)
pm.sceneName() # Get name of current scene
pm.importFile(fullPath) # Import file
pm.exportSelected(fullPath) # Export selected to a file
import glob
listExisted = glob.glob('D:/DNA/images/*.jpg')
pm.createReference(fullPath, sharedNodes =('shadingNetworks', 'displayLayers', 'renderLayersByName') , ns = nameSpace )
fullPathRef = pm.FileReference(namespace = 'nameSpace')
fullPathRef.replaceWith(fullPathRefNew)
from maya.mel import eval
eval('AbcExport -j " -framerange {2} {3} -uvWrite -root {0} -file {1}"'.format(groupName, pathABC, frameStart, frameEnd))
from maya.mel import eval
eval(' AbcImport -ct "{0}" "{1}" '.format(groupName, pathABC))
import json
dataFile = 'D:/DNA/datFile.json'
dataTransfer = 'Information to write to a file'
json.dump(dataTransfer, open(dataFile, 'w'), indent = 4) # write
import json
dataFile = 'D:/DNA/datFile.json'
dataTransfer = json.load(open(dataFile))
list = pm.ls('nodeName') # With exact name
list = pm.ls('prefix_*') # With exact part
list = pm.ls('*:nodeName') # With namespaces
list = ['A', 'B', 'C','D','E']
listLast = list.pop(-1)
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[::-1] # reverse list
a[start:end:step] # start through not past end, by step
dataShotDic = {'characters' : [ ], 'props' : [] , 'environment' : [] , 'EDA' : [] , 'endFrame' : []}
dataShotDic['characters'].append( <assetName> )
'variable A = {0}, variable B = {1}'.format(variable_A, variable_B)
print '{1:03d} {0:03d}'.format(1,2) # result: 002 001
print '{0:.2f}'.format(0.12345678) # result: 0.12
string = 'D:/projects/DNA/3D/scenes'
split = string.split('/')
# Result: ['D:', 'projects', 'DNA', '3D', 'scenes']
def setArnold(*args):
if( pm.getAttr( 'defaultRenderGlobals.currentRenderer' ) != 'arnold' ):
pm.setAttr('defaultRenderGlobals.currentRenderer', 'arnold')
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)
import mtoa.aovs as aovs
def addAOV(aovName):
aov = aovs.AOVInterface().addAOV(aovName)
return aov
# use `s = True` for selected
pm.arnoldExportAss( f = "D:/fileName.ass", startFrame = 0, endFrame = 1 )
def matAsign(material):
sel = pm.ls(sl=1)
for i in sel:
pm.sets(material, forceElement = i)
def getObjects(materialName):
material = pm.ls(materialName)
SG = pm.listConnections(materialName, type='shadingEngine')
listObjects = pm.listConnections(SG, type='shape')
return listObjects
def getShading(objectShape):
shadingGroup = pm.listConnections(objectShape, type='shadingEngine')
shader = pm.ls(mc.listConnections(shadingGroup), materials = 1)
return shader, shadingGroup
def getMat():
SG = pm.ls( sl = 1, type = 'shadingEngine' )
materials = pm.ls( pm.listConnections(SG),materials=1 )
return materials
imageFiles = pm.listHistory(shadingGroup, type='file')
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)
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
# 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)
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 )
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()
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!')
See FTrack setup and glossary of codes sections in documentation. Used API is 3.3.1
import os
os.environ['PYTHONPATH'] = 'C:/FTrack_API'
import os
os.environ['FTRACK_SERVER'] = 'https://<userAdress>.ftrackapp.com'
os.environ['FTRACK_APIKEY'] = '<API_key>'
os.environ['LOGNAME'] = '<userName>'
import ftrack
projectFTrack = ftrack.getProject(<codeProject>)
import ftrack
dataAsset = projectFTrack.getAssetBuilds().find('name', <assetName>)
dataAssetMeta = dataAsset.getMeta() # Get asset metadata
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
for i in listShotLinks :
assetName = i.getName()
assetCategory = i.getType().getName() # Environments, props, characters
import ftrack
assetData = projectFTrack.getAssetBuilds().find('name', <assetName>)
assetData.setMeta( <key>, <value> )
import ftrack
shotData = ftrack.getShotFromPath([<codeProject>,<codePart>,<codeSequence>,<codeShot>])
shotData.setMeta( <key>, <value> )
import os
os.environ['PYTHONPATH'] = 'C:/ShotGun_API'
import shotgun_api3
import sys
sys.path.append('C:/ShotGun_API')
import shotgun_api3
SERVER_PATH = "https://dna.shotgunstudio.com"
SCRIPT_NAME = 'dna'
SCRIPT_KEY = '06300256ffc46ac346d47ab9942c218b291939b0f131276d1d13c3fd554ec000'
sg = shotgun_api3.Shotgun(SERVER_PATH, SCRIPT_NAME, SCRIPT_KEY)
sg = shotgun_api3.Shotgun("https://xxx.com", login="xxx", password="xxx")
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']])
dataShot = {'project': project, 'sg_sequence': sequence, 'code':'SHOT_010' }
addShot = sg.create('Shot', dataShot) # Create a shot
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
data = { 'description': 'Hello, world!', 'sg_status_list': 'ip' }
result = sg.update('Shot', shot['id'], data)
filters = [['shot','is', {'type':'Shot','id':1169}]]
fields = ['asset','shot','sg_continuity_note']
linkedAssets = sg.find('AssetShotConnection',filters, fields)
getAssets = sg.find( "Asset", [( 'project.Project.name', 'is', 'FUTURAMA' )], [ 'code' ])
getAssetNames = sorted([asset.get('code', "(no name)") for asset in getAssets])
asset = sg.find("Asset", [["project", "is", project], ["code", "is", 'BENDER']])
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' ]])
data = {
'project': {'type':'Project', 'id':106},
'content': 'Color',
'entity': {'type':'Shot', 'id':shot['id']}
}
result = sg.create('Task', data)
task = sg.find('Task', [['id', 'is', 8082 ]])
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)
data = { 'sg_description': 'Hello, WORLD!', 'sg_internal_rounds': 0 }
result = sg.update('Task', 8481, data)
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'] )
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
sg.upload_thumbnail('Shot', 1234, 'C:/XXX/pamela_01.JPG')
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)
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)
dataCreate = { 'code': 'GROUP_ART', 'users': [ { "type": "HumanUser", "id": 86 },{ "type": "HumanUser", "id": 225 } ] }
grpCreate = sg.create('Group', dataCreate)
sg_group = sg.find('Group', [['code', 'is', 'GROUP_ART' ]], ['users'])
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)
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']})
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.
Download an place Shotgun Python API on your HDD: C:/shotgunAPI
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
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()
import nuke
- Create
usrlocal.pth
inC:\Python27\Lib\site-packages
- Add
C:\Program Files\Nuke 9.0v7\lib\site-packages
line to ausrlocal.pth
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' )
selectedNode = nuke.selectedNode()
node = nuke.toNode( '<nodeName>' )
print node.knob('file').value()
cam = nuke.toNode('<cameraName>')
print cam['xpos'].value()
nuke.tprint('<< HELLO {}! >>'.format('WORLD'))
# 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
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)
Run command prompt, enter: pip install PySide
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.
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'
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_()
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_()
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_()
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_()
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_()
# 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 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_()
Turn on color management in Maya settings, set all to 1.0 in Gamma Correction section of Arnold render setting.
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
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.
- 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
You can create a special path with selection mask of objects or shaders for compositing software.
To create AOV for any shader we use aiWriteColor node.
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
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.
In this section, we will explore how to make surfaces looks like particular materials — wood, metal, skin etc with Arnold aiStandard shader.
Lighting > Shading
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
- Rule of thirds
- Empty space
- Do not cut joints