原文:https://wiki.blender.org/index.php/Dev:2.5/Source/Architecture/Operators
概述
在Blender2.50中通过定义一个新的wmOperatorType来创建新的工具。工具运行时,wmOperator被创建,从operator执行到工具结束,它将存储临时数据。需要redo的事放到另外的context中。示例:
1 static void WM_OT_save_homefile(wmOperatorType *ot) 2 { 3 ot->name = "Save User Settings"; 4 ot->idname = "WM_OT_save_homefile"; 5 6 ot->invoke = NULL; //WM_operator_confirm; 7 ot->exec = WM_write_homefile; 8 ot->poll = WM_operator_winactive; 9 10 ot->flag = OPTYPE_REGISTER; 11 }
操作类型有一个用于用户界面的可读name,作为标识的唯一idname。idname格式如PREFIX_OT_do_some_operation。name代表一个动作并且是大写的,如“Do Some Operation”。
操作类型定义了一些RNA_properties。当调用operator时这些属性可被外部设置,并且可以保存用于重做这个操作。
exec回调函数用来运行不需要用户交互的操作。许多操作一开始不需要用户交互,但是对于那些需要交互的操作符,有三个可选的回调invoke, modal和cancel。invoke回调也将执行操作符,但这次可以使用一个事件来获取鼠标坐标。在invoke回调函数中,操作可以将自己添加为一个处理程序,并在modal回调中接收更多事件。如果由于某些外部原因(如程序退出)取消了操作,则cancel回调被调用。每个操作符都必须定义exec或invoke,只有当提供了exec时,才能被录制为宏。
exec回调接受context及其自身的operator。此时,操作可能包含调用操作用户指定的该操作所支持的任何属性子集。通常,此回调中的代码将首先使用其默认值填充所有属性,如果这些属性尚未提供,则执行操作并返回。在执行结束时填写的属性必须足以重新执行操作。
Invoke回调类似于exec,它的附加函数参数是事件。它通常只进行初始化,并在modal回调中处理进一步的事件。处理下一步的事件是通过将operator注册为适当级别的处理器来完成的。由于实现Invoke的操作符也必须实现exec,为了避免代码重复,必须对代码进行拆分。建议定义可以重用的内部init、apply和exit函数。然后,exec回调将通常使用init、apply、exit,invoke回调将使用init和apply,modal和cancel回调将同时使用Apply和Exit。通常,将使用customdata指针来存储操作期间的数据。
exec、invoke、modal和cancel函数返回四种状态之一:pass through(通过)、running modal(modal运行)、cancelled(取消)或finished(完成)。pass through(通过)状态意味着操作通过事件进入其他操作,好像回调没有运行一样,running modal(modal运行)状态表明操作仍在运行,已经注册自身来接受modal回调。cancelled和finished状态代表操作已经结束,无论失败或成功。如果成功结束操作将被注册。
poll回调用于验证操作是否可以在当前上下文中执行。由于一些更具体的原因,操作可能仍然无法执行。此回调函数通常由多个操作共享。在执行操作之前,总是调用poll函数以验证上下文,并且可以用它来让使用此操作的用户界面元素自动进行变灰显示或隐藏。
uiblock(译者:在Blender2.8代码中为ui)回调是用于为操作提供一个用户界面,来重做或重复它。
说明
- 注意RNA属性和customdata之间的区别。属性应该只存储用户看到的公共参数,这些参数作为宏的一部分保存并写入文件。定制数据是一个任意指针,通常指向c结构,该指针仅在操作运行时才存在,因此它不会写入文件。
- invoke回调还将属性作为输入,并应考虑这些属性。例如,变换操作可能有mode=rotate输入,然后应该以旋转模式启动。
约定
- 所有操作应有一个idname、name、description。
- 名字的顺序:(context_OT_subcontext)_(operation)_(可选操作信息)
- 原因是,一个函数用“copy”或“add”开头没有说太多关于它工作的context,特别是当有一个subcontext。它也使查找容易一些。我们总是用动词来分隔信息“它的作用”和它做什么。这提供了关于操作需要输入的直接信息。
- 示例:
- MESH_OT_faces_delete
- MESH_OT_faces_select_similar
- MESH_OT_add_primitive_monkey
- MESH_OT_edges_select_shortest_path
- MESH_OT_edges_select_sharp
- OBJECT_OT_modifier_delete
- OBJECT_OT_modifier_add
- OBJECT_OT_constraint_delete
- OBJECT_OT_constraint_add
- 操作分类,在很大程度上依赖鼠标的输入,或无法重做或者不能用Py API。
- 使用枚举和布尔属性代替INT来处理模式和选项。
- 在适用的情况下使用浮点百分比子类型,范围为0.0-1.0,并在操作中进行任何必要的转换。
- 定义适当的硬/软限制。
- select操作应使用布尔属性“extend”和“deselect”。
- toggle操作应该有一个标准枚举,它有三个选项:set/clear/toggle,toggle作为默认选项。
已知问题
- 依赖鼠标坐标的操作,比如移动区域或者在3D视口中选择,是有问题的。如何重做这样的操作?一种可能性是存储x和y坐标,但是这使得操作作为宏的一部分非常无用。另一种是在宏运行时使用当前的鼠标坐标,但是宏可以从菜单中运行,鼠标坐标没有什么意义。也许后者仍然是最有用的选项,因为它将支持一个像“鼠标选择面,删除选定的脸”这样的宏,尽管执行也依赖于用户输入。
- 目前的解决方案:还是不要费心支持“modal”宏好。如果这样的操作被记录为宏的一部分,仅在某个地方发出警告,表示不支持这种操作。如果有人想要制作这样一个依赖于用户界面交互的复杂宏,应该可以通过将一个操作作为python脚本来实现。
- (TON)目前我们从操作存储或宏中排除选择(上下文更改)。在“move edge”的情况下,可以将(x,y)属性存储在edge附近,以指示哪个属性,以防止“上下文”不得不定义该属性。这样,操作至少可以通过python重用。不过,这不是一个非常有用的案例。:)
- 目前的解决方案:还是不要费心支持“modal”宏好。如果这样的操作被记录为宏的一部分,仅在某个地方发出警告,表示不支持这种操作。如果有人想要制作这样一个依赖于用户界面交互的复杂宏,应该可以通过将一个操作作为python脚本来实现。
- 当第二次执行操作时,是否应该记住它的属性,以便下次执行运算符?如果是这样的话,那么它不应该对所有属性都这样做。例如,平移操作不需要记住平移向量,但是挤出操作符应该记住挤出类型。
- 工具本身也可以记住这一点,保存在工具设置中。
- (TON)特别是它的属性被记住的目的!一个有用的重做或重复是基于拥有的这些属性,并利用上下文。同样地使用同样的操作符将使用这个(如添加球形属性)。
- 工具本身也可以记住这一点,保存在工具设置中。
- 虽然操作的意图是基于重做而不是模态,但这些操作符仍然希望使用快捷键来快速设置某些属性。这与操作的定义,或者更广泛地说,事件系统是如何相适应的,因为它们并不是真正的模态,因此与其他键发生冲突?
- 更多的用户界面讨论,虽然它可能对实现有影响,但这可以添加到操作的设计,如果需要,在实验后,实现一些真正的操作。
- (Ton)对于宏、重做或重复,这种额外的用户输入不应该是必需的。如何为宏定义有用的modal()回调?
- 应在操作类型注册函数中指定支持的属性?这样的规范可用于生成文档、校验Python函数参数或为操作创建自动UI。
- 是的,这应该以某种方式完成。操作属性应该与Blender中的其他属性一样,得到一个UI min/max、 strict min/max、default、tooltip……如何确切地定义这一点,作为“data access api”的一部分,并使用相同的机制实现。
- (TON)好主意...确实需要一些想法!
- 当操作运行模式时,理论上上下文可能会任意更改,从而使操作可能存储的某些指针无效。这是否一个实际的问题,如果是的话,如何避免这种情况?
- 当我们到达那里时,最好解决这个问题。任何依赖于某些窗口/对象/图像的操作都不会被删除,应该是阻塞,或者检查它所运行的数据是否仍然存在。
- (Ton)我不认为这种情况会发生,前提是轮询()回调是正确的,而modal()不会在内部子循环中内改变上下文。
- 当我们到达那里时,最好解决这个问题。任何依赖于某些窗口/对象/图像的操作都不会被删除,应该是阻塞,或者检查它所运行的数据是否仍然存在。
- 事件处理代码在windows、area、region、handler上迭代以执行操作。如果这些操作符在迭代它们时删除其中一个,会发生什么?
- 如果一个操作运行在一个级别上,并且它移除了它上面的某个内容,那么应该通过一个notifier而不是直接的函数调用来完成这个操作。