【blender脚本】blender python脚本建立物理骨骼

以卡通角色的帽子的物理骨骼建立与绑定为例,介绍实现过程。这次需要的知识背景较多,我也尽量清楚阐释。

需要注意的是,这个脚本并不是从无到有完成物理骨骼的绑定,旨在解决物理骨骼绑定中重复繁琐的步骤。

前置的准备

知识背景

首先我们需要知道的是blender实现物理仿真的流程:

首先物理仿真分为四部分:
最底层的肯定还是我们的mesh和绑定mesh对应的defrom rig(后简称defrig),一般来说帽子这种物体想做物理效果,自动绑的效果基本上支持不了,只能手动分一下定点组和权重。
其次是physics rig(以后简称phyrig),即在defrig和mesh之间起到承接的作用,一方面,通过复制旋转约束,使ddefrig能够与phyrig实现旋转同步,进而控制网格。
phyrig和physics mesh之间的联动,则通过阻尼追踪phymesh对应的顶点组来实现。(physics mesh,即用于物理模拟的mesh,通过编辑模式+顶点吸附建立defrig对应的曲线,通过网格-曲线-曲线倒角-网格实现体积塑造)
在这里插入图片描述

数据准备

1.建立帽子对应的hatmesh
2.建立deformrig,面向hatmesh完成顶点组和权重分配
3.建立phymesh,完成顶点组和权重分配

为什么这么要求,因为一般物理仿真不像人骨骼一样这么规律,骨骼拓扑取决于物体结构,而物理仿真精度也取决于骨骼的拓扑以及数目,对应的Phymesh也需要去人工指定锚定顶点组来固定phymesh,所以这里的前期准备部分仍需要人工来做骨骼的布置和phymesh的制作。(当然我要能直接脚本搞定这个。。。那进个autodesk啥的上班也绰绰有余了吧)

完成绑定的hatmesh
在这里插入图片描述
deformRig
在这里插入图片描述
physics mesh完成顶点组和权重分配后,注意这里为了方便后续的绑定,需要按照骨骼顺序来做顶点组按序分配
在这里插入图片描述
播放,我们可以看到phymesh已经有物理效果,而帽子没有
在这里插入图片描述
在这里插入图片描述

解决的痛点

所以我们脚本的目的是什么?顺便在这一步我们梳理脚本的业务流程:

  1. 创建phyrig:复制defrig并重命名,虽然blender有批量重命名功能,但是blender复制的时候会直接将骨骼的后缀序号叠加,强迫症发作的我会顺便重命名并将其序号调整至正确
  2. 完成phyrig与phymesh的绑定:对应的bone和vertex group,赋予阻尼追踪约束
  3. 完成defrig与phyrig的绑定:对应的bone之间,赋予复制旋转约束

编写脚本

获取用户选中的物体

像骨骼绑定需要选中两个物体一样,我们的脚本也需要用户选中两个物体,然后我们需要对用户选中的情况进行检查,并反馈检查结果,避免未达成绑定条件就进入后续的计算。

需要注意的是,由于blender scripting本身不支持中文,所以我写了很多英文注释,文章中也予以保留,中文注释都是写博文的时候补充的,狠狠考验英文水平了捏。

main():
	selectedList = bpy.context.selected_objects
    
    #用户需要选中两个物体,多了少了都不行
    if(len(selectedList)!= 2):
        print("must select at least 2 objects...")
        return -1
    
    #blender can provide correct selected order of objects in selected_objects
    #注意这里,实际上blender.context.selected_objects无法获取准确的物体选中顺序,所以这段判断代码实际上并不能使用
    if(selectedList[0].type != "MESH" and selectedList[1].type != "ARMATURE"):
        print("first selected must be physics_mesh, last selected must be amature.")
        return -1

