
用轮子→改轮子→造轮子,而Blender Python API使得GUI里进行的操作十有八九都能用命令行敲出来。虽然相关的教程已经够多了,不过这次的目的还是“将(折腾MMD时的)部分操作替代为脚本,从而提升工作效率”。
自己的代码基本功不怎么扎实,读起来或许会有些费劲,如果有什么issue也还请各位同好加以纠正。
特别鸣谢
春日未来 with 星元素 担当P @Eleanor
之前研究星耀的改模时提供了脚本支援,这次则带着自己入门了bpy模块;
如果在“某个游戏”里见到了土豆的模组,十有八九得拜这位佬做过的研究所赐。
此外,本次依然受了@LC_ilmlp的脚本指导&修正,感谢。
目录
1. 环境/工具提示配置
2. bpy.context - 获取信息
3. bpy.data - 物体处理
4. 控制台→脚本注意事项
5. 简单的实战案例
写在前面
先放一篇知乎上的参考文章,虽然作者还在坑,但现有的部分已经很不错了:
《Blender Python 简易参考》
https://zhuanlan.zhihu.com/p/525475118
首先想谈一下如何“学习”Blender + Python。 最高效的方法是我要做什么我就学什么。 如果你去看API文档,然后上来“bpy包含几大模块,bpy.ops, bpy.data, bpy.mesh, bpy.context“云云,看了半天还不知道该干嘛,记也没记住,十分低效。那不如你在这篇博客里找找常见操作的代码,直接就可以用了,用了几个差不多就知道bpy是怎样一个设计的逻辑了,一个下午就能完成任务是最好的。
就像Python本身一样,各种库与函数都是随找随用,甚至可以在获取需要的信息后立刻打开ChatGPT照葫芦画瓢。因此比起背语法,更重要的是将脚本要达成的目标进行分解,并在浩如烟海的API文档中为每个步骤都找到相关的定义/写法。用到的代码或许并不复杂,但如果缺乏逻辑就开工的话,脚本没写完,大脑先Out of memory了。
本次使用的blender版本为3.4.1,以及对应的官网Python API文档:
https://docs.blender.org/api/3.4/
1. 环境/工具提示配置
点击最上面一行中的“Scripting”,或是在GUI里调出“脚本”相关窗口:


文本编辑器(中间):编辑/运行整段脚本
Python控制台(左侧):运行单个命令行
信息栏(左下):显示部分GUI操作所对应的函数/变量转换
接着,在blender最上面的“窗口”项里调出系统控制台(cmd窗口):

需要区分的是,Python控制台只负责显示控制台内输入的指令/操作:

而脚本运行的输出内容则会显示在cmd窗口里:

在blender本体由于脚本或GUI操作卡死时,也可以通过在cmd窗口按下Ctrl+C组合键来强制停止脚本运行,因此建议在测试脚本时保持cmd窗口的常时开启。
还有一个blender自带的,写脚本所需的重要功能:
设置-界面-勾选“使用工具提示”与“Python工具提示”,在鼠标悬停到对应按钮/物体时可以看到API中对应的python变量/调用函数。

举个小例子,用GUI的按钮添加一个柱体:

左下角的信息栏里就出现了:
bpy.ops.mesh.primitive_cylinder_add(radius=1, depth=2, enter_editmode=False, align='WORLD', location=(0, 0, 0), scale=(1, 1, 1)) 把这行敲进控制台就有了相同的效果:

随便套个for循环进去:
import bpy
for num in range(1,10):
bpy.ops.mesh.primitive_cylinder_add(radius=1, depth=num, enter_editmode=False, align='WORLD', location=(5*num, 0, 0), scale=(1, 1, 1)) 效果显而易见:

也就是说,有些批量化的功能(匹配/重复生成/状态应用&赋值等)只能通过脚本而不能通过GUI来实现。
虽然已经有了星月佬的现成工具miritore,但由于模型格式相对规范且方便讲解,所以还请允许自己这次继续用土豆的模型作为数据范例。
随机请上一位小偶像——

.rd 1d52 = 24 = 杏奈。
在接下来的内容里不会出现其他图示,还请各位兔子P们安心。
如果想仿照着接下来的步骤进行操作的话,任意导入一个其他的fbx/pmx就行。

不算SHS,杏奈和纱代子每件衣服都有两套头模——当然,杏奈的头模对应着两套表情。
重新排一下GUI视图,环境设置完毕。
模型的物体属性和信息都包含在bpy.context里:
https://docs.blender.org/api/current/bpy.context.html

通过读取bpy.context以及其子变量,可以获得“当前GUI窗口中选择的物体(或者说,当前状态为active的物体)”的属性,这些都可以在API文档中查询到:

比如最基础的,获得当前选择物体的名称列表:

