关于 blender-python-addon
这项开发技能介绍如何使用 `bpy` API 构建 Blender 插件,涵盖项目结构、界面、安全场景操作、注册、测试与打包。
主要功能
- 01插件包与清单结构
- 02操作器、面板和属性
- 03安全的场景与网格操作
- 04注册和清理规范
- 05跨 Blender 版本测试
- 06打包扩展和插件
适用场景
- 01搭建新的 Blender 插件
- 02在 Blender 界面中提供操作和设置
- 03检查插件能否正确注册与卸载
- 04准备可安装的插件包
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill blender-python-addon安装后在 Claude Code 中输入/blender-python-addon确认已加载。
详细介绍
Blender Python 插件开发
本技能介绍如何使用 bpy API 构建 Blender 插件,包括插件结构、操作器、面板、属性、安全地操作场景,以及跨 Blender 版本测试。
Blender 插件构建工作流
- 搭建插件框架 — 创建一个以
__init__.py为入口的包,并使用blender_manifest.toml(Blender 4.2+ 扩展)或bl_info字典(传统插件)描述名称、版本和支持的 Blender 版本。 - 定义数据 — 创建
PropertyGroup类来管理成组设置,并将其注册到相应的 ID 类型上(例如,bpy.types.Scene.my_addon = PointerProperty(type=MyAddonSettings))。 - 实现操作器 — 为每项用户操作派生
bpy.types.Operator子类;实现poll()来检查是否可用,使用invoke()进行交互式设置,并通过execute()执行实际操作。 - 构建 UI 面板 — 派生
bpy.types.Panel子类,在相应编辑器中显示操作器和属性(3D 视口侧栏、属性编辑器等)。 - 注册所有内容 — 在
classes元组中列出所有类,并在对应的register()/unregister()函数中完成注册和注销;属性指针应与相应的类一并注册。 - 在干净的配置中测试 — 使用
--factory-startup启动 Blender,安装并启用插件,测试每个操作器,然后禁用插件并确认没有残留。 - 打包并发布 — 将插件文件夹压缩为 ZIP(或构建
.zip扩展包),并确认可通过 Blender 的 Preferences > Add-ons(或 Extensions)面板顺利安装。
插件结构
- 将插件入口放在
__init__.py中,并提供清晰的register()和unregister()函数,使二者严格对应(所有已注册内容都必须按相反顺序注销)。 - 当插件规模不再简单时,将操作器、面板、属性、偏好设置和实用工具代码拆分到不同模块;再从
__init__.py导入并注册它们。 - 根据目标 Blender 版本和打包方式,使用
bl_info(4.2 之前的传统插件)或blender_manifest.toml(4.2+ 扩展),不要混用两种约定。 - 保持 UI 标签简洁;如果项目支持本地化,可使用
bpy.app.translations或"Category"/标签约定来本地化面向用户的文本。
API 用法
- 使用
bpy.types.Operator实现操作,使用bpy.types.Panel构建 UI 布局,并使用bpy.types.PropertyGroup管理相关设置。 - 为每个操作器显式定义
bl_idname(小写,采用category.action格式,例如object.apply_custom_modifier)、bl_label和bl_options(例如{'REGISTER', 'UNDO'})。 - 在
poll()中验证上下文,确保操作器可以运行——检查是否存在活动对象、模式是否正确或选择是否有效,让操作器按钮在不可用时显示为灰色,而不是运行时报错。 - 使用
invoke()进行交互式设置(显示对话框、读取鼠标位置),并使用execute()执行实际操作;如果invoke()不需要额外交互,应调用self.execute(context)。 - 成功时返回
{'FINISHED'},失败或用户取消时始终返回{'CANCELLED'}——不要返回单独的None或无法识别的字符串。 - 读取包含修改器、形态键或其他由依赖关系图驱动的结果的最终场景状态时,使用
bpy.context.evaluated_depsgraph_get()和object.evaluated_get(depsgraph)。
示例:操作器、面板和注册
bl_info = {
"name": "Random Vertex Color",
"author": "Example",
"version": (1, 0, 0),
"blender": (4, 0, 0),
"category": "Mesh",
}
import bpy
import random
class MESH_OT_random_vertex_color(bpy.types.Operator):
"""Assign a random color to the active vertex color layer"""
bl_idname = "mesh.random_vertex_color"
bl_label = "Randomize Vertex Colors"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
obj = context.active_object
return (
obj is not None
and obj.type == "MESH"
and obj.mode == "OBJECT"
and obj.data.color_attributes.active_color is not None
)
def execute(self, context):
obj = context.active_object
color_layer = obj.data.color_attributes.active_color
for data in color_layer.data:
data.color = (random.random(), random.random(), random.random(), 1.0)
obj.data.update()
self.report({"INFO"}, f"Randomized colors on '{obj.name}'")
return {"FINISHED"}
class VIEW3D_PT_random_vertex_color(bpy.types.Panel):
bl_label = "Vertex Color Tools"
bl_idname = "VIEW3D_PT_random_vertex_color"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "Tool"
def draw(self, context):
layout = self.layout
layout.operator(MESH_OT_random_vertex_color.bl_idname, icon="COLOR")
classes = (
MESH_OT_random_vertex_color,
VIEW3D_PT_random_vertex_color,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
if __name__ == "__main__":
register()
数据和属性
- 通过
PropertyGroup类注册自定义属性,而不是将零散的全局状态塞进模块级变量中;模块级变量在文件重新加载后不会保留,也无法安全撤销。 - 在注册了
bl_idname且其值与插件模块名称一致的AddonPreferences子类中存储插件偏好设置(API 密钥、默认路径、可跨文件保留的 UI 开关)。 - 使用
PointerProperty、CollectionProperty和类型完整的属性(StringProperty、FloatProperty、BoolProperty等),并显式设置name=和description=,让工具提示和 Python API 自我说明。 - 在
unregister()中清理自定义属性、bpy.app.handlers条目、计时器和键位映射——添加到bpy.types.*类或处理器列表中的任何内容都必须显式移除。
安全性和性能
- 未经用户明确操作,绝不执行破坏性场景操作(删除对象、覆盖文件);对于任何破坏性操作,都不得在加载时静默自动执行。
- 避免在模态操作器中阻塞 UI 线程;对于长时间运行的操作,应使用
context.window_manager.event_timer_add()和模态状态机,而不是紧密循环。 - 批量处理网格编辑;以编程方式编辑网格数据时,结构性更改应使用
bmesh模块(bmesh.from_edit_mesh、bmesh.new()),而不是逐个循环处理mesh.vertices。 - 避免反复扫描大型场景,或在
draw()方法中重复计算开销很大的数据——draw()会在每次 UI 重绘时运行,因此应缓存结果,并仅在底层数据发生变化时使缓存失效。 - 保持文件路径可配置,并使用 Blender 路径工具(
bpy.path.abspath、bpy.utils.resource_path)解析路径,而不是硬编码特定操作系统的路径。
测试和调试
- 在干净的 Blender 配置文件(
blender --factory-startup)和具有代表性的生产场景中进行测试:前者可发现对其他插件的隐藏依赖,后者可发现性能和数据结构问题。 - 添加冒烟测试:导入插件模块,调用
register(),通过bpy.ops运行每个核心操作器,然后无错误、无残留状态地调用unregister()。 - 使用
self.report({'ERROR'}, "message")或{'WARNING'}/{'INFO'}向用户反馈操作器信息,而不是打印到控制台——大多数用户都看不到控制台。 - 将特定版本的 API 差异(例如 Blender 3.x 和 4.x 之间的 API 变化)封装在小型辅助函数中,避免在插件其他部分散布版本检查。
常见错误
- 忘记在
unregister()中注销类、处理器、计时器和键位映射,导致禁用插件后 Blender 处于异常状态。 - 在
Panel.draw()方法中修改 Blender 数据(添加对象、更改网格数据)——draw()只能读取数据并布局 UI。 - 未先检查
context,就假定存在活动对象、非空选择或特定模式(Object/Edit/Sculpt)。 - 硬编码仅在开发者机器上存在的绝对资源路径,而不是使用相对路径或
bpy.utils.resource_path。
评论
支持 Markdown