alembic example - shotgunsoftware/tk-multi-publish GitHub Wiki
To publish Alembic Cache files from Maya, make sure that the AbcExport
plug-in is loaded in the plug-in manager. You`ll also need a Shot scene with some grouped meshes ready to be published.
First, install the tk-multi-publish
app for the Shot_Step
environment:
LINKBOX_APP:shotgunsoftware/tk-multi-publish:See the Multi Publish installation instructions for more information.
As well as adding the app to the environment, this will also download it from the app store and ensure you are running the latest version.
Once installed, open the Shot_Step
environment file, typically found at <pipeline_configuration_root>/config/env/Shot_Step.yml
and locate the entry for the tk-multi-publish
app under the tk-maya
engine that you want to modify:
engines:
tk-maya:
apps:
tk-multi-publish:
...
Tip: If you want to experiment with a new configuration for an app you can duplicate the entry in the environment and call it something different. Many apps also support a separate display name to help differentiate them in the main Menu.
For this example, modify the app name and display name to be tk-multi-publish-alembic-example
and Publish Alembic Example
respectively. The initial configuration should now look like:
tk-multi-publish-alembic-example:
allow_taskless_publishes: true
display_name: Publish Alembic Example
expand_single_items: false
hook_copy_file: default
hook_post_publish: default
hook_primary_pre_publish: default
hook_primary_publish: default
hook_scan_scene: default
hook_secondary_pre_publish: default
hook_secondary_publish: default
hook_thumbnail: default
location: {name: tk-multi-publish, type: app_store, version: v0.2.7}
primary_description: Publish and version up the current work file
primary_display_name: Current Work File
primary_icon: ''
primary_publish_template: maya_shot_publish
primary_scene_item_type: work_file
primary_tank_type: Maya Scene
secondary_outputs: []
template_work: maya_shot_work
You'll be changing some of these settings but for the full reference take a look at the Multi Publish documentation.
Save the config and then reload the engines and apps in Maya. You'll now have a new Publish Alembic Example
entry in the main menu!
The first thing the app does is scan the scene for things to publish. To do this, it uses the hook specified by the hook_scan_scene
setting. The default implementation just looks for the scene itself, so using this as a starting point, you are going to extend it to additionally find grouped geometry from the scene.
LINKBOX_DOC:11:Read this doc for full instructions on how to work with hooks.
To quickly summarize, you need to do the following to take the default hook as a starting point for this implementation:
- Locate the default implementation - this can be found in
<code_root>/install/apps/app_store/tk-multi-publish/<version>/hooks/scan_scene_tk-maya.py
. - Copy this hook to the hooks directory in your current pipeline configuration, e.g.
<pc_root>/config/hooks
- Rename it to something unique, e.g.
alembic_example_scan_scene.py
- Update the
hook_scan_scene
setting to use this hook:hook_scan_scene: alembic_example_scan_scene
The app will now use your version of the scan scene hook so go ahead and open it up.
The hook returns a list of items that it finds from the scene - the default implementation just finds the scene itself so let's extend it so that it also finds all top level groups that contain one or more meshes. To do this, add the following code at the end of the execute method:
...
# look for root level groups that have meshes as children:
for grp in cmds.ls(assemblies=True, long=True):
if cmds.ls(grp, dag=True, type="mesh"):
# include this group as a 'mesh_group' type
items.append({"type":"mesh_group", "name":grp})
...
You'll notice that when it adds an item to the list, it specifies the type: mesh_group
:
items.append({"type":"mesh_group", "name":grp})
You'll see in a minute how this type
is used by the app to associate outputs with each item - remember this as it's quite important!
---FOLD---
This is the complete version of alembic_example_scan_scene.py
import os
import maya.cmds as cmds
import tank
from tank import Hook
from tank import TankError
class ScanSceneHook(Hook):
"""
Hook to scan scene for items to publish
"""
def execute(self, **kwargs):
"""
Main hook entry point
:returns: A list of any items that were found to be published.
Each item in the list should be a dictionary containing
the following keys:
{
type: String
This should match a scene_item_type defined in
one of the outputs in the configuration and is
used to determine the outputs that should be
published for the item
name: String
Name to use for the item in the UI
description: String
Description of the item to use in the UI
selected: Bool
Initial selected state of item in the UI.
Items are selected by default.
required: Bool
Required state of item in the UI. If True then
item will not be deselectable. Items are not
required by default.
other_params: Dictionary
Optional dictionary that will be passed to the
pre-publish and publish hooks
}
"""
items = []
# get the main scene:
scene_name = cmds.file(query=True, sn=True)
if not scene_name:
raise TankError("Please Save your file before Publishing")
scene_path = os.path.abspath(scene_name)
name = os.path.basename(scene_path)
# create the primary item - this will match the primary output 'scene_item_type':
items.append({"type": "work_file", "name": name})
# look for root level groups that have meshes as children:
for grp in cmds.ls(assemblies=True, long=True):
if cmds.ls(grp, dag=True, type="mesh"):
# include this group as a 'mesh_group' type
items.append({"type":"mesh_group", "name":grp})
return items
---FOLD---
Now you've told the app how to find mesh groups in the scene, the next step is to tell it what outputs are available for these groups...
The Multi Publish app has two types of output:
-
Primary
- Typically the Primary output is used to represent your scene file - there can only be a single Primary output. -
Secondary
- Any additional things to be published are represented as Secondary outputs. You can define as many of these as you need.
These are represented in different areas of the main interface:
This example defines a new Alembic Cache secondary output for all top-level mesh groups within the scene. To do this, edit the secondary_outputs
setting in your tk-multi-publish-alembic
configuration.
Lets fill in some details and then we'll walk through the important bits:
...
secondary_outputs:
- description: 'Publish Alembic Cache data for the model'
display_group: Caches
display_name: Alembic Cache
icon: 'icons/alembic_output.png'
name: alembic_cache
publish_template: maya_shot_mesh_alembic_cache
required: false
scene_item_type: mesh_group
selected: false
tank_type: 'Alembic Cache'
...
Most of these are self explanatory but for full details please see the Multi Publish reference documentation.
The settings you need to care about for this example are:
name: alembic_cache
The name should be unique across all outputs and can't be primary
as this is reserved for the primary output. You'll use this name used later when you are doing the actual publishing...
scene_item_type: mesh_group
Remember the type returned for each item in the scan scene hook back in Step 2? Setting the scene_item_type
in the output to the same type tells the app that for all items of type mesh_group
you want to publish an Alembic Cache
output for it.
Tip: This mechanism allows the association of multiple different outputs with a single item type so if you want to add another output later, for example OBJ export, you can do this without having to modify the scan hook again.
publish_template: maya_shot_mesh_alembic_cache
This setting allows you to specify a template to be used for the published Alembic Cache files. You'll have access to this when you do the publish.
LINKBOX_DOC:4:Take a look at this doc for lots of useful information on how to configure templates.
For this example, the following template has been used:
# excerpt from templates.yml
keys:
...
grp_name:
type: str
paths:
...
maya_shot_mesh_alembic_cache: '@shot_root/publish/maya/{name}[_{grp_name}].v{version}.abc'
If you are using the default config you can add this to your templates.yml
file. Otherwise you may have to tweak the template and/or hooks to work correctly.
tank_type: Alembic Cache
This is the unique publish type that will be used when registering your Alembic Cache files in Shotgun. Again, you'll have access to this when you do the publish.
icon: 'icons/alembic_output.png'
This is the icon the app will use to represent outputs of this new type. Save this icon:
to your configuration's 'icons' directory, e.g. <pc_root>/config/icons/alembic_output.png
Lets see what this looks like. Save the config, reload the app in Maya, open up your test Shot scene and run the Publish Alembic Example
command from the menu. If everything has gone to plan, you will now find some Secondary Publishes listed in the UI!
Don't click Publish just yet as you still need to add the code that exports the Alembic Cache files and registers them with Shotgun.
Publishing is split into three stages:
- Pre-Publish
- Publish
- Post-Publish.
The first of these, Pre-Publish, allows you to do any validation of the items to be published and report any issues back to the user.
As with scanning the scene, Pre-Publish is implemented via a hook which is defined in the app configuration by the hook_secondary_pre_publish
setting.
Follow the instructions in Step 2
to copy the default hook <code_root>/install/apps/app_store/tk-multi-publish/<version>/hooks/secondary_pre_publish_tk-maya.py
to your configuration hooks directory and rename it to alembic_example_secondary_pre_publish.py
.
Then edit the setting in the configuration to:
hook_secondary_pre_publish: alembic_example_secondary_pre_publish
The app will now use your version of the Pre-Publish hook so go ahead and open it up.
The hook takes a list of tasks as it's main input:
def execute(self, tasks, work_template, progress_cb, **kwargs):
...
This list is based on the users selection from the UI so it only contains things they have selected to be published:
Each task is a dictionary containing an item
and an output
.
- The item is the one returned from the scan scene hook back in Step 2.
- The output contains the name, publish_template and tank_type you configured for the Secondary output in Step 3.
The default implementation for this hook just provides a scaffold to iterate over all the tasks selected by the user and reports an error if it doesn't know how to handle an output.
The user might have deleted or modified the group returned by the scan scene hook so this implementation needs to be extended to make sure they haven't and that everything is ready to be published.
To do this, first replace:
# pre-publish item here, e.g.
#if output["name"] == "foo":
# ...
#else:
# don't know how to publish this output types!
errors.append("Don't know how to publish this item!")
with
# pre-publish alembic_cache output
if output["name"] == "alembic_cache":
errors.extend(self._validate_item_for_alembic_cache_publish(item))
else:
# don't know how to publish this output types!
errors.append("Don't know how to publish this item!")
Then add a new validation method to the PrePublishHook
class:
def _validate_item_for_alembic_cache_publish(self, item):
"""
Validate that the item is valid to be exported
to an alembic cache
"""
errors = []
# check that the group still exists:
if not cmds.objExists(item["name"]):
errors.append("This group couldn't be found in the scene!")
# and that it still contains meshes:
elif not cmds.ls(item["name"], dag=True, type="mesh"):
errors.append("This group doesn't appear to contain any meshes!")
# finally return any errors
return errors
Any problems it finds are returned so that the user is given an opportunity to fix them before publishing. You can of course extend this to do much more comprehensive validation of the items.
---FOLD---
This is the complete version of alembic_example_secondary_pre_publish.py
import os
import maya.cmds as cmds
import tank
from tank import Hook
from tank import TankError
class PrePublishHook(Hook):
"""
Single hook that implements pre-publish functionality
"""
def execute(self, tasks, work_template, progress_cb, **kwargs):
"""
Main hook entry point
:tasks: List of tasks to be pre-published. Each task is be a
dictionary containing the following keys:
{
item: Dictionary
This is the item returned by the scan hook
{
name: String
description: String
type: String
other_params: Dictionary
}
output: Dictionary
This is the output as defined in the configuration - the
primary output will always be named 'primary'
{
name: String
publish_template: template
tank_type: String
}
}
:work_template: template
This is the template defined in the config that
represents the current work file
:progress_cb: Function
A progress callback to log progress during pre-publish. Call:
progress_cb(percentage, msg)
to report progress to the UI
:returns: A list of any tasks that were found which have problems that
need to be reported in the UI. Each item in the list should
be a dictionary containing the following keys:
{
task: Dictionary
This is the task that was passed into the hook and
should not be modified
{
item:...
output:...
}
errors: List
A list of error messages (strings) to report
}
"""
results = []
# validate tasks:
for task in tasks:
item = task["item"]
output = task["output"]
errors = []
# report progress:
progress_cb(0, "Validating", task)
# pre-publish alembic_cache output
if output["name"] == "alembic_cache":
errors.extend(self._validate_item_for_alembic_cache_publish(item))
else:
# don't know how to publish this output types!
errors.append("Don't know how to publish this item!")
# if there is anything to report then add to result
if len(errors) > 0:
# add result:
results.append({"task":task, "errors":errors})
progress_cb(100)
return results
def _validate_item_for_alembic_cache_publish(self, item):
"""
Validate that the item is valid to be exported
to an alembic cache
"""
errors = []
# check that the group still exists:
if not cmds.objExists(item["name"]):
errors.append("This group couldn't be found in the scene!")
# and that it still contains meshes:
elif not cmds.ls(item["name"], dag=True, type="mesh"):
errors.append("This group doesn't appear to contain any meshes!")
# finally return any errors
return errors
---FOLD---
The Publish stage is where you are going to export an Alembic Cache file to the publish path for each item and then register them with Shotgun. For this example, this is also the final thing you need to do.
This stage is also implemented via a hook which is defined in the app configuration by the hook_secondary_publish
setting.
Again, follow the previous instructions to copy the default hook <code_root>/install/apps/app_store/tk-multi-publish/<version>/hooks/secondary_publish_tk-maya.py
to your configuration hooks directory and rename it to alembic_example_secondary_publish.py
. Then edit the setting in the configuration to:
hook_secondary_publish: alembic_example_secondary_publish
The app will now use your version of the Publish hook so go ahead and open it up.
The Publish hook is also provided a list of tasks together with a few more inputs:
def execute(self, tasks, work_template, comment, thumbnail_path,
sg_task, primary_publish_path, progress_cb, **kwargs):
...
- The
comment
,thumbnail_path
andsg_task
represent the information provided by the user through the UI. - The
primary_publish_path
is the path of the primary publish, in this case the scene file itself.
As with the Pre-Publish hook, the default implementation is just a scaffold for iterating over the tasks so the first thing to do is add some code so that it can handle the new alembic_cache
output.
To do this, replace:
# publish item here, e.g.
#if output["name"] == "foo":
# ...
#else:
# don't know how to publish this output types!
errors.append("Don't know how to publish this item!")
with:
# publish alembic_cache output
if output["name"] == "alembic_cache":
try:
self._publish_alembic_cache_for_item(item, output, work_template, primary_publish_path,
sg_task, comment, thumbnail_path, progress_cb)
except Exception, e:
errors.append("Publish failed - %s" % e)
else:
# don't know how to publish this output types!
errors.append("Don't know how to publish this item!")
Next, add a new publish method to the PublishHook
class:
def _publish_alembic_cache_for_item(self, item, output, work_template, primary_publish_path,
sg_task, comment, thumbnail_path, progress_cb)
...
Lets look at what this new method does.
First, it uses the work_template to extract a dictionary of fields from the current scene path:
scene_path = os.path.abspath(cmds.file(query=True, sn=True))
fields = work_template.get_fields(scene_path)
publish_version = fields["version"]
Next, it adds the group name to the fields and then builds the publish path to use for the Alembic Cache file:
fields["grp_name"] = group_name
publish_path = publish_template.apply_fields(fields)
Using this path, it then uses the Alembic Cache export command AbcExport
to export the group to the publish path:
frame_start = int(cmds.playbackOptions(q=True, min=True))
frame_end = int(cmds.playbackOptions(q=True, max=True))
abc_publish_path = publish_path.replace("\\", "/")
abc_export_cmd = ("AbcExport -j \"-fr %d %d -root %s -file %s\""
% (frame_start, frame_end, item["name"], abc_publish_path))
try:
self.parent.log_debug("Executing command: %s" % abc_export_cmd)
mel.eval(abc_export_cmd)
except Exception, e:
raise TankError("Failed to export Alembic Cache: %s" % e)
Finally, it registers the published Alembic Cache file with Shotgun:
self._register_publish(publish_path, ...
And that's it!
---FOLD---
This is the complete alembic_example_secondary_publish.py
import os
import shutil
import maya.cmds as cmds
import maya.mel as mel
import tank
from tank import Hook
from tank import TankError
class PublishHook(Hook):
"""
Single hook that implements publish functionality for secondary tasks
"""
def execute(self, tasks, work_template, comment, thumbnail_path, sg_task, primary_publish_path, progress_cb, **kwargs):
"""
Main hook entry point
:tasks: List of secondary tasks to be published. Each task is a
dictionary containing the following keys:
{
item: Dictionary
This is the item returned by the scan hook
{
name: String
description: String
type: String
other_params: Dictionary
}
output: Dictionary
This is the output as defined in the configuration - the
primary output will always be named 'primary'
{
name: String
publish_template: template
tank_type: String
}
}
:work_template: template
This is the template defined in the config that
represents the current work file
:comment: String
The comment provided for the publish
:thumbnail: Path string
The default thumbnail provided for the publish
:sg_task: Dictionary (shotgun entity description)
The shotgun task to use for the publish
:primary_publish_path: Path string
This is the path of the primary published file as returned
by the primary publish hook
:progress_cb: Function
A progress callback to log progress during pre-publish. Call:
progress_cb(percentage, msg)
to report progress to the UI
:returns: A list of any tasks that had problems that need to be reported
in the UI. Each item in the list should be a dictionary containing
the following keys:
{
task: Dictionary
This is the task that was passed into the hook and
should not be modified
{
item:...
output:...
}
errors: List
A list of error messages (strings) to report
}
"""
results = []
# publish all tasks:
for task in tasks:
item = task["item"]
output = task["output"]
errors = []
# report progress:
progress_cb(0, "Publishing", task)
# publish alembic_cache output
if output["name"] == "alembic_cache":
try:
self._publish_alembic_cache_for_item(item, output, work_template, primary_publish_path, sg_task, comment, thumbnail_path, progress_cb)
except Exception, e:
errors.append("Publish failed - %s" % e)
else:
# don't know how to publish this output types!
errors.append("Don't know how to publish this item!")
# if there is anything to report then add to result
if len(errors) > 0:
# add result:
results.append({"task":task, "errors":errors})
progress_cb(100)
return results
def _publish_alembic_cache_for_item(self, item, output, work_template, primary_publish_path, sg_task, comment, thumbnail_path, progress_cb):
"""
Export an Alembic cache for the specified item and publish it
to Shotgun.
"""
group_name = item["name"].strip("|")
tank_type = output["tank_type"]
publish_template = output["publish_template"]
# get the current scene path and extract fields from it
# using the work template:
scene_path = os.path.abspath(cmds.file(query=True, sn=True))
fields = work_template.get_fields(scene_path)
publish_version = fields["version"]
# update fields with the group name:
fields["grp_name"] = group_name
# create the publish path by applying the fields
# with the publish template:
publish_path = publish_template.apply_fields(fields)
# build and execute the Alembic export command for this item:
frame_start = int(cmds.playbackOptions(q=True, min=True))
frame_end = int(cmds.playbackOptions(q=True, max=True))
# The AbcExport command expects forward slashes!
abc_publish_path = publish_path.replace("\\", "/")
abc_export_cmd = "AbcExport -j \"-fr %d %d -root %s -file %s\"" % (frame_start, frame_end, item["name"], abc_publish_path)
try:
self.parent.log_debug("Executing command: %s" % abc_export_cmd)
mel.eval(abc_export_cmd)
except Exception, e:
raise TankError("Failed to export Alembic Cache: %s" % e)
# Finally, register this publish with Shotgun
self._register_publish(publish_path,
group_name,
sg_task,
publish_version,
tank_type,
comment,
thumbnail_path,
[primary_publish_path])
def _register_publish(self, path, name, sg_task, publish_version, tank_type, comment, thumbnail_path, dependency_paths=None):
"""
Helper method to register publish using the
specified publish info.
"""
# construct args:
args = {
"tk": self.parent.tank,
"context": self.parent.context,
"comment": comment,
"path": path,
"name": name,
"version_number": publish_version,
"thumbnail_path": thumbnail_path,
"task": sg_task,
"dependency_paths": dependency_paths,
"published_file_type":tank_type,
}
# register publish;
sg_data = tank.util.register_publish(**args)
return sg_data
---FOLD---
---FOLD---
This is the complete tk-multi-publish-alembic-example
configuration in the Shot_Step
environment.
tk-multi-publish-alembic-example:
allow_taskless_publishes: true
display_name: Publish Alembic Example
expand_single_items: false
hook_copy_file: default
hook_post_publish: default
hook_primary_pre_publish: default
hook_primary_publish: default
hook_scan_scene: alembic_example_scan_scene
hook_secondary_pre_publish: alembic_example_secondary_pre_publish
hook_secondary_publish: alembic_example_secondary_publish
hook_thumbnail: default
location: {name: tk-multi-publish, type: app_store, version: v0.2.7}
primary_description: Publish and version up the current work file
primary_display_name: Current Work File
primary_icon: ''
primary_publish_template: maya_shot_publish
primary_scene_item_type: work_file
primary_tank_type: Maya Scene
secondary_outputs:
- description: 'Publish Alembic Cache data for the model'
display_group: Caches
display_name: Alembic Cache
icon: 'icons/alembic_output.png'
name: alembic_cache
publish_template: maya_shot_mesh_alembic_cache
required: false
scene_item_type: mesh_group
selected: false
tank_type: 'Alembic Cache'
template_work: maya_shot_work
---FOLD---
Everything is now setup to publish Alembic Caches for referenced Assets in a Shot scene!
Save the config, reload the app in Maya, open up your test Shot scene and run the Publish Alembic Example
command from the menu.
Make sure the Alembic Cache items found by your custom scan hook are selected in the UI, add a comment and hit Publish
. The app will first validate that the references are valid using your new pre publish hook. If everything is ok it will then export Alembic Caches and register them as Shotgun published files using your new publish hook!