实操一下,先确认当前的GUI大纲视图状态:

进控制台起命令,得到了包含一个名为“Armature”的object的list:
#输入
>>> bpy.context.selected_objects
#控制台输出
[bpy.data.objects['Armature']] 需要注意的是,输出的变量类型不是string而是list(只有一项的list也是list),在指定了具体某一项后才能输出string:
>>> type(bpy.context.selected_objects)
<class 'list'>
>>> bpy.context.selected_objects[0]
bpy.data.objects['Armature']
>>> type(bpy.context.selected_objects[0])
<class 'bpy_types.Object'>
当然,bpy.context可以get到的内容远远不止selected_objects,又比如:
#获得当前GUI中可选择的物体列表
>>>list(bpy.context.selectable_objects)
[bpy.data.objects['Armature'], bpy.data.objects['obj_head_GP'], bpy.data.objects['shape_Grp'], bpy.data.objects['eyes_add'], bpy.data.objects['face'], bpy.data.objects['hair'], bpy.data.objects['eyes']]
#获得当前GUI中可见物体列表(包括不可选的object)
>>>list(bpy.context.view_layer.objects)
[bpy.data.objects['Armature'], bpy.data.objects['obj_head_GP'], bpy.data.objects['shape_Grp'], bpy.data.objects['ss002_acc01'], bpy.data.objects['eyes_add'], bpy.data.objects['face'], bpy.data.objects['hair'], bpy.data.objects['eyes']] 另一方面,通过向bpy.context中的一些变量赋值,也可以改变GUI中的显示,包括但不限于选中物体,或是设定某个状态的开关。
而作为脚本的基础规则之一,只有将物体指定为active_object之后才能对该物体进行操作,平常在GUI中所进行的点击也可以视作将物体设为active。
因此,大部分脚本的起手式都是“选择需要编辑的物体”——set object as active:
#选中名为eyes的object
#需要用的是view_layer.objects而不是active_object
bpy.context.view_layer.objects.active = bpy.data.objects['eyes']
#错误示范:如果直接给active_object赋值会因为变量状态为只读而报错
>>> bpy.context.active_object = bpy.data.objects['eyes']
Traceback (most recent call last):
File "<blender_console>", line 1, in <module>
AttributeError: bpy_struct: Context property "active_object" is read-only 回到GUI,可以看到位于eyes名称左侧位置出现了小框:

虽然光标选在face上,但在脚本中依然会对eyes进行处理
此时,eyes就成为了active_object,可以进行后续操作了。
在从GUI或bpy.context选中物体后,接着就是通过bpy.data对物体进行处理。
https://docs.blender.org/api/current/bpy.data.html

点开data type,可以看到bpy.data下同样包含了众多子变量:

关键的是,可以通过bpy.context.active_object.data,或是bpy.context.selected_objects.data的形式,将上文中bpy.context的内容作为data使用:

而对于这部分,最需要理解的就是“到底该调用哪个变量”。
受篇幅所限,这次的专栏就以“获取骨骼”为例:
bpy.context.active_object.data.bones
bpy库功能.在GUI中显示出内容的.当前选中的物体的.数据中的.骨骼合集
稍微改一下,改为显示指定物体而非当前物体:
bpy.data.objects['Armature'].data.bones
bpy库功能.在GUI中显示出内容的.名为“Armature”的物体的.数据中的.骨骼合集
再改一下,又可以变成:
bpy.context.selected_objects[0].data.bones
bpy库功能.在GUI中显示出内容的.所有可选物体[最顶层]的.数据中的.骨骼合集
并且显然,获取object内容的方式并不唯一:
#也就是说,上面的代码都是等效的
>>> list(bpy.context.active_object.data.bones)
>>> list(bpy.data.objects['Armature'].data.bones)
>>> list(bpy.context.selected_objects[0].data.bones)
#以上三段命令都会输出如下骨骼list
[bpy.data.armatures['Armature'].bones["ch_ss002_024ann"], bpy.data.armatures['Armature'].bones["KUBI"], bpy.data.armatures['Armature'].bones["ATAMA"],
#"此处省略一堆内容",
bpy.data.armatures['Armature'].bones["KUBI_RT__twist_x0"]] 而正如第一节中所提到的,脚本中主要采用active_object来指定物体名称:
>>> bpy.context.active_object=<需要设定active的物体名称> 并配合着其对应的bpy.data进行操作:
>>> bpy.context.active_object.data.XXXX 诸如此类,相关类型和列表都记载在API文档里,动画(actions),形态键(keys/shape_keys),材质(material),翻翻type和子变量,print()或者list()几下,总是能找到的。
确定对象之后,就是对object进行赋值来改变物体属性了。这步想必不用多作解释,保证等号左右两边变量类型一致就行。
#显示名称
>>> bpy.data.armatures['Armature'].bones["ATAMA"].name
'ATAMA'

