本节摘要:动手环节。我们从零写一个"选中物体批量重命名"插件:先做最小 Operator 并在脚本编辑器里跑通,再补上参数属性、错误报告、撤销支持,然后挂一个侧边栏面板,最后加上插件元数据让它成为可安装的 Add-on。每一步都先跑起来再解释原理,跑不通的代码不算学会。
阅读完本节,你应当能够:
打开 Blender 的脚本编辑器,新建文本,敲入下面这段(这是概念骨架,逐行可跑):
import bpy class OBJECT_OT_batch_rename(bpy.types.Operator): bl_idname = "object.batch_rename" bl_label = "批量重命名" bl_options = {'REGISTER', 'UNDO'} prefix: bpy.props.StringProperty(name="前缀", default="asset_") def execute(self, context): selected = context.selected_objects if not selected: self.report({'WARNING'}, "没有选中任何物体") return {'CANCELLED'} for i, obj in enumerate(selected, start=1): obj.name = f"{self.prefix}{i:03d}" self.report({'INFO'}, f"已重命名 {len(selected)} 个物体") return {'FINISHED'} bpy.utils.register_class(OBJECT_OT_batch_rename)
点运行,然后按 F3 搜索"批量重命名"并执行——选中三个物体试试,名字全部变成带序号的格式。改一下前缀再跑,确认参数生效。按 Ctrl+Z,名字回来了——这就是 bl_options 里 UNDO 的功劳。
这二十行里有几个值得立刻解释的点。bl_idname 必须是"类别.名称"格式且全小写,它是操作符在系统里的唯一标识,F3 搜索、快捷键绑定、菜单调用都靠它。execute 是业务逻辑入口,接收上下文,返回状态码:FINISHED 表示成功,CANCELLED 表示放弃,模态场景下还有 RUNNING_MODAL。self.report 把消息送进 Blender 的通知系统,用户能在状态栏与信息编辑器里看到——比 print 更正规,且能分级为信息、警告、错误。
prefix 用注解语法声明为字符串属性。注意这不是普通的类属性赋值:属性工厂函数向 RNA 注册了一条属性描述,换来的是界面自动渲染、值随撤销栈回滚、参数能被操作历史记录。这就是 1.1 节"契约"的最小体现——你按规范声明,系统替你管生命周期。
上面的版本在没选中物体时会弹警告。更优雅的做法是把"能不能执行"前置为类的准入条件:
@classmethod def poll(cls, context): return context.selected_objects is not None and len(context.selected_objects) > 0
poll 是类方法,返回假时按钮自动置灰、菜单项自动隐藏,用户根本点不到不该点的时机。它和面板的上下文字段(马上会讲)构成双重语义闸门。把环境判断前置,比在执行中补救体面得多——这是"上下文敬畏"守则的第一个落地。
⚠️ 常见坑:在
poll里修改场景。poll在每次界面重绘时都会被调用,里面做写操作等于每帧改一次数据,轻则闪烁,重则崩溃。poll 只读,写入留给 execute。
每次按 F3 搜索太笨了。给插件加一个侧边栏面板:
class OBJECT_PT_batch_rename(bpy.types.Panel): bl_label = "批量重命名工具" bl_idname = "OBJECT_PT_batch_rename" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "我的工具" def draw(self, context): layout = self.layout props = layout.operator(OBJECT_OT_batch_rename.bl_idname) # 属性会自动出现在执行前的浮动面板里 bpy.utils.register_class(OBJECT_PT_batch_rename)
运行后,3D 视口右侧工具栏(按 N 呼出)出现"我的工具"标签页。这段代码的信息密度不低,逐个字段拆:bl_space_type 声明面板属于哪类区域(3D 视口、属性编辑器、节点编辑器各是一套);bl_region_type 声明在区域内哪个层(侧边栏、顶部横条、主窗口);bl_category 决定侧边栏标签页名。draw 每次重绘时被调用,你在里面用布局对象声明控件——layout.operator 放一个按钮,点击即触发对应操作符,参数面板自动弹出。
💡 关键直觉:面板的 draw 是"每次重绘都重新回答一遍此刻该显示什么",不是"配置一次永远有效"。所以 draw 里永远不要缓存计算结果到 self——它的生命周期只有一帧。
要让这段代码成为可安装的插件,还需要元数据与注册入口。整体结构变成一个包含初始化模块的包:
bl_info = { "name": "批量重命名工具", "author": "你的名字", "version": (1, 0, 0), "blender": (4, 2, 0), "location": "3D视口 N 面板 我的工具", "description": "为选中物体按序号批量重命名", "category": "Object", } classes = (OBJECT_OT_batch_rename, OBJECT_PT_batch_rename) 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()
bl_info 是插件的名片:版本号、最低 Blender 版本、入口位置、分类。安装时系统读它来决定兼容性;category 决定它出现在偏好设置的哪个分组里。register 与 unregister 成对出现,前者在启用时被调用,后者在禁用时被调用,注册顺序与注销顺序严格互逆(细节在 1.4 节)。把整个文件夹压缩成 zip 包,在偏好设置的插件页点安装,你的第一个正式插件就上架了——至少在你自己的机器上。
开发期的循环不该是"改代码、重启 Blender"。三个提速技巧:其一,脚本编辑器里选中部分代码按回车只执行选中段,适合反复试一句 API;其二,把调试开关打开,观察你重写的内置按钮的真实标识符;其三,用远程断点在 execute 里停住,看看 context 里到底有什么——很多"为什么是 None"的疑问,看一次就懂了。
| 迭代方式 | 速度 | 适合场景 |
|---|---|---|
| 选中执行选中段 | 极快 | 试探单句 API 行为 |
| 重新运行整个文件 | 快 | 注册类之后的小改动 |
| 重启 Blender | 慢 | 注册结构变更、依赖更新 |
| 远程断点 | 中 | 复杂逻辑与上下文排查 |

