3.3 菜单、快捷键与Gizmo实战


3.3 菜单、快捷键与 Gizmo 实战

本节摘要:动手环节。我们给第 2 章的参数化插件补齐完整入口:自定义菜单挂进内置菜单、插件级键映射注册快捷键、渐进式多面板组织参数,再实现一个跟随选中物体的视口手柄(Gizmo),串起事件归一化与按需绘制。顺手讲清"交互语义污染"这个生态级问题——为什么不该占用右键。

本节目标

阅读完本节,你应当能够:

  1. 把自定义菜单追加到内置菜单尾部,并在注销时干净移除;
  2. 用插件级键映射注册快捷键,理解其作用域语义;
  3. 用父子面板组织"主开关加子参数"的渐进式界面;
  4. 实现一个最小 Gizmo 组:准入、装配、事件响应;
  5. 判断自己的交互设计是否污染了用户肌肉记忆。

一、菜单:给你的工具一个正经入口

面板之外,菜单是第二入口。自定义菜单类的结构与面板类似:声明标识、标签,draw 里摆菜单项。关键动作是"追加"——把你的菜单项挂到某个内置菜单的尾部:

class VIEW3D_MT_rename_menu(bpy.types.Menu): bl_idname = "VIEW3D_MT_rename_menu" bl_label = "批量重命名" def draw(self, context): layout = self.layout layout.operator("object.batch_rename_v2", text="执行重命名") layout.prop(context.scene.rename_tool, "prefix") def menu_func(self, context): self.layout.separator() self.layout.menu(VIEW3D_MT_rename_menu.bl_idname) def register(): bpy.utils.register_class(VIEW3D_MT_rename_menu) bpy.types.VIEW3D_MT_object_context_menu.append(menu_func) def unregister(): bpy.types.VIEW3D_MT_object_context_menu.remove(menu_func) bpy.utils.unregister_class(VIEW3D_MT_rename_menu)

三个要点。第一,追加与移除必须成对,函数对象要同一个——这就是 1.4 节处理器纪律在界面层的翻版。第二,菜单项上可以直接摆属性控件(如示例里的前缀输入),让轻量调整不用打开面板。第三,想找到"该挂到哪个内置菜单",用 1.2 节的界面透视开关,直接看目标菜单的标识符——不要靠猜。

二、快捷键:插件级键映射的正确姿势

快捷键注册的完整链路是:窗口管理器的插件级键配置里新建键映射(指定空间类型),再在键映射里添加键映射项(指定按键、事件值、触发的操作符):

addon_keymaps = [] def register_hotkey(): wm = bpy.context.window_manager kc = wm.keyconfigs.addon if kc: km = kc.keymaps.new(name="3D View", space_type='VIEW_3D') kmi = km.keymap_items.new( "object.batch_rename_v2", type='R', value='PRESS', ctrl=True, shift=True, alt=False, ) addon_keymaps.append((km, kmi)) def unregister_hotkey(): for km, kmi in addon_keymaps: km.keymap_items.remove(kmi) addon_keymaps.clear()

要点有三。其一,键映射按区域类型分层组织——同一个键在 3D 视口是移动、在节点编辑器是别的操作,这是区域主权在输入层的推论;注册时必须显式指定空间类型,否则快捷键如幽灵飘在虚空。其二,插件级键配置是专门给插件用的沙盒,用户可在偏好设置里查看与改键,你的注册不会霸占用户配置。其三,注销时逐项移除并存进列表管理——散着存必然漏删。

⚠️ 常见坑:抢占高频单键。给插件绑 Q、W 这类裸单键,等于对用户的肌肉记忆宣战——生态里大量冲突投诉源于此。约定俗成的安全区是三键组合(Ctrl 加 Shift 加字母)或双键组合,且优先让用户在偏好设置里自行改键。

三、渐进式面板:主开关加子面板

复杂工具的参数按"使用频率与认知深度"分层。主面板只放总开关与最常用参数;深层参数放进声明了父面板标识的子面板:

