Motion Builder 2016 Plugins的编写

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

工作中有这么几个需求:

1、将动捕服数据实时接入到MoBu(Motion Builder简写,下同)

2、将retarget后的动作数据实时导出到3D引擎里驱动模型

3、在MoBu里K相机动画曲线,将相机参数同步到引擎里,轻松实现机位变动切换等需求。

我主要就是写这么3个device插件来完成这些功能。

以前没接触过Autodesk系列的软件,对MoBu也完全没听说过,因此,要开发我想要的功能,必须仔细读一读它的SDK文档。

MoBu的SDK文档写的很不全,很多细节都完全没有涉及。主要是大致介绍了一下软件的实现的概念构成,代码实现层面几个重要类的介绍,讲解了一下概念体系里比较典型的几个关系,还有关于Animation的一些稍微比较详细一点的介绍。从阅读SDK文档开始,到写出第一个插件--input device插件,耗时近1个月,大部分时间是跟着文档走,尝试尽量理解它。后来的几个插件就很容易了,基本上拷贝粘贴再稍微改改就出来了。

我的经验是:MoBu的扩展开发其实是可以很容易掌握的,最快速掌握的方法,就是充分利用Python。

MoBu的windows菜单项里有Python Editor一项,打开它,就是软件集成的python解释器。基本上一般的功能都能直接在这个解释器里直接调用执行。我在这个解释器里,跟着SDK文档里的Your First Python程序,完整地敲了一遍实例代码,每一行都要理解,碰到不懂的概念,就去看文档的其他部分的介绍,这个例子里几乎有实现我的插件需求的全部工具了。

下面,我就罗列一下我脑子里还记得的比较常用的一些概念。

先说打开软件第一眼看到的最大的画了一张格子的窗口,这就是场景窗口,或说视图窗口,可以有若干个视图窗口同时存在。所有的视图窗口展现的都是同一棵对象树,只不过可能展示的是这棵树的不同部分或角度。而且MoBu里有且只有这么一棵树,它叫Scene。我们的MoBu软件系统名称叫FBSystem()。你可以在python editor里敲下FBSystem().Scene.Name,它打印出来字符串“Scene”,这里的FBSystem().Scene就是刚才说的那棵猥琐的树Scene。我说它是树,不是指它在内存里就是一种树数据结构来实现的,而仅仅是指可以理解成这么一棵树的形状,各种物件都以一定层次关系挂在这棵树上,我们的视图窗口就从某个角度来画出这个窗口里看到的这棵树的样子。我们用户的界面操作,就是往这棵树上挂东西或拿掉东西,以及操作树上的东西。这个就是总的直观的概念,有了这个总的直观的印象,细枝末节就可以慢慢来加深理解了。

接下来看软件的左下角窗口,是所谓的Navigator Window(在默认布局下)。这个破窗口,看起来像个树状结构。那当然了,它大部分功能,就是显示我们的Scene树的层次样貌。先看窗口里的第一个节点,叫Scene,这个名字特别无厘头,因为我们的Scene树里没有叫Scene的子节点。我们先通过File菜单或者Asset Browsering窗口加载一个模型,然后跑一下代码:

scene=FBSystem().Scene
for i in scene.RootModel.Children:
    print i.Name

看看打印出来的字符串,我就发现和Navigator窗口里Scene节点下第一级子节点的内容一样。这再明显不过了,这里的Scene节点展示的其实是我们Scene树的RootModel节点的内容。所以我觉得这个Scene节点应该改名叫RootModel。

接下来,打开FBScene的文档,查看它的定义,能看到里面有很多属性成员,看属性名字大概就能猜到这些属性和Navigator窗口里的这些顶层节点是一一对应的。所以,下面的代码成立:

print scene.Cameras.Name
print scene.Characters.Name
print scene.Lights.Name

for x in scene.Cameras:
    print x.Name

for x in scene.Shaders:
    print x.Name

for x in scene.Takes:
    print x.Name

# 等等等等。。。

对我们定义的这个scene变量,执行help(scene)语句,在输出的[Data descriptors defined here:]部分能看到scene包含的那些可以访问的顶层对象类别。

>>>>>>>>>>>>>>>>>>>>>

2018.05.14 ↓↓↓

#选中一个节点【【

#先查找该节点
rh = FBFindModelByLabelName( 'RightFingerBase' )

#选中rh节点
rh.Selected = True

#取消选中rh节点
rh.Selected = False

#】】



#设置节点的变换【【

rh.Translation = FBVector3d(5,5,5) # Lcl
rh.Rotation = FBVector3d(0,0,90)   # Lcl

#】】

2018.05.16 ↓↓↓

actors = scene.Actors
a = actors[0]
#print(a)
#>>> 
#a.Name
#>>> 'Actor'

# 修改Actor的Hips旋转
a.SetDefinitionRotationVector(FBSkeletonNodeId.kFBSkeletonHipsIndex, FBVector3d(50, 50,0))
 

== 未完待续 ==

>>>>>>>>>>>>>>>>>>>>>
2016.05.23 续 ↓↓↓


