Publishing example with Maya


#1

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!