前言

  这个工具源于动画产线上的需求,需要制作一个工具实现TSM绑定的完全镜像。
  最初绑定部部长丸子给我了指出了大概的思路,然而我其实并没有完全弄明白。
  丸子指出Autodesk的Bounus工具里面有一个针对动画的镜像工具,然而这个工具用起来很是不方便。
  于是,让我想办法将工具的效果集成到TSM的绑定系统中。

  那个工具的确可以实现动画镜像的效果,但是实现的过程让我有点迷,无非就是选中相关的轴向,然后对特定的变换通道反向赋值。

Bounus 动画镜像工具

  于是我照着葫芦画瓢做了第一版的镜像效果。
  由于我并没有完全理解产线需要的镜像效果,所以我完全按照镜像工具的原理镜像的效果是牛头不对马嘴的。

  因此后面我和丸子再度沟通,丸子给我看了以前写好的ADV镜像工具,我才真正了解到产线的需求。
  产线需要做的就是让TSM角色左右对称的完美镜像,其实也不太难理解。

插件原理

  需求明白了就好办了,接下来的工作就是撸起袖子干了。
  根据ADV镜像工具演示的效果,具体就是左右手和左右脚的控制器位置互换
  所以说我之前做的根本就是牛头不对马嘴,因为我只是在原先的位置信息上面变相反数而已,这样是达不到最终效果的,因为所有控制器不是以世界坐标的为原点的,况且要交换位置,单纯负值无法实现的


  由于丸子给我演示的ADV镜像工具只能支持单帧镜像,我就打算开发个可以按照时间轴时间快速镜像的功能。
  然而正因为这个想法,一开始就把自己带到沟里去了。
  有时间范围的镜像固然是好的,但是如果连简单的单帧镜像方案都没有理顺的话,那么Debug的时间就被大大延长了。

  所以到后来才吸取了教训。

插件界面

  按照惯例,首先需要开发插件的界面
Bounus 动画镜像工具
Bounus 动画镜像工具
  看到这个界面是不是很熟悉,没错,这个就是在开发完了藤蔓生长工具之后,基于UI2CG功能快速生成的界面。
  原本是想着极大扩展插件的灵活性,如果插件的自动获取出问题了,也可以让产线手动去拾取正确的控制器的。
  但是这个界面交到丸子手里的时候就被否决了。
  原因是界面太复杂了,而且上面的标签切换也很容易让人误解,交互非常不友好(:зゝ∠)
  产线需要越简单越好的插件,最好就是只有一个按钮,点击就能完成工作。
  产线上的人不会去研究插件背后的原理,也不会去思考插件出问题自己解决,插件出问题了就会返工给我了(/ω\)
  所以后面我就将界面大改,并且根据新的需求制作出了 UI2CG -ver 2.0.0 版本
Bounus 动画镜像工具
Bounus 动画镜像工具
Bounus 动画镜像工具
  这个就是最终的界面设置,在制作这个界面的过程中也遇到了巨大的坑。
Bounus 动画镜像工具
  编辑设置这个三个工能迁移够来其实没有多大的难度,而且后面通过重新整理 UI2CG 可以完美自动实现效果。
Bounus 动画镜像工具
  至于另一个窗口切换的功能,这个就足足折腾了我四天时间,周六日都过来加班,就是为了能够让这个功能能够顺利实现。
  这其中最大的坑就是,直接使用Qt的 QAction 去实现Maya的界面切换会导致Maya全面崩溃。
  然而当时是什么原因我又一直弄不清楚,这才是最最最坑爹的地方。
  结果我需要打开一堆Maya。不断打开插件去测试到底是哪一行代码出问题了。
  因为一开始没有掌握到问题的规律,有时候点击 QAction 没有出现问题,有时候又出现问题,这增加了我无数的Debug时间。


  经过反复测试之后,我发现workspace状态切换是没有问题的,但是其他状态的切换就会导致莫名其妙的崩溃。
  最最最让我无语的是,点击Maya的工具架按钮是不会出现问题的,或者用以前的按钮触发也是不会出现任何的问题的。
  于是我又去查Maya的错误日志,发现是Qt的动态链接库出问题了,但是看不懂日志上的问题根源。
  不过通过上面的点击现象,我联想到了之前网页开发的异步问题,我猜测问题就出在了弹出的菜单QMenu上。
  于是我又制作了一个测试用的插件,这次QMenu不集成在工具的上面,而是改用了右键触发的效果。
  不过在切换的过程中果然出现了相同的问题,更加印证了我之前的猜想。
  于是我想肯定是因为QMenu没有执行到删除命令,然而窗口的删除命令已经执行了,这个过程可能会到导致Qt崩溃。
  终于,在兜兜转转之后感觉自己靠近了问题的答案了。


  于是我尝试通过在Python构建延时操作,类似于 JavaScript 的 settimeout 函数。
  在执行关闭命令之前先延时操作,让其他操作先执行。
  我首先想到的就是 Python 的 time 模块下的 time.sleep() 函数。
  然而这个操作让我失望了,它的延时的结果是导致Maya整体都冻结了。
  也就是执行切换命令,Maya和插件都无法动弹,等到冻结的时间过去之后 Maya 再崩溃。
  于是我又猜想,可能是因为 time.sleep() 函数 是单线程的缘故。
  于是我尝试了多线程的延时操作,又在网上查了一轮,也确实在multithread的模块下有多线程的延时方案。
  但是经过尝试还是导致了Maya的崩溃。


  走到这里,我开始萌生退意了,兜兜转转的测试本身就和插件功能毫不相干,没有必要为了细节而牺牲了全部。
  但是最后我还是心有不甘,那么多时间的付出岂是说放下就放下的。
  于是我咬着牙在周日继续Debug。
  我打算重新整理思路,先找出到底是具体在哪一行的代码上出现了错误,能否通过一些方法对那行代码进行改良,甚至去掉。
  于是我重新投入到了崩溃循环中。
