第1章:Blender Python API入门
说实话,很多做硬表面建模的朋友,一开始听到Python都觉得头大。我当年也是这么过来的——打开Blender的文本编辑器,看着空白的窗口,完全不知道从哪下手。但后来我发现,只要搞懂几个核心概念,写插件其实没那么玄乎。
这一章,我们就来拆解bpy模块的四个核心部分。嗯,你把它当成工具箱就行。
1.1 bpy模块概览:整个API的骨架
bpy是Blender Python的根模块。说白了,它就是Blender和Python之间的翻译官。你在Blender界面里做的每一个操作——移动顶点、添加材质、渲染输出——都能通过bpy用代码完成。
我个人习惯把bpy分成三大块:
- bpy.context:当前场景的上下文,比如选中的物体、激活的相机
- bpy.data:Blender的数据块,网格、材质、灯光都存在这里
- bpy.ops:操作符,对应菜单里的每个功能
你想想看,这三块加起来,几乎覆盖了Blender的所有功能。我在项目中遇到过最典型的场景:用bpy.data拿到模型数据,用bpy.context判断当前选了什么,最后用bpy.ops执行操作。一条流水线就出来了。
核心记忆点:
- bpy.context → 当前状态
- bpy.data → 所有数据
- bpy.ops → 执行操作
1.2 bpy.context与bpy.data:一个管状态,一个管数据
这两个东西经常被搞混。我刚开始学的时候也分不清,后来用了一个比喻就记住了:bpy.context是遥控器,bpy.data是仓库。
bpy.context告诉你当前Blender处于什么状态。比如:
# 获取当前选中的物体
selected = bpy.context.selected_objects
# 获取当前激活的物体
active = bpy.context.active_object
# 获取当前场景
scene = bpy.context.scene
我在做硬表面插件时,经常需要判断用户选了几个物体。如果选了多个,我就自动进入批量处理模式。这个判断全靠bpy.context。
bpy.data则不同。它存的是Blender里的所有资源。不管你看不看得到,它们都在那里:
# 遍历所有网格
for mesh in bpy.data.meshes:
print(mesh.name)
# 获取名为"Bevel"的材质
mat = bpy.data.materials.get("Bevel")
# 创建新材质
new_mat = bpy.data.materials.new(name="HardSurface_Mat")
我的经验:用bpy.data.get()比直接用bpy.data["xxx"]安全。因为get()找不到时返回None,不会报错。我在插件里几乎都用get(),避免用户场景里缺少某个资源时直接崩溃。
为什么会这样设计?因为Blender需要区分「当前操作环境」和「永久存储数据」。你想想看,如果你在编辑模式下修改顶点,bpy.context会告诉你当前是编辑模式,但bpy.data里的网格数据直到你确认修改才会更新。这个分离设计,其实是为了操作安全。
1.3 操作符(Operator)与面板(Panel):插件的左右手
操作符和面板,是Blender插件最基础的UI单元。我习惯这么理解:操作符是「做什么」,面板是「在哪做」。
操作符(Operator)
每个操作符都是一个类,继承自bpy.types.Operator。它必须实现execute方法:
import bpy
class MESH_OT_bevel_plus(bpy.types.Operator):
bl_idname = "mesh.bevel_plus"
bl_label = "Bevel Plus"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
# 这里写你的操作逻辑
bpy.ops.mesh.bevel(offset=0.1, segments=3)
return {'FINISHED'}
注意bl_options里的'UNDO'。我曾经漏掉这个,结果用户执行操作后按Ctrl+Z发现撤销不了,被骂惨了。所以记住:凡是会修改数据的操作符,一定要加'UNDO'。
面板(Panel)
面板就是你在Blender侧边栏看到的那些UI区域。它继承自bpy.types.Panel:
class VIEW3D_PT_hard_surface(bpy.types.Panel):
bl_label = "硬表面工具"
bl_idname = "VIEW3D_PT_hard_surface"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "硬表面"
def draw(self, context):
layout = self.layout
layout.operator("mesh.bevel_plus", text="超级倒角")
避坑指南:bl_category是面板在侧边栏的标签页名称。如果你写了中文,有些Blender版本可能显示乱码。我建议用英文或拼音,比如"HardSurface"。
1.4 属性(Property)系统:让用户能调参数
没有属性的操作符,就像没有旋钮的音响——只能开和关,没法调音量。属性系统让用户能在面板上调整参数,然后传给操作符使用。
Blender的属性类型很多,常用的有:
| 属性类型 | 用途 | 示例 |
|---|---|---|
| bpy.props.FloatProperty | 浮点数,比如倒角大小 | offset: bpy.props.FloatProperty(default=0.1, min=0, max=1) |
| bpy.props.IntProperty | 整数,比如分段数 | segments: bpy.props.IntProperty(default=2, min=1, max=10) |
| bpy.props.BoolProperty | 开关,比如是否启用 | use_limit: bpy.props.BoolProperty(default=True) |
| bpy.props.EnumProperty | 下拉菜单 | mode: bpy.props.EnumProperty(items=[...]) |
属性要定义在操作符类里,然后在面板的draw方法中显示出来:
class MESH_OT_bevel_plus(bpy.types.Operator):
bl_idname = "mesh.bevel_plus"
bl_label = "Bevel Plus"
offset: bpy.props.FloatProperty(
name="倒角大小",
default=0.1,
min=0.0,
max=1.0,
unit='LENGTH'
)
segments: bpy.props.IntProperty(
name="分段数",
default=2,
min=1,
max=10
)
def execute(self, context):
bpy.ops.mesh.bevel(
offset=self.offset,
segments=self.segments
)
return {'FINISHED'}
class VIEW3D_PT_hard_surface(bpy.types.Panel):
bl_label = "硬表面工具"
bl_idname = "VIEW3D_PT_hard_surface"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "硬表面"
def draw(self, context):
layout = self.layout
# 显示操作符及其属性
op = layout.operator("mesh.bevel_plus", text="超级倒角")
# 这里可以设置默认值,但通常用属性自己的default就行
我的习惯:给属性加unit参数,比如unit='LENGTH'。这样Blender会自动显示单位,用户一看就知道是毫米还是米。这个小细节,能让插件显得更专业。
知识体系总览
下面这张图,是我自己整理的本章知识结构。你可以把它当成地图,随时回来对照:
这张图把bpy.context、bpy.data、操作符、面板和属性系统的关系画清楚了。你写插件时,基本就是沿着这个流程走:面板上放按钮和参数,用户点击后触发操作符,操作符读取属性值,然后修改bpy.data里的数据。
嗯,这一章的内容就到这里。记住这四个核心概念,后面写具体插件时你会觉得顺手很多。我曾经带过不少新人,他们最大的问题就是没搞懂这几个模块的分工,结果代码写得乱七八糟。你只要把这张图印在脑子里,至少能少走一半弯路。
公众号:蓝海资料掘金营,微信deep3321