前言
最近刚好有用 Blender 开发工具,于是研究了一下如何使用 Blender Python 来实现简单的工具。
Blender 印象
虽然久仰 Blender 大名了,一直吐槽 Maya 的不思进取,将 Blender 奉为圭臬。但是其实我并没有使用过它。 目前Blender 已经到 2.9 的版本,界面风格都好看了不少,整体透露着精致。 以前老版本的界面和 Unity 的灰色界面一个调性,说实话我还是喜欢深色一点风格。 而且 blender 整体的大小优化得很好,功能也很齐全,启动速度还快了很多。 然而遗憾的是终究没有太多时间深入来学习它,毕竟对我目前而言,学习 Houdini 更为重要。
Blender 下载&界面
Blender 下载可以去到 Blender 官网下载地址
点击大大的下载按钮下载安装即可。 安装上其实无脑按确认和下一步即可。 安装完成之后可以打开界面如下图所示。
默认会启动一个界面进行快速配置。
快捷键 shortcut 推荐使用 industry compatible 毕竟我好歹也是 Maya 用户迁移过来的,Maya Houdini Unreal 主流的界面操作方式已经成为标准了。 所以使用这个就不用去研究 Blender 自身的三维控制操作,让我舒服了很多。
在上面的界面点击确认会在 %appdata%\Roaming\Blener Foudnation\Blender\${version}\config
这个路径下创建 userpref.blend 的文件。 如果配置错了,可以把这个文件删除,重启 Blender 可以重新配置。
Blender 编程界面准备
新版本的 Blender 已经将不同的功能界面整合到最上面了。 编程方面可以直接选择最右边的 Scripting
Blender 使用 Python3 ,可以在这个界面下新建一个文本编写代码。 代码 print 的信息可以在 Windows > Toggle System Console
打开查看信息。 链接 如果没有双屏,可以使用 PowerToys 快速分屏将 Blender 的窗口和 Console 摆放好,方便查看输出。
另外如果可以打印到 Python Console 也挺好的,所以我找到这个链接 通过重载 print 方法可以实现。
bpy 编程
blender 内置了 bpy 模块,从而可以通过 Python 代码来自动化 Blender 。 学习编程还是离不开从文档开始 链接 blender.stackexchange 是个非常好的提问网站,比起 Maya 的编程,这里有大量大佬提供了很多问题的解决方案。
关于 Blender 的 GUI 编程可以参考官方文档 Panel 相关的介绍 链接 Blender 并不是用 Qt 框架写的,所以界面上稍微没有那么灵活,不过也可以通过 Python 安装 PyQt 或者 PySide2 相关的 GUI 库来编写界面。
GUI 界面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import bpyclass HelloWorldPanel (bpy.types.Panel): bl_idname = "OBJECT_PT_hello_world" bl_label = "Hello World" bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "object" def draw (self, context ): self.layout.label(text="Hello World" ) bpy.utils.register_class(HelloWorldPanel)
官方案例运行起来如上图所示。 注意: 官方要求 bl_idname
必须 CATEGORY_PT_name
一这样的形式命名,否则 Console 会弹出警告。
更多 Blender 插件是直接配置 Category
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import bpyclass HelloWorldPanel (bpy.types.Panel): bl_idname = "OBJECT_PT_hello_world2" bl_label = "Hello World" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_context = "objectmode" bl_category = "MyCategory" def draw (self, context ): self.layout.label(text="Hello World" ) bpy.utils.register_class(HelloWorldPanel)
blender 的 GUI 编程目前我接触得很不深,初步使用和 Mel GUI 编程很类似,通过 layout 布局进行配置 链接 layout 通过 colum
grid_flow
等不同的方法进行 GUI 排列。 虽然没有 Qt 编程那么灵活,但是胜在简单明了。
另外 Blender 还支持通过 property 配置生成 GUI 的操作, 类似 Maya 的这个 qargparse 库 链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 import osimport bpyfrom bpy.props import StringProperty, PointerPropertyfrom bpy.types import Panel, PropertyGroup class MyProperties (PropertyGroup ): directory : StringProperty( name="" , description="Path to Directory" , default="" , maxlen=1024 , subtype='DIR_PATH' ) bpy.utils.register_class(MyProperties) class ButtonOperator (bpy.types.Operator): bl_idname = "mf_blender.select_path" bl_label = "Print Directory" def execute (self, context ): scn = context.scene path = scn.my_tool.directory print (path) return {'FINISHED' } bpy.utils.register_class(ButtonOperator) class OBJECT_PT_DirectoryPanel (Panel ): bl_idname = "OBJECT_PT_Directory" bl_label = "Directory Panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "MyPanel" bl_context = "objectmode" def draw (self, context ): layout = self.layout scn = context.scene col = layout.column(align=True ) col.prop(scn.my_tool, "directory" , text="directory" ) col.operator("mf_blender.select_path" ) bpy.utils.register_class(OBJECT_PT_DirectoryPanel) bpy.types.Scene.my_tool = PointerProperty(type =MyProperties)
上面的脚本实现获取路径,点击按钮打印输出路径。 Blender 会根据 StringProperty 的配置自动生成获取路径的 UI 通过 Scene 绑定 property 来实现数据共享。
当然上面的操作获取路径比较繁琐,能否只通过一个按钮来获取路径并且打印呢? 当然也是可以的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import osimport bpyfrom bpy.props import StringProperty, PointerPropertyfrom bpy.types import Panel, PropertyGroup class ButtonOperator (bpy.types.Operator): bl_idname = "mf_blender.select_path" bl_label = "Import Directory" directory : StringProperty( name="" , description="Path to Directory" , default="" , maxlen=1024 , subtype='DIR_PATH' ) def invoke (self, context, event ): context.window_manager.fileselect_add(self) return {'RUNNING_MODAL' } def execute (self, context ): print (self.directory) return {'FINISHED' } bpy.utils.register_class(ButtonOperator) class OBJECT_PT_DirectoryPanel (Panel ): bl_idname = "OBJECT_PT_Directory" bl_label = "Directory Panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = "MyPanel" bl_context = "objectmode" def draw (self, context ): layout = self.layout scn = context.scene col = layout.column(align=True ) col.operator("mf_blender.select_path" ) bpy.utils.register_class(OBJECT_PT_DirectoryPanel)
材质节点自动化 1 2 3 4 5 6 7 8 9 10 11 12 import bpyobj = bpy.data.objects["Cube" ] slot = obj.material_slots["Material" ] print (slot) mat_name = slot.name print (mat_name) material = slot.material print (material)
上面的方法可以获取到特定的材质 objects
material_slots
这些方法在 Blender 里面既可以用数组的序号获取也可以用字典的名称来获取。
1 2 3 4 5 6 7 8 9 10 matnodes = material.node_tree.nodes texture=matnodes.new("ShaderNodeTexImage" ) image_path = r"C:\Users\timmyliang\Pictures\Sketchpad.png" texture.image = bpy.data.images.load(image_path) channel = matnodes['Principled BSDF' ].inputs["Base Color" ] material.node_tree.links.new(channel, texture.outputs["Color" ])
上面执行可以实现贴图节点连接到材质的颜色通道上。
开启加载脚本 - 配置顶部菜单项 https://blender.stackexchange.com/questions/2738
根据上面的回答,可以配置用户的 Scripts 目录路径,然后再路径下添加 startup 文件夹实现开启 Blender 自动执行。
https://devtalk.blender.org/t/blender-user-scripts-and-multiple-paths/10395
修改用户的 Blender 配置比较麻烦,可以用上面链接提到的环境变量来配置 Blender 的启动路径
$BLENDER_USER_CONFIG Directory for user configuration files.
$BLENDER_USER_SCRIPTS Directory for user scripts.
$BLENDER_SYSTEM_SCRIPTS Directory for system wide scripts.
有了这些之后就可以实现类似 Maya userSetup.py 脚本的效果。
下面我们想要实现 Blender 添加顶部自定义菜单,点击菜单弹出提示弹窗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import bpyfrom bpy.utils import register_classclass WM_OT_myOp (bpy.types.Operator): bl_label = "Alert Dialog" bl_idname = "wm.myop" bl_options = {'REGISTER' , 'UNDO' } def execute (self,context ): return {'FINISHED' } def draw (self,context ): self.layout.label(text="Custom Dialog Message" ) def invoke (self,context,event ): return context.window_manager.invoke_props_dialog(self) register_class(WM_OT_myOp) bpy.ops.wm.myop('INVOKE_DEFAULT' )
上面的脚本可以实现自定义弹窗。
更简单的弹窗配置方案 链接
1 2 3 4 5 6 import bpydef show_message (message="" , title="Message Box" , icon="INFO" ): bpy.context.window_manager.popup_menu( lambda self, _: self.layout.label(text=message), title=title, icon=icon )
下面就是想办法配置顶部的菜单项然后触发上面的 Operator 弹窗
https://blender.stackexchange.com/questions/156652
根据上面的链接可以知道,自定义菜单栏添加可以使用 TOPBAR_MT_editor_menus.append
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import bpyfrom bpy.utils import register_classclass WM_OT_MessageDialog (bpy.types.Operator): bl_label = "Alert Dialog" bl_idname = "wm.message_dialog" def execute (self, context ): return {"FINISHED" } def draw (self, context ): self.layout.label(text="Custom Dialog Message" ) def invoke (self, context, event ): return context.window_manager.invoke_props_dialog(self) class TOPBAR_MT_custom_menu (bpy.types.Menu): bl_label = "Custom Menu" def draw (self, context ): layout = self.layout layout.operator(WM_OT_MessageDialog.bl_idname, text="Open Message Dialog" ) @classmethod def menu_draw (cls, self, context ): self.layout.menu(cls.__name__) register_class(WM_OT_MessageDialog) register_class(TOPBAR_MT_custom_menu) bpy.types.TOPBAR_MT_editor_menus.append(TOPBAR_MT_custom_menu.menu_draw)
bl_idname 没有设置自动用 class 的名称作为 id 链接
总结
Blender 工具开发接触还比较浅,目前体验的感觉是灵活度欠缺了很多,用惯了 Qt 框架,面对新的框架编程充满了不习惯。 可能还是因为没有找到好用的 ui 可视化界面工具吧,比较怀念 QtDesigner 的开发体验。 (接触尚浅,太菜了,有好的经验欢迎大家分享。) 不过 Blender 虽然没那么灵活,也实实在在的简单高效~