懒得介绍细节了, 就说说插件怎么做吧.
MoBu最重要的特性之一就是它的Retarget功能,我们的动画数据进入到MoBu之后,如果后续不能和Retarget工作流衔接上,那简直就是rubbish。
那我们就先了解一下Retarget吧,不知道这个名词的自己摆渡一下去。这个操作具体到MoBu里,就是在界面右上角的这里进行:
232141_rtKh_263969.png
由Source去驱动Character,即Source怎么动,Character就怎么动。就像Source是老师,Character是学生。学生踩在老师肩膀上,所以界面上Character在Source一栏上方----这是我意淫的,不要当真。
可以了,我们数据进到MoBu里后,无非就是要驱动模型动作。所以,要被驱动的模型就是上图中的Character。那么它要知道怎么动,还需要一个老师。制造老师就是我们插件的核心任务。
调查一下就知道,这个Source列表框里,可以是Actor和Character(都是MoBu里的概念),我跟Actor不熟,所以对我来说,老师应该是一个Character,制造老师就是制造Character。那就调查一下Character怎么创建吧。new FBCharacter()。一句代码搞定。我现在在说C++代码,不是Python。说到这里强调一下,MoBu的绝大部分扩展性都可以通过python来实现,但是Device插件只能通过C++来实现,因为这是SDK文档上说的。而我做的就是Device插件,I/O-Device。接前两句,创建出来的Character是一具空壳,没有灵魂。很明显,接下来可能都猜得到我要给它灵魂了。然而,我也给不了它灵魂,能给的仅仅是把它做成提线木偶,线的另一端才是灵魂。而这个灵魂就是我们插件的灵魂--ModelTemplate。

要回家了,改天继续。

== 未完待续 ==

============2017.09.10 ↓==============

/**
 * 以下面这个重要的函数为例来描述一下mobu里的四元数如何使用。
 * 输入的动捕节点的position+rotation都是local坐标系形式。
 * 这个函数的功能就是需要将local系的输入转换为world系,给到骨骼节点上去。
 */
void ORDeviceMotion_Hardware::calc_local_quat(int i) {
    //int index = mMotionData.qua[i].id - 1;  // 注意确认动捕数据里的id编号从0还是从1开始.
    int index = mMotionData.qua[i].id;

    if (!global_quats[index].filled) {
        /// 注意FBQuaternion的构造函数参数意义:是按照qx、qy、qz、qw的顺序
        auto quat = FBQuaternion(mMotionData.qua[i].qx, mMotionData.qua[i].qy, mMotionData.qua[i].qz, mMotionData.qua[i].qw);
        if (index == 0) {
            global_quats[index].filled = true;
            mChannelData[index][DATA_TX] = mMotionData.pos.x;
            mChannelData[index][DATA_TY] = mMotionData.pos.y;
            mChannelData[index][DATA_TZ] = mMotionData.pos.z;
            global_quats[index].quat = quat;
            return;
        }
        else {
            calc_local_quat(SKELETON_PARENT[index]);   // 隐藏的bug.这里的实参与形参i意义已经不同
            /// a*d=b. a、b代表两个方位,d代表a、b之间的方位差(或称角位移),就是从a到b,需要运动d。
            /// 下式就是:FBQMult(b, a, d)
            FBQMult(global_quats[index].quat, global_quats[SKELETON_PARENT[index]].quat, quat);
            
            /// 构造三维向量的齐次坐标表示:添加的分量应该是0.这样做乘法时平移分量会被清0,不起作用,这正是向量变换的特性。
            /// 这里与FBQuaternion其实没啥关系,只不过FBQuaternion刚好就是FBVector4,而且正好可以用于对齐次坐标进行
            /// 进行变换的FBQMult接受的就是FBQuaternion类型参数,这里复用这个API的实质而已。
            FBQuaternion parentToChildVector(
                mChannelDefaultData[index][DATA_TX] - mChannelDefaultData[SKELETON_PARENT[index]][DATA_TX],
                mChannelDefaultData[index][DATA_TY] - mChannelDefaultData[SKELETON_PARENT[index]][DATA_TY],
                mChannelDefaultData[index][DATA_TZ] - mChannelDefaultData[SKELETON_PARENT[index]][DATA_TZ],
                0);

            /// 四元数的逆。这里必须采用将(x,y,z,w)的x、y、z分量取反的方式来求逆。不能采用将w取反的方式来求。原因不详。
            /// 这个结论来自于这里的实践。
            FBQuaternion qInverseParentG(global_quats[SKELETON_PARENT[index]].quat);
            qInverseParentG[0] = -qInverseParentG[0];
            qInverseParentG[1] = -qInverseParentG[1];
            qInverseParentG[2] = -qInverseParentG[2];

            /// p' = q * p * (q^-1)
            /// 对点或向量p执行q代表的旋转。
            /// 具体到这里的实例就是:子节点相对父节点有一定offset,当父节点进行特定旋转后(会带动子节点运动),求子节点在何处。(这里只求位移)
            /// parentToChildVector就是这里的offset,NewPos就是子节点最终的位置。
            FBQuaternion NewPos;
            FBQMult(NewPos, global_quats[SKELETON_PARENT[index]].quat, parentToChildVector);
            FBQMult(NewPos, NewPos, qInverseParentG);

            mChannelData[index][DATA_TX] = mChannelData[SKELETON_PARENT[index]][DATA_TX] + NewPos[0];
            mChannelData[index][DATA_TY] = mChannelData[SKELETON_PARENT[index]][DATA_TY] + NewPos[1];
            mChannelData[index][DATA_TZ] = mChannelData[SKELETON_PARENT[index]][DATA_TZ] + NewPos[2];

            global_quats[index].filled = true;
            return;
        }
    }
}

 

转载于:https://my.oschina.net/zhoubaojing/blog/530851

你可能感兴趣的:(python,c/c++,数据结构与算法)