![]() |
从 Maya 发布 Alembic本教程介绍了如何配置多发布,以在从 Maya 发布多组几何体时导出 Alembic 文件。 |
准备工作
要从 Maya 发布 Alembic 缓存文件,请确保已在插件管理器中加载 AbcExport
插件。您还需要一个镜头场景,里面有一些准备发布的分组网格。

第 1 步 - 准备环境
首先,为 Shot_Step
环境安装 tk-multi-publish
应用:
除了将应用添加到环境以外,此操作还将从应用商店下载该应用,确保您运行的是最新版本。
安装完成后,打开 Shot_Step
环境文件(通常位于 <pipeline_configuration_root>/config/env/Shot_Step.yml
),并在要修改的 tk-maya
插件下找到 tk-multi-publish
应用的条目:
engines: tk-maya: apps: tk-multi-publish: ...
提示:如果您想为应用试用新配置,可以在环境中复制该条目,将其改为不同的名称。许多应用还支持单独的显示名称,以帮助您在主菜单中区分它们。
在此示例中,分别将应用的名称和显示名称修改为 tk-multi-publish-alembic-example
和 Publish Alembic Example
。初始配置现在应如下所示:
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
您要更改其中一些设置,有关完整的参考,请参见多发布的文档。
保存配置,然后在 Maya 中重新加载插件和应用。现在,您的主菜单中将有一个新的 Publish Alembic Example
菜单项!
第 2 步 - 扫描场景
应用要做的第一件事是扫描场景中要发布的内容。它将使用由 hook_scan_scene
设置指定的挂钩来完成此操作。默认执行仅查找场景本身,因此您要在此基础上扩大查找范围,另外再查找场景中的分组几何体。
简单来说,您需要执行以下操作,以默认挂钩作为此执行的基础:
- 找到默认执行 - 此执行位于
<code_root>/install/apps/app_store/tk-multi-publish/<version>/hooks/scan_scene_tk-maya.py
中。 - 将此挂钩复制到当前工作流配置中的 hooks 目录,例如:
<pc_root>/config/hooks
- 将它重命名为具有唯一性的名称,例如
alembic_example_scan_scene.py
- 将
hook_scan_scene
设置更新为使用此挂钩:hook_scan_scene: alembic_example_scan_scene
现在,应用将使用您自己的“扫描场景”挂钩,接下来继续打开它。
挂钩会返回从场景中找到的各个项的列表 - 默认执行仅查找场景本身,因此我们要扩大查找范围,让它也查找所有包含一个或多个网格的顶级组。为此,请将以下代码添加到执行方法的结尾处:
... # 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}) ...
您会发现,在向列表中添加内容项时,会指定类型 mesh_group
:
items.append({"type":"mesh_group", "name":grp})
然后,您就能看到应用如何使用此 type
将输出与每个项相关联 - 记住这一点非常重要!
这是 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
现在,您已告诉应用如何查找场景中的网格组,下一步是告诉应用哪些输出可用于这些组...
第 3 步 - 定义输出
多发布应用有两种输出:
Primary
- 这是主输出,通常用于表示场景文件 - 主输出只能有一个。Secondary
- 这是辅助输出,要发布的其他任何内容都表示为这种输出。您可以根据需要定义任意多个辅助输出。
这些输出显示在主界面的不同区域:

下面的示例为场景内所有顶级网格组定义一个新的 Alembic 缓存辅助输出。要执行此操作,请编辑 tk-multi-publish-alembic
配置中的 secondary_outputs
设置。
我们先来补充一些细节,然后再逐个讲解要点:
... 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' ...
上面大部分设置的意义一目了然,但是如果需要完整详情,请参见多发布参考文档。
在本示例中,您需要关注的设置有:
name: alembic_cache
此名称应该在所有输出中具有唯一性,并且不能为 primary
,因为此名称已预留给主输出。稍后在实际发布时,将会用到此名称...
scene_item_type: mesh_group
还记得前面第 2 步中“扫描场景”挂钩为每项返回的类型吗?将输出中的 scene_item_type
设置为相同的类型,即告诉应用您想为所有 mesh_group
类型的项发布 Alembic Cache
输出。
提示:此机制让多个不同的输出可以与一个项类型关联,这样,当您稍后想再添加另一个输出时(例如 OBJ 导出),就不必再修改扫描挂钩。
publish_template: maya_shot_mesh_alembic_cache
此设置让您可以指定发布的 Alembic 缓存文件使用的模板。您可以在进行发布时使用此设置。
此示例中使用了以下模板:
# 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'
如果您使用的是默认配置,可以将此模板添加到您的 templates.yml
文件。否则,可能需要调整模板和/或挂钩才能正常工作。
tank_type: Alembic Cache
这是在 Shotgun 中注册您的 Alembic 缓存文件时使用的具有唯一性的发布类型。同样,您可以在进行发布时使用此设置。
icon: 'icons/alembic_output.png'
这是应用在表示此新类型的输出时将使用的图标。将此图标:

保存至您的配置的“icons”目录中,例如 <pc_root>/config/icons/alembic_output.png
我们来看看效果如何。保存配置,在 Maya 中重新加载应用,打开测试用的镜头场景,然后从菜单运行 Publish Alembic Example
命令。如果一切按计划进行,现在您会发现用户界面中列出了一些辅助发布!