跑通基本盘之后,做三个小改造练手。练习一:给批量重命名加一个"仅处理网格物体"的开关,体会枚举与布尔属性在浮动面板里的渲染差异。练习二:把序号起始值也做成参数,并在面板里用一行两列的布局摆整齐——布局的分列与对齐是界面质感的基本功。练习三:故意把操作符标识写成大写,观察注册时报什么错;再故意去掉撤销选项,确认改动确实回不去。错误是教程,报错信息是最好的文档。
| 报错信息 | 根因 | 修复 |
|---|---|---|
| 类注册时提示标识格式错误 | 标识含大写或不带类别前缀 | 改成小写加"类别.名称"格式 |
| 面板不显示 | 区域或层字段写错 | 对照速查图核对五个字段 |
| 按钮永远是灰的 | poll 返回假 | 检查判断条件是否过严 |
| 参数改了没效果 | execute 里没读参数 | 确认逻辑用的是 self 上的属性 |
| 撤销不回去 | 少了撤销选项 | 补上注册与撤销声明 |
两种可能:对方的 Blender 版本低于你声明的最低版本(安装时被拒),或者面板挂在了对方没展开的标签页。前者在元数据里核对版本声明,后者把入口位置写进插件描述,并在文档里给出导航路径。细节虽小,却是第一批用户留存的关键——找不到入口的用户不会来提问,只会卸载。
第一个插件的价值不在功能,在于它会固化成你的肌肉记忆。趁现在代码还短,养成三个动作。
**动作一:报错先读后改。**每次报错把信息完整读一遍再动手——初学者平均只读前半句就开始改代码。报错信息里的类名、标识名、行号是一手情报,养成读完的习惯,排错时间直接减半。配合系统控制台保持开启,情报链路才完整。
**动作二:改完立即测撤销。**每加一段改动数据的逻辑,按一次撤销验证回得去。这个三秒钟的动作,能在发布前拦住最伤信任的一类缺陷——"用了插件文件就坏了"。
**动作三:小步提交。**每完成一个可运行的小功能(一个属性、一个按钮、一个判断),就做一次版本提交。提交信息写"做了什么、为什么",未来的你会感谢现在这个只在五分钟前写代码的人。这三个动作没有任何技术含量,全部收益来自坚持。
基础版有个明显缺陷:前缀每次执行完就忘了,下次要用得重新输入。做一个进阶改造,让插件记住上次用的参数——这恰好是通往第 4 章的桥。
思路一(会话记忆):把前缀存到窗口管理器层的属性上,执行时读它、执行后写它。改动五行,重启前有效。思路二(跨会话记忆):把参数做成偏好设置项,用户在偏好界面改默认值。思路三(工程记忆):把参数放进场景属性组,随工程文件保存——换工程自动换一套。三种思路对应三档需求,实现成本递增,选用时机在第 4 章会给出完整判据。
这个练习的真正目的不是功能,是让你亲身体验"同一个需求,三种存法,三种命运"——数据放哪里,从来不是语法问题,是设计问题。做完它,你对第 4 章的分层存储模型会有一种"终于有人把我说不清楚的感觉讲清楚了"的熟悉感。
这个插件能跑之后,你的学习路线出现了第一个分岔。如果感觉"写起来很顺,就是想知道更多为什么"——直接进第 2 章,契约与数据模型正等着满足你的好奇。如果感觉"步骤都做完了但心里没底"——先别翻页,把本章实战清单的六项逐一做过,尤其第 6 项(反复启停无残留),手感是靠重复建立的。如果你已经迫不及待想给它加功能——可以,但给自己立个规矩:每次只加一个能力,加完就跑一遍撤销与重启验证。小步验证的习惯,从这个玩具插件开始养成,成本最低。
UNDO 选项让改动进撤销栈,这是用户信任的底线;插件能跑了,但为什么注册要逆序注销、热重载为什么会炸?下一节讲生命周期。
写到这里,第一个插件的旅程真正结束了。回头看这不到一百行的代码,它已经具备了正规军的骨架:有元数据、有契约、有界面、有反馈、可撤销、可清理。接下来的五章所做的,不过是往这副骨架里填肌肉与神经——而这个骨架的形状,将决定它们长成什么样子。慎重对待你的第一个插件,它就是你未来所有插件的模板。
带着这个骨架,我们进入第 2 章,看看它脚下的地板究竟由什么构成。