deleteUI
  终于功夫不负有心人,我最后发现是窗口重建过程中 cmds.deleteUI() 出问题了。
  而且问题还是限定在删除 dockControl 和 普通窗口上出问题。
  于是我就想,既然这样删除不可以,难道我就没有替代方案吗?
  没错,就是用Qt的deleteLater来删除这里窗口。
  果然,这个方案是完全没有问题的,唯一需要注意的是 dockControl 需要删除Parent才能完全删除而已
  折腾了那么久,总算是把自己想要的效果实现了,心情激动真的是难以言喻。
  于是我就继续开始优化切换窗口的显示效果。
  我发现DockControl的交互比较糟糕,经常切换完之后并没有显示在最上面,而是在右侧多了个标签。
  在这里我又想起了 ADV 插件,它就可以实现让 DockControl Dock在最上面的效果,于是我又翻开它的代码进行查询。
ADV 代码
  虽然我的重点应该是在 dockControl 的那段代码上。
  但是看到前面那个命令,我简直不淡定了。Deferred这个单词我可是在研究JQuery异步的时候看到过的。
  这个东西不就是我之前想要实现异步的Maya提供的方案吗?
  于是我赶紧查了 evalDeferred 的文档,果然是针对我之前遇到的情况开发的异步接口。
  于是我将Qt的删除代码替换为 异步处理的 cmds.deleteUI()
  结果就再也没有报错了……
  兜兜转转的坑都是经验不足的锅呀,这么难的问题解决了,后续的问题都不太大。
  后面对UI的显示的一些优化也没有太大的问题,都可以轻松解决。

功能开发

  终于经过了这么长时间的摸索之后,终于正式开始了功能上的开发了,说来真是让人惭愧(/ω\)
初始化数组
  首先在 init 函数中 先将先关的TSM空间名称初始化。
  这样就可以简化大量的代码
一键自动获取的代码
  所以将相关的函数记录到数组中,一键获取的函数就几行代码就可以实现了。


  下面就是重头戏,单帧镜像的实现。
单帧镜像
  我的思路是先记录左右控制器的位置到相关的数组中,然后在将相关的数组赋值到对应的控制器上。
  不过由于中间的控制器只需要翻转数值就可以了,所以获取的过程可以顺便对中间的控制器进行镜像。
  至于哪些数值需要镜像哪些是不需要的,那就很难说清楚了,基本就是通过大量的时间测试出来的。(所以没有通用性)
左右镜像
  后续的代码都是镜像左右手脚上各个控制器的镜像。
  通过前面获取到的字典,一一将信息对应赋值。
其他属性镜像
  这个部分我初次开发是没有的,是到后来产线那边提出要求,也的确,只是左手右手的镜像还不够,其他相关属性的镜像也非常重要的。
  这里开发的最大难关在于 for 循环太多了,学过数据结构的我非常担心过多的for循环会不会大大增加运算的时间。
  但是为了实现效果,我也没有更好的方法了。想到后面还要对关键帧进行镜像,就能想像这要花费很长的时间来完成效果了。


获取镜像范围

  先获取关键帧的镜像范围,后面还需要制作进度条来显示镜像进度
关键帧镜像
  其实这里的镜像操作和单帧镜像一致的,但是要加入关键帧的处理,就复杂了很多很多。

总结

  其实功能开发也很坎坷,最主要是要理顺各个镜像物体的对应关系,花了不少的时间。
  不过付出是值得,当我看到TSM绑定在我的插件下完全镜像,真是满满的自豪感o(∩_∩)o