Hi all,
I typed up an example for a friend and figured I’d share it with you all, the more the merrier.
Model publishing example
Here’s an example in which two extractors operate on models of two kinds; one for animation, and one optimised for playback/preview.
Interface
Assets are assumed to be located within an objectSet
with the following attributes attached:
bool: isAsset
string: family
These are of course completely arbitrary. Another common approach is to instead make the collector search for assets by convention. E.g. a model may reside within a top-level group with a “_MOD” suffix, and shaders may be all file
nodes and so forth.
Usage
Copy/paste/run contents of example.py
from your Script Editor, and then publish either via Python or GUI.
- See scene.ma for an example scene with 1 asset.
Publish with GUI
# Assumes you've `pip install pyblish-lite`
import pyblish_lite
pyblish_lite.show()
Publish without GUI
# util.py is a top-level convenience layer included with pyblish-base
# for publishing via Python. Alternative are pyblish-qml or pyblish-lite,
# which offer a graphical interface over the same API (see api.py).
import pyblish.util
context = pyblish.util.publish()
for result in context.data['results']:
for record in result['records']:
print(record.msg)
# Details on what's inside each `result` dictionary
# - http://api.pyblish.com/pages/result.html
# Output:
# Collecting instanceA..
# Collecting instanceB..
# Extracting obj..
# Extracting proxy..
# More formatting inspiration:
# - http://learn.pyblish.com/chapters/17-report-iii.html
example.py
This file contains the source plug-ins, and are intended to be copy/pasted into the Maya Script Editor and run. Once run, you can either create a series of assets yourself via the interface specified above, or try the example scene below.
'''Self-contained example of publishing a model from Autodesk Maya
Plug-ins
- CollectInstances: Generic collector, assuming configuration from Maya
- ExtractObj: Export an OBJ from instances of model.animation
- ExtractProxy: Export an Alembic GPU cache from instances of model.proxy
Families
- model.animation: Geometry with topology fit for animation
- model.proxy: Low resolution geometry fit for previewing
'''
import pyblish.api
class CollectInstances(pyblish.api.ContextPlugin):
'''This docstring is meant for the user.
Some of the GUIs pick this up and draw it nicely.
It should say things about what its purpose is,
how to influence the things that it finds - such
as configurable options.
'''
# Tell Pyblish to run this during collection
order = pyblish.api.CollectorOrder
def process(self, context):
'''This docstring isn't picked up by Pyblish
It can be used for developer documentation. It could
also reside at module-level.
The developer can put multiple plug-ins within the
same module. This may influence where to document things.
'''
# Keeping the import internal to the function enables use of
# a module across multiple applications (where relevant)
from maya import cmds
# In this example, the company has decided that any asset is
# identified by storing whichever nodes it relates to within
# an objectSet with a particular attribute and value.
# This objectSet then holds attributes related to publishing
# of this asset.
for objset in cmds.ls(type='objectSet', objectsOnly=True):
if not cmds.objExists(objset + '.isAsset'):
continue
self.log.info("Collecting %s.." % objset)
instance = context.create_instance(name=objset)
# Assume families is defined in Maya
for key in cmds.listAttr(objset, userDefined=True) or list():
instance.data[key] = cmds.getAttr(objset + '.' + key)
# Include contents of set in this instance for
# subsequent processing steps, like validation.
instance[:] = cmds.sets(objset, query=True)
class ExtractObj(pyblish.api.InstancePlugin):
'''Produce an OBJ of an instance'''
order = pyblish.api.ValidatorOrder
optional = True
families = ["model.animation"]
def process(self, instance):
# Implementation left to the reader
self.log.info("Extracting obj..")
class ExtractProxy(pyblish.api.InstancePlugin):
'''Produce a GPU Proxy of an instance'''
order = pyblish.api.ValidatorOrder
optional = False
families = ['model.proxy']
def process(self, instance):
# Implementation left to the reader
self.log.info('Extracting proxy..')
# Quickly test out a series of plug-ins and their interactions
# by registering them in-memory and run them as-is.
pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(ExtractObj)
pyblish.api.register_plugin(ExtractProxy)
example.ma
This example scene contains a single asset; a box in an objectSet and appropriate properites for the Collector to pick up.
//Maya ASCII 2016 scene
//Name: martin_example.ma
//Last modified: Tue, Aug 23, 2016 08:05:26 PM
//Codeset: 1252
requires maya "2016";
currentUnit -l centimeter -a degree -t film;
fileInfo "application" "maya";
fileInfo "product" "Maya 2016";
fileInfo "version" "2016";
fileInfo "cutIdentifier" "201511301000-979500";
fileInfo "osv" "Microsoft Windows 8 Enterprise Edition, 64-bit (Build 9200)\n";
createNode transform -s -n "persp";
rename -uid "52D8CE0F-4838-D7DE-AC7A-78B6595CEB3F";
setAttr ".v" no;
setAttr ".t" -type "double3" 2.5413039099508832 2.3053791446736431 4.5809494947684524 ;
setAttr ".r" -type "double3" -24.938352729602727 27.400000000000123 0 ;
createNode camera -s -n "perspShape" -p "persp";
rename -uid "1336AEB3-4662-946C-8442-7183C5AA657F";
setAttr -k off ".v" no;
setAttr ".fl" 34.999999999999993;
setAttr ".coi" 5.8296740112978593;
setAttr ".imn" -type "string" "persp";
setAttr ".den" -type "string" "persp_depth";
setAttr ".man" -type "string" "persp_mask";
setAttr ".hc" -type "string" "viewSet -p %camera";
createNode transform -s -n "top";
rename -uid "850049D9-4321-BB88-2C55-60A9E8273D11";
setAttr ".v" no;
setAttr ".t" -type "double3" 0 100.1 0 ;
setAttr ".r" -type "double3" -89.999999999999986 0 0 ;
createNode camera -s -n "topShape" -p "top";
rename -uid "FB4827FB-4E68-9603-CAAF-B3AA10C28FA6";
setAttr -k off ".v" no;
setAttr ".rnd" no;
setAttr ".coi" 100.1;
setAttr ".ow" 30;
setAttr ".imn" -type "string" "top";
setAttr ".den" -type "string" "top_depth";
setAttr ".man" -type "string" "top_mask";
setAttr ".hc" -type "string" "viewSet -t %camera";
setAttr ".o" yes;
createNode transform -s -n "front";
rename -uid "28C353AE-4FAE-6FCA-7137-6C9BCBBB7AC7";
setAttr ".v" no;
setAttr ".t" -type "double3" 0 0 100.1 ;
createNode camera -s -n "frontShape" -p "front";
rename -uid "9BDC1F39-4816-8D0B-950F-B69F2AC0C8FA";
setAttr -k off ".v" no;
setAttr ".rnd" no;
setAttr ".coi" 100.1;
setAttr ".ow" 30;
setAttr ".imn" -type "string" "front";
setAttr ".den" -type "string" "front_depth";
setAttr ".man" -type "string" "front_mask";
setAttr ".hc" -type "string" "viewSet -f %camera";
setAttr ".o" yes;
createNode transform -s -n "side";
rename -uid "7F572DE8-4763-322E-9717-EC963A8386A9";
setAttr ".v" no;
setAttr ".t" -type "double3" 100.1 0 0 ;
setAttr ".r" -type "double3" 0 89.999999999999986 0 ;
createNode camera -s -n "sideShape" -p "side";
rename -uid "50261B8F-409E-DBB0-2A5D-DF877BD6B6E1";
setAttr -k off ".v" no;
setAttr ".rnd" no;
setAttr ".coi" 100.1;
setAttr ".ow" 30;
setAttr ".imn" -type "string" "side";
setAttr ".den" -type "string" "side_depth";
setAttr ".man" -type "string" "side_mask";
setAttr ".hc" -type "string" "viewSet -s %camera";
setAttr ".o" yes;
createNode transform -n "pCube1";
rename -uid "24CD1333-42D4-9775-38D4-748787760323";
createNode mesh -n "pCubeShape1" -p "pCube1";
rename -uid "C2767B0A-4DB3-5907-8E07-12950E02C625";
setAttr -k off ".v";
setAttr ".vir" yes;
setAttr ".vif" yes;
setAttr ".uvst[0].uvsn" -type "string" "map1";
setAttr ".cuvs" -type "string" "map1";
setAttr ".dcc" -type "string" "Ambient+Diffuse";
setAttr ".covm[0]" 0 1 1;
setAttr ".cdvm[0]" 0 1 1;
setAttr ".vbc" no;
createNode lightLinker -s -n "lightLinker1";
rename -uid "B377EAE6-45CF-3F58-24F3-2FA225875F31";
setAttr -s 2 ".lnk";
setAttr -s 2 ".slnk";
createNode displayLayerManager -n "layerManager";
rename -uid "CA22F2E7-4BCC-882B-416B-3BBA3F47D064";
createNode displayLayer -n "defaultLayer";
rename -uid "E55803F2-475C-E15C-4A51-5CB5776A956C";
createNode renderLayerManager -n "renderLayerManager";
rename -uid "D4A6E0A1-4E95-B333-F594-B8A341E219DB";
createNode renderLayer -n "defaultRenderLayer";
rename -uid "5679E97D-4C54-58BA-C2A8-05BDDB82568E";
setAttr ".g" yes;
createNode polyCube -n "polyCube1";
rename -uid "FAD600AC-4F6A-0035-5466-E88CA250A6B8";
setAttr ".cuv" 4;
createNode objectSet -n "myModel_SET";
rename -uid "6EE3FCF0-47E2-9123-3649-1EB5E4A3C28C";
addAttr -ci true -sn "isAsset" -ln "isAsset" -min 0 -max 1 -at "bool";
addAttr -ci true -sn "family" -ln "family" -dt "string";
setAttr ".ihi" 0;
setAttr -k on ".isAsset" yes;
setAttr -k on ".family" -type "string" "model.animation";
createNode script -n "sceneConfigurationScriptNode";
rename -uid "0C0143F1-4B95-A91A-D2D9-9388B59919DE";
setAttr ".b" -type "string" "playbackOptions -min 1 -max 24 -ast 1 -aet 48 ";
setAttr ".st" 6;
createNode nodeGraphEditorInfo -n "MayaNodeEditorSavedTabsInfo";
rename -uid "9FA5D44E-4D68-C3E8-605A-F8B7A4AEF6FF";
setAttr ".pee" yes;
setAttr ".tgi[0].tn" -type "string" "Untitled_1";
setAttr ".tgi[0].vl" -type "double2" -329.76189165834455 -91.666663024160755 ;
setAttr ".tgi[0].vh" -type "double2" 197.61903976637254 234.52380020467101 ;
select -ne :time1;
setAttr ".o" 1;
setAttr ".unw" 1;
select -ne :hardwareRenderingGlobals;
setAttr ".otfna" -type "stringArray" 22 "NURBS Curves" "NURBS Surfaces" "Polygons" "Subdiv Surface" "Particles" "Particle Instance" "Fluids" "Strokes" "Image Planes" "UI" "Lights" "Cameras" "Locators" "Joints" "IK Handles" "Deformers" "Motion Trails" "Components" "Hair Systems" "Follicles" "Misc. UI" "Ornaments" ;
setAttr ".otfva" -type "Int32Array" 22 0 1 1 1 1 1
1 1 1 0 0 0 0 0 0 0 0 0
0 0 0 0 ;
setAttr ".fprt" yes;
select -ne :renderPartition;
setAttr -s 2 ".st";
select -ne :renderGlobalsList1;
select -ne :defaultShaderList1;
setAttr -s 4 ".s";
select -ne :postProcessList1;
setAttr -s 2 ".p";
select -ne :defaultRenderingList1;
select -ne :initialShadingGroup;
setAttr ".ro" yes;
select -ne :initialParticleSE;
setAttr ".ro" yes;
select -ne :defaultResolution;
setAttr ".pa" 1;
select -ne :hardwareRenderGlobals;
setAttr ".ctrs" 256;
setAttr ".btrs" 512;
connectAttr "polyCube1.out" "pCubeShape1.i";
relationship "link" ":lightLinker1" ":initialShadingGroup.message" ":defaultLightSet.message";
relationship "link" ":lightLinker1" ":initialParticleSE.message" ":defaultLightSet.message";
relationship "shadowLink" ":lightLinker1" ":initialShadingGroup.message" ":defaultLightSet.message";
relationship "shadowLink" ":lightLinker1" ":initialParticleSE.message" ":defaultLightSet.message";
connectAttr "layerManager.dli[0]" "defaultLayer.id";
connectAttr "renderLayerManager.rlmi[0]" "defaultRenderLayer.rlid";
connectAttr "pCube1.iog" "myModel_SET.dsm" -na;
connectAttr "defaultRenderLayer.msg" ":defaultRenderingList1.r" -na;
connectAttr "pCubeShape1.iog" ":initialShadingGroup.dsm" -na;
// End of martin_example.ma
Enjoy!