这里我们就遇到了第一个坑,按理说我们是应该检查一下用户的选择物类型,并检查下选择顺序:
但很快belnder直接给了我们一巴掌,blender.context.selected_objects的顺序不是基于用户选择的顺序,而是基于场景的顺序,就是说不管用户选择顺序的先后,selected_objects给出的元素顺序总是一样的。。。
但是后续操作中又会涉及到选择物体和激活物体(最后一个选择物体)的差异,如果激活物体选成了mesh,就会报错。
我只能说bpy整的一手好活,大哥,我都用selected_objects了,我怎么可能不在乎选择顺序呢?

这里建议一方面检查选中物体的类型,另外通过active_object来检查激活物体的类型,我就不再阐述了。

下一步很清晰,就是根据选中物体的类型,分别用一个变量记录:hatmesh以及defrig

for mem in selectedList:
    if mem.type == "MESH":
        Tarmesh = mem
    if mem.type == "ARMATURE":
        RigObject = mem

创建phyrig

基于当前的骨骼,进入编辑模式将其复制,完成重命名,返回phyrig,phyrig/defrig骨骼总数,defrig。

def createPhyBones(DeformRig):
    if(bpy.context.active_object.mode != "EDIT"):
        print("not edit mode, now switch to edit mode")
        bpy.ops.object.mode_set(mode = "EDIT")
        
    #select all bones
    #切换到编辑模式后,我们全选当前骨骼
    bpy.ops.armature.select_all(action = 'SELECT')
    
    RigLength = len(bpy.context.selected_bones)
    
    DeformRig = bpy.context.selected_bones
    
    #after copy, blender will automatically select all new bones in edit mode
    #全选当前骨骼后,完成复制,blender会自动切换到副本骨骼
    bpy.ops.armature.duplicate()
    PhyRig = bpy.context.selected_bones
    
    #针对副本骨骼完成重命名,phyrig创建完成
    for i in range(RigLength):
        PhyRig[i].name = ("HatBonePhy.%03d" % i)
        print(PhyRig[i].name)
    
    return PhyRig, RigLength, DeformRig

完成phyrig和phymesh的绑定

回到main函数部分:

#创建phyrig
	PhyRig, RigLength, DeformRig = createPhyBones(RigObject)
    
    #切换物体模式,重新选中当前骨骼,确保当前armature object已经更新到了phyrig和defrig共存的状态
    bpy.ops.object.mode_set(mode = "OBJECT")
    
    PosePhyObject = bpy.context.active_object
    #切换姿态模式
    bpy.ops.object.mode_set(mode = "POSE")
    
    for i in range(RigLength):
        #by createPhyBones() we get PhyRig: the list of copied bones that named "...phy"
        #but we cant add constraint directly for PhyRig: as mem in PhyRig is edit bones
        # the switch from different data structure of belnder is complex, be careful 
        try:
			#获取对应骨骼,完成阻尼追踪约束的赋予
            Tarbone = PosePhyObject.pose.bones[PhyRig[i].name]
            
            unitCons = Tarbone.constraints.new(type="DAMPED_TRACK")
            unitCons.target = Tarmesh
            unitCons.subtarget = Tarmesh.vertex_groups[i+1].name
        except Exception as e:
            print("error occur for: no." + str(i))
            print(e)
    
    
    return 1

这里可能有人又要问,你这不是都返回了PhyRig,直接从PhyRig里面做约束不行吗,为什么要重新在PosePhyObject里面选定呢。

这是因为在编辑模式下复制完的骨骼,是editbone,而editbone是edit的子分支,可能熟悉blender的人就能猜的着:

就像编辑模式下不能添加pose constraint,editbone也不存在constraint属性。

我们只能老老实实重新从object里选对应的骨骼,切换到pose子属性下来赋予约束。

blender返回值传递偏差导致的utf-8编码错误

到这一步,如果你是跟着做的话,很有可能一跑下来就会发现,phyrig创建和绑定的效果很不稳定,骨骼的检索会出现随机错误
我们直接打印骨骼检索的情况来做检查:
在这里插入图片描述
在这里插入图片描述
带来的结果就是,phyrig绑定的效果很不稳定,下图是三次执行的结果,每次都有不同的骨骼出现绑定问题。(实际上就是检索问题,对应的key value未能检索到对应的bone)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
当然我第一反应也是基于传统的编码问题的求解思路,用encode,decode方法来做编码和解码,结果完全不能解决问题