class OBJECT_PT_rename_main(bpy.types.Panel): bl_label = "批量重命名" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "我的工具" def draw(self, context): cfg = context.scene.rename_tool self.layout.prop(cfg, "prefix") self.layout.operator("object.batch_rename_v2") class OBJECT_PT_rename_advanced(bpy.types.Panel): bl_label = "高级选项" bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_category = "我的工具" bl_parent_id = "OBJECT_PT_rename_main" # 挂到主面板下 bl_options = {'DEFAULT_CLOSED'} def draw(self, context): cfg = context.scene.rename_tool self.layout.prop(cfg, "padding") self.layout.prop(cfg, "start")

子面板自动嵌在主面板内、默认折叠——用户需要时展开,不需要时视而不见。这与 3.1 节的"渐进式面板"模式完全对应,成本只是两个声明字段。

四、Gizmo:一个最小的视口手柄

Gizmo 的组织单元是交互组件组(GizmoGroup):准入方法决定何时出现,装配方法创建手柄,手柄自身响应事件。一个让选中物体绕自身轴旋转的最小示例骨架:

class VIEW3D_GGT_rotate_handle(bpy.types.GizmoGroup): bl_idname = "VIEW3D_GGT_rotate_handle" bl_label = "旋转手柄" bl_space_type = 'VIEW_3D' bl_region_type = 'WINDOW' @classmethod def poll(cls, context): obj = context.active_object return obj is not None and obj.select_get() def setup(self, context): gz = self.gizmos.new("GIZMO_GT_arrow_3D") gz.use_draw_modal = True gz.target_set_prop("offset", context.active_object, "rotation_euler", index=0) self.rotate_gz = gz def refresh(self, context): obj = context.active_object if obj: # 拓扑一致性:矩阵跟随物体姿态 self.rotate_gz.matrix_basis = obj.matrix_world.normalized()

短短几行里藏着 3.2 节的三大契约落地:poll 只在"有选中物体"时放行(语义准入);手柄的偏移直接绑定到物体的旋转属性(事件归一化——拖动手柄产生的语义增量写进属性,不关心你是鼠标还是数位板);刷新时矩阵跟随物体世界矩阵(拓扑一致性——转了 90 度后轴向仍指向用户定义的方向)。

绑定目标属性是 Gizmo 的核心便利:target_set_prop 把手柄的交互量与 RNA 属性路径关联,拖动即写值、自动进撤销栈——你不用写一行模态代码就得到一个可撤销的三维控件。需要更自由的交互时,才升级到手柄的事件方法(捕获、拖动、释放、退出)自行处理增量。

💡 关键直觉:能用属性绑定就别手写模态。Gizmo 的属性绑定把"事件解析、增量计算、撤销登记"三件事全托管了,手写模态是最后手段而非炫技场。

五、交互语义污染:生态级的礼貌

最后一个话题关乎生态而非代码。当不同插件对同一视觉元素赋予截然不同的操作含义——原生右键是取消选择、A 插件把它变成快速布尔、B 插件变成切轴向——用户不是在学习多个工具,而是在不断覆盖自己的肌肉记忆。这种"交互语义污染"造成的损耗,不亚于性能瓶颈。

三条自律原则。不覆盖高频原生键位,尤其单键与双键组合。交互模式遵循平台惯例——确认用左键、取消用右键或 Esc,不要发明新语义。入口给用户留退路——所有快捷键可改、所有手柄可关。插件的"灵魂"在解决问题的锐度,锐度不该以磨损用户已有技能为代价。

注册物 生命周期纪律 生态礼仪
菜单项 追加与移除成对,同一函数 挂在语义相关的菜单下
快捷键 列表管理逐项删 避开高频单键,允许改键
子面板 随插件注销自动消失 默认折叠,按需展开
Gizmo poll 控制出现时机 可关闭,不抢原生手柄

六、入口体系的收尾与自检

至此,插件的入口体系完整了:面板承载日常、菜单承载发现、快捷键承载高频、手柄承载空间操作。收尾时做一轮入口自检,四个问题过一遍。