先不要单击“发布”(Publish),因为您还需要添加用来导出 Alembic 缓存文件并向 Shotgun 注册这些文件的代码。
第 4 步 - 发布前验证
发布分为三个阶段:
- 发布前
- 发布
- 发布后
“发布前”是第一个阶段。在这个阶段,您可以对要发布的项进行任何验证,并向用户报告任何问题。
与扫描场景一样,“发布前”阶段通过一个挂钩来执行,此挂钩由应用配置中的 hook_secondary_pre_publish
设置进行定义。
按照 Step 2
中的说明,将默认挂钩 <code_root>/install/apps/app_store/tk-multi-publish/<version>/hooks/secondary_pre_publish_tk-maya.py
复制到您的配置的 hooks 目录,并将它重命名为 alembic_example_secondary_pre_publish.py
。
然后,将配置中的设置编辑为:
hook_secondary_pre_publish: alembic_example_secondary_pre_publish
现在,应用将使用您自己的“发布前”挂钩,接下来继续打开它。
此挂钩接受任务列表作为主输入:
def execute(self, tasks, work_template, progress_cb, **kwargs): ...
此列表基于用户在用户界面中所做的选择,因此仅包含已选择要进行发布的内容:
每个任务是一个词典,其中包含一个 item
和一个 output
。
- 这里的 item 是前面第 2 步中“扫描场景”挂钩返回的项。
- output 包含您在第 3 步中为辅助输出配置的 name、publish_template 和 tank_type。
此挂钩的默认执行仅提供一个“脚手架”(Scaffold) 来遍历用户选择的所有任务,并在不知道如何处理输出时报告错误。
用户可能已经删除或修改了“扫描场景”挂钩返回的分组,因此我们需要扩大此执行,确保分组没有被删除或修改,一切都已做好发布准备。
要执行此操作,首先将以下内容:
# 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!")
替换为
# 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!")
然后向 PrePublishHook
类中添加一个新的验证方法:
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
它会返回发现的所有问题,让用户有机会在发布前修复这些问题。当然,您可以扩大范围,对发布项进行更全面的验证。
这是 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
第 5 步 - 发布
在“发布”阶段,您要将每个项的 Alembic 缓存文件导出到发布路径,并向 Shotgun 注册这些文件。在本示例中,这也是您需要做的最后一件事。
此阶段也通过一个挂钩来执行,此挂钩由应用配置中的 hook_secondary_publish
设置进行定义。
同样,按照前面的说明,将默认挂钩 <code_root>/install/apps/app_store/tk-multi-publish/<version>/hooks/secondary_publish_tk-maya.py
复制到您的配置的 hooks 目录,并将它重命名为 alembic_example_secondary_publish.py
。然后,将配置中的设置编辑为:
hook_secondary_publish: alembic_example_secondary_publish
现在,应用将使用您自己的“发布”挂钩,接下来继续打开它。
“发布”挂钩也接受任务列表以及其他一些输入:
def execute(self, tasks, work_template, comment, thumbnail_path, sg_task, primary_publish_path, progress_cb, **kwargs): ...
comment
、thumbnail_path
和sg_task
表示用户通过用户界面提供的信息。primary_publish_path
是主发布的路径,在本例中是场景文件本身。
与“发布前”挂钩一样,默认执行只是一个用来遍历任务的“脚手架”(Scaffold),因此我们首先要做的是添加一些代码,让它可以处理新的 alembic_cache
输出。
要执行此操作,请将以下内容:
# 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!")
替换为:
# 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!")
接下来,向 PublishHook
类中添加一个新的发布方法:
def _publish_alembic_cache_for_item(self, item, output, work_template, primary_publish_path, sg_task, comment, thumbnail_path, progress_cb) ...
我们来看看这个新方法有何用处。
首先,它使用 work_template 从当前场景路径提取一个字段词典:
scene_path = os.path.abspath(cmds.file(query=True, sn=True)) fields = work_template.get_fields(scene_path) publish_version = fields["version"]
接下来,它将组名称添加到各个字段,然后建立要为 Alembic 缓存文件使用的发布路径:
fields["grp_name"] = group_name publish_path = publish_template.apply_fields(fields)
通过此路径,它将使用 Alembic 缓存导出命令 AbcExport
将组导出到发布路径:
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)
最后,它向 Shotgun 注册发布的 Alembic 缓存文件:
self._register_publish(publish_path, ...
就这么简单!
这是 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
这是 Shot_Step
环境中的完整 tk-multi-publish-alembic-example
配置。
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
现在一切准备就绪,可以为镜头场景中引用的资产发布 Alembic 缓存了!
保存配置,在 Maya 中重新加载应用,打开测试用的镜头场景,然后从菜单运行 Publish Alembic Example
命令。
确保已在用户界面中选中您的自定义扫描挂钩找到的 Alembic 缓存项,然后添加备注,再单击“Publish
”。应用将首先使用您的新“发布前”挂钩验证引用是否有效。如果一切正常,它将导出 Alembic 缓存,并使用您的新“发布”挂钩将它们注册为 Shotgun 发布文件!
