文档章节

Motion Builder 2016 Plugins的编写

小保哥
 小保哥
发布于 2015/11/16 13:49
字数 2364
阅读 1036
收藏 0

工作中有这么几个需求:

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)
#>>> <pyfbsdk.FBActor object at 0x000000001132AD68>
#a.Name
#>>> 'Actor'

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

== 未完待续 ==

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


懒得介绍细节了, 就说说插件怎么做吧.
MoBu最重要的特性之一就是它的Retarget功能,我们的动画数据进入到MoBu之后,如果后续不能和Retarget工作流衔接上,那简直就是rubbish。
那我们就先了解一下Retarget吧,不知道这个名词的自己摆渡一下去。这个操作具体到MoBu里,就是在界面右上角的这里进行:

由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<double>,而且正好可以用于对齐次坐标进行
            /// 进行变换的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;
        }
    }
}

 

© 著作权归作者所有

小保哥
粉丝 7
博文 40
码字总数 39063
作品 0
朝阳
程序员
私信 提问
加载中

评论(1)

gougy
gougy
您好,请问能加您QQ私聊么,我们现在在进行MotionBuilder的二次开发,看到您以前做过相关的工作,希望能跟您进行合作,已发私信给您
(个人)VR实时交互的太极拳学习系统创新实训第一周(1)

这周主要进行了人物模型和示例动画的制作和利用动捕进行舞蹈学习的论文的学习。 使用Neuron和Motion Builder进行了太极拳动作的采集和处理。以下是我使用Motion Buileder导出模型到MAYA,再导...

little_raspberry
2018/04/06
0
0
Maven项目创建的时候pom.xml文件出现错误!

错误描述: 解决办法: 首先关闭 eclipse 删除repository 目录下全部东西 启动eclipse 在项目上选择maven菜单, 选择菜单中的 upate projec子菜单。 这是原答案:If you are working on win...

天高地厚宇宙无穷
2016/07/24
44
0
Flash Builder4.7 破解方法

具体步骤如下: 1.到Adobe官网下载FlashBuilder 4.7. 我下载的是Flash Builder Premium版。 2.安装时,不用序列号,直接选择安装试用版; 3.安装完成后在安装目录下依次修改下列3个文件: (1...

stefanzhlg
2014/12/29
0
0
Flash Builder4.7 破解方法

具体步骤如下: 1.到Adobe官网下载FlashBuilder 4.7. 我下载的是Flash Builder Premium版。 2.安装时,不用序列号,直接选择安装试用版; 3.安装完成后在安装目录下依次修改下列3个文件: (1...

stefanzhlg
2014/12/29
0
0
Leap Motion API类库:Controller

1.Controller 控制器类是您的Leap Motion控制器的主要接口。 创建这个控制器类的实例,访问跟踪数据和配置信息的帧。帧数据可以在任何时候使用frame()轮询功能,调用frame() 或 frame(0) 以获...

储明城
2016/01/28
55
0

没有更多内容

加载失败,请刷新页面

加载更多

面向对象方面的一些东东

<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" cont......

流年那么伤
25分钟前
2
0
git 过滤文件夹和文件(IDEA)

最近使用git版本管理工具遇到一件很烦的事情,commit时总会把.idea、.*.iml和target文件添加进来,实际开发中,这些是需要过滤掉的。在.gitnore文件添加了过滤不起作用。下面介绍一种成功过滤...

uug
32分钟前
2
0
Spark Streaming 实时统计商户当日累计PV流量

一、问题 对实时流量日志过滤筛选商户流量,对每个商户的流量进行累计,统计商户实时累计流量。 当时间超过24时时,重新统计当日商户的实时累计流量。 二、实现步骤 1、采用Spark Streaming...

boonya
37分钟前
2
0
如果Task与Event 创建了记录类型后,不出现在Lightning的Activities中

如果在Lightning的Activities没出现这两个Button,但是在页面布局的Lightning 按钮区也存在,全局操作的记录类型就需要选择一个,否则不会出现

在山的那边
46分钟前
2
0
ddd中的子域和界限上下文

我们先来说说子域是什么?子域在我的理解是在一个庞大的系统中可以明显感知的不同的区块,如果在电商模块中,商品目录,订单,物流,库存,发票等等都可以感知他们明显的不同,可以认为是子域...

算法之名
55分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部