没办法,我们只能逐步排查,首先我们在createPhyBones对phyrig完成命名复制后,直接打印对应phyrig的name属性,看看是否有问题。
在这里插入图片描述
在这里插入图片描述
好,至少看来赋值是没有问题了,然后我们检查参数回传后的情况。

获取到phyrig后,我们直接打印其元素。
在这里插入图片描述
果然是返回值出现了偏差,导致后续出现了检索错误。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里我也没能理解,为什么python的返回值会被更改?我本身只跑这一个创建物理骨骼的脚步,也没能在骨骼上搜到相应情况的反馈和修改方法,实在是太奇怪了。。。

那没办法了,本来我还想好好做人,既然返回的局部变量有问题,那就别怪我不客气了!我直接改成全局变量,不需要返回值了!

反正都是给个人用户用的,还在乎什么编码规范啊。

简单来说就是,phyrig和deformrig这两个变量都改成全局的。

然后就绑定成功了。。。
在这里插入图片描述

完成defrig和phyrig的绑定及收尾

for i in range(PhyRigLen):
        #by createPhyBones() we get PhyRig: the list of copied bones that named "...phy"
        #but we cant add constraint directly for PhyRig: as mem in PhyRig is edit bones
        # the switch from different data structure of belnder is complex, be careful 
        try:
            print("start seeking... " + PhyRigNameList[i])
            Tarbone = PosePhyObject.pose.bones[PhyRigNameList[i]]
            
            #赋予阻尼追踪约束:phyrig to phymesh
            unitCons = Tarbone.constraints.new(type="DAMPED_TRACK")
            unitCons.target = Tarmesh
            unitCons.subtarget = Tarmesh.vertex_groups[i+1].name
			
			#赋予复制旋转约束:defrig to phyrig, in local space
            TarDeformbone = PosePhyObject.pose.bones[DeformRigNameList[i]]
            boneCons = TarDeformbone.constraints.new(type="COPY_ROTATION")
            boneCons.target = PosePhyObject
            boneCons.subtarget = PhyRigNameList[i]
            boneCons.target_space = "LOCAL"
            boneCons.owner_space = "LOCAL"
            
        except Exception as e:
            print("error occur for: no." + str(i))
            print(e)

另外,为了方便操作,生成好的phyrig我们直接移入一个空图层

# in createPhyBones():

# bone.layers is an array of 32 boolean values tells you whether the bone is present on each of the 32 layers.
#需要注意的是,blender使用的layers来管理对应armature object的bonelayer,它是一个32位的布尔向量,在需要把当前选中骨骼转移到对应图层时,把对应位赋予true即可,从对应图层中清除该骨骼时,赋予对应位false即可
    emptyLayer = 0
    recentLayer = 0
    breakTag = 0
    #获取指向空图层及当前图层的位置
    for i in range(32):
        if (breakTag == 2):
            break
        if(bpy.context.selected_bones[0].layers[i] == False):
            emptyLayer = i
            breakTag +=1
        else:
            recentLayer = i
            breakTag +=1
    
    for i in range(RigLength):
        PhyRig[i].name = ("HatBonePhy.%03d" % i)
        
        PhyRigNameList.append("HatBonePhy.%03d" % i)
        DeformRigNameList.append(DeformRig[i].name)
        
        print(PhyRig[i].name)
        
        #对应bone直接移入空图层,并将其移出defrig所在图层
        PhyRig[i].layers[emptyLayer] = True
        PhyRig[i].layers[recentLayer] = False

效果演示:

一键即可完成物理骨骼创建及绑定
在这里插入图片描述
phyrig自动移入空图层,与defrig分开
在这里插入图片描述
整体效果
在这里插入图片描述
后续还需要跟body armature做父子级绑定,这个就是手工操作的后话了。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值