前言

  没想到过年一拖更,便是长达一个月,如今已经开工第二周了(:з」∠)
  过年回家没有更新博客,倒是看了些许 Houdini 教程,记录在 HoudiniWiki 网站中了。
  然而最近一周迷上了煲剧 - 庆余年,快前年的剧了,热播的时候就有所耳闻,一直压着没看。
  最近回深圳上班之后,感觉不在状态,周末就想着看些爽剧休闲一下,结果打开了潘多拉的魔盒ε=(´ο`*)))唉

  恰逢最近工作需要搭建流程框架,试图将影视的部分 publish 流程,迁移到现有的游戏工作流中。
  这难免让我想起了以前老东家干的活,虽然那时候在实习,毕竟也是担任流程 TD 的岗位。
  当时做的很多东西未能落地,实属我入行的遗憾,未接触过成熟的流程框架落地的感觉。

  总算在这边可以尝试开源的 Pyblish 框架将流程落地了。

  Pyblish 框架是 mottosson 大佬(由于 Github 的图片是秃头的,我们都尊称为秃子)结合开源社区制作的一款通用流程框架。
  框架将前端后端分离,开源免费,适合大小 studio 结合不同平台来使用。

官网
教学
API
Github生态


我个人收集的一些 studio 的案例

https://github.com/pyblish/pyblish-mindbender
https://github.com/BigRoy/pyblish-magenta
https://github.com/kredencstudio/pyblish-kredenc
https://github.com/bumpybox/pyblish-bumpybox

Pyblish 作用

  Pyblish 是 Python + Publish 的合成词,以为结合 Python 的发布流程。
  Pyblish 是前后端分离框架, pyblish-base 是底层内核的调度仓库。
  由于前后端分离,因此 Pyblish 前端有 pyblish-litepyblish-qml 两种不同的实现方式
  前端只是个界面,背后依靠的还是 pyblish-base 的调度,分为 collect 收集 validate 校验 extract 提取 integrate 整合 四个环节。
  这个四个环节官方去其英文首字母缩写为 CVEI 流程

alt

  这四个环节就是 pyblish 集众多流程软件的经验总结抽象出来的核心。
  四个环节的拆分可以让环节之间尽可能解耦,让框架更加灵活。

  如果觉得我说的很抽象的话,可以看下面这个视频,这是秃子 Vimeo 账号下发布的 Pyblish 动画小短片。

  这个是动画非常直观的表现了 Pyblish 框架的目的。
  作为一个质检流程,避免错误的文件发布到下游,提前发现问题,降低沟通成本。

  结合 Github 的 Qml 前端操作的截图,想必会更加具象。

alt

alt

  pyblish 会将分成 CVEI 四个环节分部进行。
  第一步是收集检查需要用到的信息,然后根据相关的信息生成对应 检查实例
  第二步是根据生成的 检查实例 分类进行响应不同的检查项检查
  第三步提取将文件导出关联的格式,比如 fbx abc 等等(影视流程要考虑更多,诸如 ass 高低模等等)
  第四步整合则是对接到相关到任务管理软件,更新任务相关的数据。

  Pyblish 的核心是 Validate 质检过程,通过大量的检查项确保发布的文件基本符合规格。


  了解 Pyblish 的基础要素强烈推荐过一遍官方的 Learn文档
  其中 Quick Start 章节有 Pyblish 的核心内容,

Pyblish 概念 & 使用

Pyblish 基础运用

  Pyblish 内部有两种不同的 Plugin ContextPluginInstancePlugin
  Collect 阶段使用 ContextPlugin 随后生成 InstancePlugin 来进行不同的调度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import pyblish
disk = {}
items = ["JOHN.person", "door.prop"]

class CollectInstances(pyblish.api.ContextPlugin):

order = pyblish.api.CollectorOrder # <-- This is new

def process(self, context):
#
for item in items:
name, suffix = item.split(".")
context.create_instance(name, family=suffix)


class ValidateNamingConvention(pyblish.api.InstancePlugin):

order = pyblish.api.ValidatorOrder

def process(self, instance):
name = instance.data["name"]
assert name == name.title(), "Sorry, %s should have been %s" % (
name,
name.title(),
)


class ExtractInstances(pyblish.api.InstancePlugin):

order = pyblish.api.ExtractorOrder

def process(self, instance):
name = instance.data["name"]
transient_path = "c:\temp\%s.mb" % name
disk[name] = instance
instance.data["transientDest"] = transient_path

# NOTE maya 下 导出文件
from maya import cmds
cmds.file(transient_path, exportSelected=True)

class IntegrateInstances(pyblish.api.InstancePlugin):
order = pyblish.api.IntegratorOrder

def process(self, instance):
transient_dest = instance.data["transientDest"]
permanent_dest = "/instances/%s.mb" % instance
server[permanent_dest] = disk[transient_dest]


pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(ValidateNamingConvention)
pyblish.api.register_plugin(ExtractInstances)
pyblish.api.register_plugin(IntegrateInstances)
pyblish.util.publish()

  上述代码基于官方教学整合而来。
  注册 Plugin 之后通过调用 publish 方法可以不通过 UI 执行注册的发布。

  所有的 Plugin 固定调用 process 方法

  ContextPlugin 固定传入 Context 类
  InstancePlugin 固定传入 Instance 类

  Context 类可以通过 create_instance 方法创建实例,每个实例会根据 关联属性被相关的 InstancePlugin 调度。 链接

Pyblish 调度执行

alt

  我们会创建很多不同检查项,但是不同的实例走的检查不尽相同,Pyblish 提供了相应的标签属性来合理调度检查项。

Plugin API链接

alt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import pyblish.api

items = ["john.person", "door.prop"]


class CollectInstances(pyblish.api.ContextPlugin):
order = 0

def process(self, context):
for item in items:
name, suffix = item.split(".")
instance = context.create_instance(name)
instance.data["families"] = [suffix]


class PrintPersons(pyblish.api.InstancePlugin):
order = 1
families = ["person"]

def process(self, instance):
print("Person is: %s" % instance)


class PrintProps(pyblish.api.InstancePlugin):
order = 1
families = ["prop"]

def process(self, instance):
print("The prop is: %s" % instance)


pyblish.api.register_plugin(CollectInstances)
pyblish.api.register_plugin(PrintPersons)
pyblish.api.register_plugin(PrintProps)

import pyblish.util
pyblish.util.publish()
# The person is "john"
# The prop is "door"

  上面通过配置 families 参数可以让 分别走不同的 InstancePlugin
  类似的还有 hosts 属性和 targets 属性

alt

  order 属性来管理当前检查的顺序,下面是官方支持的范围

1
2
3
4
-0.5 to 0.499.. = Collection
0.5 to 1.499.. = Validation
1.5 to 2.499.. = Extraction
2.5 to 3.499.. = Integration

  action 属性可以给当前检查项添加右键菜单,触发额外的功能,比如获取错误的模型或者快速修复有问题的地方。

alt

  以上提到的就是 pyblish 的核心功能。
  另外还有一些让 Pyblish 更加灵活的功能,比如 event 事件系统

alt

  可以在不同的状态下触发不同的事件。
  也支持注册自定义事件,在检查项中触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pyblish.util
import pyblish.api


class MyCollector(pyblish.api.ContextPlugin):
order = pyblish.api.CollectorOrder

def process(self, context):
pyblish.api.emit("myEvent", data="myData")


def on_my_event(data):
print(data)

pyblish.api.register_plugin(MyCollector)
pyblish.api.register_callback("myEvent", on_my_event)
pyblish.util.publish()
# 打印 myData

Pyblish-base 调度原理

常用数据结构

alt

  上面是 Context & Instance 类, process 传入的 context 和 instance 参数就是这两个类的相应实例

1
2
3
4
5
6
7
class CollectInstances(pyblish.api.ContextPlugin):
def process(self, context):
pass

class ValidateNamingConvention(pyblish.api.InstancePlugin):
def process(self, instance):
pass

  它们的基类 AbstractEntity 是个数组,所以可以进入 for 循环进行遍历。

publish

alt

  入口函数是 publish ,从上面的截图可以看到后续核心调用是 _convenience_iter

alt

  _convenience_iter 核心调度
  logic.Iterator 返回的 Context 参数 和 Instance 参数

logic.Iterator

alt

  在 logic.Iterator 遍历器里面会先对 targets 属性进行过滤。
  然后遍历一遍传入的 plugins 类,通过 instances_by_plugin 的方法找出 plugin 和 context.create_instance 生成实例的关系。

alt

  这里直接对传入的 context 当成 instances 变量进行 for 循环。
  那为啥 context 是 instances 呢?
  这就承接上面数据结构所提到的, context 的基类是个数组,执行 context.create_instance 方法。

alt

  查看 create_instance 的调用可以看到如果传入 parent 就会将自身 append 到 parent 里面了。
  所以 context 直接 for 循环就是其关联的 instances。

alt

  最后会通过 plugin.match 以及 instance 传入的 familyfamilies 过滤出对应调用的 plugin 。
  create_instance 的时候会将所有的后续参数 **kwargs update 到 data 中。
  过滤 family 会用到 data 中的数据,因此 family 的调用可以写到 create_instance 里面

1
2
3
class ValidateMeshNonManifold(pyblish.api.Validator):
def process(self,context):
context.create_instance("instance",family="character",families=["actor"])

plugin.process

  通过上面的 logic.Iterator 可以返回 plugin 类和其关联的 instance 属性。
  后续会到 plugin.process 进行调用,注意这个 plugin 是小写是 模块 里面的函数调用。

alt

  process 为了兼容以前的 API 会分 __explicit_process__implicit_process
  __explicit_process 会实例化传入的 plugin 类并且判断当前是 action 还是 plugin
  从中分配好 process 使用的参数。

总结

  虽然上述的调用有点绕,不过基本就是这个原理完成了 publish 的实例调度过程。
  这么做可以根据当前文件里不同类型的数据拆分到不同 families 检查项下检查。
  实现更细致的检查控制和调度。

  pyblish-base 是无 GUI 下检查调度的功能。
  pyblish-lite 带 GUI 检查的时候并不是直接用 pyblish-base 下的 publish 的。
  为了同步响应 GUI , pyblish-lite 拆分了 publish 的过程,不过核心的调度还是用了 base 的 API。