**入口是否冗余?**每个功能至少一个入口、至多三个(面板加菜单加快捷键)。零入口的功能等于不存在;四五个入口的功能是维护负担。**命名是否一致?**同一个动作在面板、菜单、搜索里的文案应当一致——用户用搜索找到的命令,点开面板还得再认一遍名字,就是命名失职。**键位是否可退?**所有快捷键都能在偏好设置里找到并修改,所有手柄都能在插件设置里关闭。**禁用是否干净?**关闭插件后,菜单追加、键映射、手柄全部消失,不留任何残影。

手柄和模态怎么选?

经验法则:交互的对象是"某个属性的连续调整"选手柄(绑定属性后白得撤销与事件处理),交互的过程是"多步骤的流程"选模态(框选、预览、确认三段式)。两者也能组合:手柄负责微调,模态负责流程编排。判断的锚点还是那句话——用户的意图是什么形状,交互就长什么形状。

为什么我的快捷键在别人的机器上不生效?

三个可能:键映射的空间类型与用户的使用场景不符(你注册在 3D 视口,用户在节点编辑器按);键位与用户已装插件冲突(后注册者胜,你的被覆盖);用户手动禁用了插件键映射。处理策略:文档里写清键位的生效范围与修改方法;选择冷门组合降低冲突概率;提供面板入口作为兜底——快捷键是加速器,不该是唯一通道。

七、入口的国际化与无障碍

入口体系的最后两个维度容易被忽略:语言与可达性。

**文案的本地化。**界面标签、菜单名、错误提示都可能需要翻译。属性工厂的描述字段、枚举的显示名都是为本地化预留的槽位;硬编码在代码里的中文字符串则要集中管理(一个翻译字典模块),不要散落各处。提前做这一步,将来接手翻译的志愿者会感激你;散装的字符串则几乎不可能被完整翻译。

**可达性 basics。**三个低成本动作:所有图标按钮配文字提示(悬浮提示字段就是干这个的);颜色不只用于传达信息(红绿之外加图标或文字,照顾色觉障碍用户);快捷键避免依赖小键盘(笔记本用户常没有)。这些不影响主流程,却决定了一部分用户"能不能用"而非"用得好不好"。

把入口当产品做

最后升一个维度:入口体系本身就是产品。用户对插件的全部认知,都从入口开始——搜索时看到的名字与描述、启用后找到的第一个面板、按下的第一个快捷键。把这三个"第一触点"打磨到无可挑剔,比多写十个功能更能赢得口碑。功能决定插件能做什么,入口决定用户知道它能做什么——后者常常是更稀缺的资源。

八、本节的三分钟复盘法

学完就忘是交互知识的常态——API 细节忘了可查文档,设计判断力丢了才是损失。建议用"三分钟复盘法"固化本节收获:合上教程,画一张自己插件的入口关系图(面板、菜单、快捷键、手柄各自的位置与职责),标注每个入口的生命周期(何时出现、何时消失、禁用后如何清理)。画不出来的部分,就是下次要用时要去查的部分;画得出来的部分,已经变成了你的设计直觉。

这张图还有第二用途:发布前附进插件文档的"功能地图",用户第一次安装时,靠它三分钟建立全局认知。作者与用户共享一张图,沟通成本降到最低——这大概是"为自己的设计画图"这个习惯最实在的回报。

关于键位的最后一句

快捷键是插件的公共资源消耗——每绑一个,整个生态的键位空间就少一个。发布前做最后一次谦逊检查:这个功能值得用户记一个新键吗?半年后的用户还会高频用它吗?答案犹豫,就把它降级为菜单项。克制不是美德表演,是对生态容量的诚实。

本章回顾

  • 要点一:菜单项追加与移除必须成对且用同一函数对象,配合界面透视找对挂载点;
  • 要点二:快捷键注册进插件级键映射,显式指定空间类型,注销逐项清理;
  • 要点三:父子面板加默认折叠,是最便宜的渐进式界面;
  • 要点四:Gizmo 的属性绑定托管了事件解析、增量计算与撤销登记,优先于手写模态;
  • 要点五:手柄矩阵要跟随物体世界矩阵,保证拓扑一致性;
  • 要点六:不占高频键、遵循平台惯例、留退路——交互设计的生态礼貌。

界面完工。第 4 章解决"用户关掉 Blender 后设置还在吗"——持久化与外部数据。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U