第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会自动显示单位,用户一看就知道是毫米还是米。这个小细节,能让插件显得更专业。

知识体系总览

下面这张图,是我自己整理的本章知识结构。你可以把它当成地图,随时回来对照:

Blender Python API 核心结构 bpy 模块 bpy.context 当前状态 selected_objects active_object / scene bpy.data 数据仓库 meshes / materials objects / cameras bpy.ops + Operator 执行操作 execute() 方法 bl_idname / bl_label Panel UI 容器 draw() 方法 bl_category / bl_space_type 属性系统 (Property) FloatProperty / IntProperty / BoolProperty / EnumProperty 定义在 Operator 类中 → 在 Panel 的 draw() 中显示 → 用户调整 → execute() 读取 核心流程:Panel 显示 UI → 用户调整 Property → Operator 执行操作 → 修改 bpy.data

这张图把bpy.context、bpy.data、操作符、面板和属性系统的关系画清楚了。你写插件时,基本就是沿着这个流程走:面板上放按钮和参数,用户点击后触发操作符,操作符读取属性值,然后修改bpy.data里的数据。

嗯,这一章的内容就到这里。记住这四个核心概念,后面写具体插件时你会觉得顺手很多。我曾经带过不少新人,他们最大的问题就是没搞懂这几个模块的分工,结果代码写得乱七八糟。你只要把这张图印在脑子里,至少能少走一半弯路。


公众号:蓝海资料掘金营,微信deep3321