从 Maya 发布 Alembic

从 Maya 发布 Alembic

本教程介绍了如何配置多发布,以在从 Maya 发布多组几何体时导出 Alembic 文件。

准备工作

要从 Maya 发布 Alembic 缓存文件,请确保已在插件管理器中加载 AbcExport 插件。您还需要一个镜头场景,里面有一些准备发布的分组网格。

 

ae_prerequisites.zh-cn.png

第 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-examplePublish 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 - 这是辅助输出,要发布的其他任何内容都表示为这种输出。您可以根据需要定义任意多个辅助输出。

这些输出显示在主界面的不同区域:

 

ae_outputs.zh-cn.png

下面的示例为场景内所有顶级网格组定义一个新的 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 命令。如果一切按计划进行,现在您会发现用户界面中列出了一些辅助发布!

 

ae_alembic_outputs.zh-cn.png

先不要单击“发布”(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):
    ...
  • commentthumbnail_pathsg_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 发布文件!

 

ae_success_shotgun.zh-cn.png
 
关注

0 评论

登录写评论。