赋值,改名:
#改名
>>> bpy.data.armatures['Armature'].bones["ATAMA"].name='Head'

最终,改名操作只用到了一行代码。
大多数控制台的内容放到脚本里都能用,但写脚本时还是有一些注意事项:
1. (废话)控制台可以直接输入命令,而使用脚本则需要引入bpy库:
import bpy 2. 同理,控制台不能使用其他py库,但脚本则不受限制——
比如打时间戳:
import datetime
print("=== Start at",datetime.datetime.now().strftime('%X'),"===")

或是通过正则匹配遍历列表,直接将set_active_object的对象设定到符合条件的物体上,可以进行批量操作,或是避免在选择不合适的物体时脚本报错:
import re
for object in bpy.context.selectable_objects:
print("Selected:",object.name)
#来点桃子
if re.search(r"*049mom*", object.name):
object_sel=object.name
break
bpy.context.view_layer.objects.active = bpy.context.scene.objects[object_sel] 注意:如果在脚本在运行过程报错,会直接跳出而非继续运行,因此需要使用合适的判断语句来避免在可能报错(比如input为空,或是input变量类型错误)的情况下执行命令。
3. 如果要给blender装额外的py库,则需要去blender自带的Python目录(blender文件夹\python\bin\python.exe)下起shell开pip install,详细方法在开头引用的参考文章里“[Blender Python] 安装Python包”一节中可以找到。
4. 对于blender的外部插件同样可以用“python工具提示”来查看对应命令,比如改模时常用的mmd_tools插件的“转换模型”按钮:
>>> bpy.ops.mmd_tools.convert_to_mmd_model()
{'FINISHED'}
你已经学会了Blender脚本的基础编写方式,现在尝试在改模时把改名插件送进回收站吧!
给定一个头部fbx模型,将其作为Armature导入blender并选中后:
1. 列出该Armature的名字与其包含的总骨骼数量
2. 找到位于颈部和头部的两个骨骼,分别改名为“首/頭”,且返回赋值结果
将任务拆分一下:
-模型名称和骨骼信息都可以从bpy.context里找到
-通过自定义字典来建立映射关系
-如果骨骼名称包括在字典内,就对骨骼进行改名
开码:
import bpy
print("=== Start ===")
#命名用字典,这部分可以自定义
dic_test = {
"Neck":"首",
"Head":"頭"
}
#获取物体信息
obj=bpy.context.active_object
print("Armature selected:",obj.name)
print("The current armature has",len(obj.data.bones),"Bones")
#查找与替换
for single_bone in obj.data.bones:
rename=""
name_in=single_bone.name
#不匹配时跳出循环以免报错
if name_in not in dic_test.keys():
continue
else:
rename=dic_test[name_in]
#对骨骼进行赋值改名
bpy.context.active_object.data.bones[name_in].name = rename
print("bone",name_in,"has been renamed to",rename)
print("=== Done ===") 然后跑下脚本:

一个可挂载自定义字典的骨骼重命名工具——有效代码不到20行。
思考题
-能改名的可以是模型的骨骼,也可以是动作的骨骼,还可以是镜头动画。
-赋值能实现的远远不只是“改名”,更是包括了”对物体应用骨骼动画“,“开启/关闭形态键”,“给材质上贴图”等应用类操作,一些强大的插件甚至可以实现对材质与纹理的快速切割/分层。
-除了bpy.context和bpy.data外,还有输入输出文件的bpy.path句柄,原则上来说可以实现从文件输入到输出的一条龙操作。
题外话
其一
Steam上的blender可以通过设置来自选版本(并且每个版本都有各自的独立维护与更新),使用体验更好点,还可以让好友看到自己开着blender敬业,不过有个小缺点是每次更新大版本需要重新手动开启插件。

对了,神之天平真的挺好玩。
其二
虽然说的晚了点,之前专栏里使用过的部分操作也可以被脚本代替掉——有时间的话或许会回去补充点内容,也可以作为不错的习题来练手。
其三
虽然百10th活动结束了,还是挺好奇活动每日播片里的高精度模型以后是否有机会实装进游戏...回头再看着现行的纸板裙反而略显奇怪了。
不过往好了想,除了比较消耗事务员以外,正是简单而标准化的模型,才能让土豆在衣服上有着那么高的产能。
本期专栏头图
既然随机到了,那就斗胆借题发挥一下...
红桃赛季没把这组安排到一起对我来说还是挺遗憾的。
Copyright©
BANDAI NAMCO
看着闪5Day1和某位作曲家的庭审记录给这篇专栏做了最后的校对,东西写完,血压也爆了。
这次的课题不完全是MMD相关,但还是那句话,要做什么就学什么。