Dev:2.5/Source/Architecture/Operators

提供: wiki
< Dev:2.5‎ | Source‎ | Architecture
2018年6月29日 (金) 03:36時点におけるYamyam (トーク | 投稿記録)による版 (1版 をインポートしました)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)
移動先: 案内検索

Operators

Overview

Creating new tools in Blender 2.50 is done by defining a new wmOperatorType. When running the tool, a wmOperator is created, which will store temporary data while the operator is executing and after the tool finishes, everything needed to redo the operation in a different context

Example:

 static void WM_OT_save_homefile(wmOperatorType *ot)
 {
 	ot->name = "Save User Settings";
 	ot->idname = "WM_OT_save_homefile";
 	
 	ot->invoke = NULL; //WM_operator_confirm;
 	ot->exec = WM_write_homefile;
 	ot->poll = WM_operator_winactive;
 	
 	ot->flag = OPTYPE_REGISTER;
 }

Each operator type has a human readable name for the user interface, and a unique idname for identifying it. The idname is formatted as PREFIX_OT_do_some_operation. The name represents an action and is capitalized, for example “Do Some Operation”.

Operator types also define a number of RNA properties. These are the properties that can be set externally when calling the operator, and that get saved to redo the operator.

The exec callback is provided to run the operator without user interaction. Many operators will not need user interaction to begin with, but for the ones that do, there are three optional callbacks invoke, modal, and cancel. The invoke callback will also execute the operator, but this time with an event available to get mouse coordinates for example. From the invoke callback, the operator can add itself as a handler, and receive further events in the modal callback. If the operator is cancelled due to some external reason (e.g. the program quitting), the cancel callback is called. Every operator must define either exec or invoke, and only if exec is provided can the operator be recorded for a macro.

Context-operator.png

The exec callback receives a context and its own operator. At this point, the operator may contain any subset of properties supported by this operator specified by the user calling the operator. Typically the code in this callback will first fill in any properties with their default values if they are not provided already, execute the operation and return. The properties filled in at the end of the execution must be sufficient to redo the operation.

The invoke callback is similar to exec, with as an additional function argument an event. It often only does initialization and handles further events in the modal callback. Handling further events is done by registering the operator as a handler at the appropriate level. Since an operator implementing invoke must also implement exec, the code must be split up in order to avoid code duplication. It is advised to define internal init, apply and exit functions, that can be reused. The exec callback will then typically use init, apply, exit, the invoke callback will use init and apply, and the modal and cancel callbacks will use both apply and exit. Often the customdata pointer will be used to store data for the duration of the operation.

The exec, invoke, modal and cancel functions return one of four states: pass through, running modal, cancelled or finished. The pass through state means the operator passes on the event to other operators, as if the callback did not run. The running modal state indicates that the operator is still running and has registered itself to receive modal callbacks. The states cancelled and finished mean the execution of the operator has ended, either unsuccessfully or successfully. The operator will be registered if it finishes successfully.

The poll callback is used to verify if the operator can be executed in the current context. The operator may still fail to execute due to some more specific reason. The callback function used for this is often shared by multiple operators. The poll function is always called before executing the operator to verify the context and it may be used to gray out or hide user interface elements that use this operator automatically.

The uiBlock callback is used to provide a user interface to the operator, to redo or repeat it.

Caveats

  • Note the distinction between RNA properties and customdata. The properties should only store public parameters as seen by the user, these are saved as part of macro's and written to file. The customdata is an arbitrary pointer, typically to a C struct, that should only exist while the operator is running, an so it is not written to file.
  • The invoke callback also takes properties as inputs and should take those into account. For example a transform operator might have a mode=rotate input, and should then start in rotation mode.

Conventions

  • All operators should have an idname, name and description.
  • Ordering for names: (context _OT_ subcontext) _ (operation) _ (optional operation info)
  • The reason is that a function beginning with 'copy' or 'add' doesn't say much about the context it works in, especially when there's a subcontext. It also makes it a bit easier to locate. We always use the verb to separate the information in 'what it works on' and what it does. This gives straight information about what the operator needs as input.
  • Examples:
    • 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
  • Classify operators well that heavily rely on mouse input, or cannot be redone or cannot be used for the py api.
  • Use enum and boolean properties instead of ints for things like modes and options.
  • Use the float percentage subtype where applicable, with range 0.0-1.0 and do any necessary conversions in the operator.
  • Define proper hard/soft limits.
  • Selection operators should use boolean properties "extend" and "deselect".
  • Toggle operators should have a standard enum with three options: set/clear/toggle, with toggle the default option.

Open Issues

  • Operators that depend on mouse coordinates, like moving areas or selecting in the 3d viewport, are problematic. How to redo such an operation? One possibility is to store the x and y coordinates, but this makes the operator quite useless as part of a macro. Another is to use the current mouse coordinates while the macro is running, but the macro may be run from a menu for example, in which case the mouse coordinate have little meaning. Maybe the latter is still the most useful option since it will support a macro like “mouse select face, delete selected face”, though then exec also depends on user input?
    • Current solution: just don't bother supporting "modal" macros well. If an operator like this is recorded as part of a macro, it can just give a warning somewhere that this is not supported. If someone wants to make such a complex macro that depends on user interface interaction, it should become possible to do that by making an operator as a python script.
      • (Ton) We exclude for now selection (contextual changes) from operator storage or macros. In the case of "move edge", you can store an (x,y) property close to the edge to indicate which, to prevent "Context" having to define such. That way the operator is re-usable via Python at least. Although, this isn't a very useful case. :)
  • When an operator is executed a second time, should its properties be remembered for the next time the operator executes? If so, then it should not do this for all properties. For example a translation operator would not need to remember the translation vector, but an extrude operator should remember the extrusion type.
    • This could also be remembered by the tool itself, saved in the tool settings or so.
      • (Ton) It is especially the purpose that its properties are remembered! A useful redo or repeat is based on having those properties, and make use of Context. Also using the same operator again will make use of that (like Add Sphere properties).
  • While it is the intention that operators work based on redo rather than being modal, these operators might still want to use shortcut keys to set certain properties quickly. How does this fit in with the operator definition, or more generally, the event system, since these would not truly be modal and so conflict with other keys?
    • More of a user interface discussion, though it can have an effect on the implementation, this can be added to the operator design if needed after experimenting with implementing some real operators.
      • (Ton) in the case of Macros, Redo or Repeat, such extra user input should not be a requirement. How to define a useful modal() callback for a Macro?
  • Should supported properties be specified in the operator type register function? Such a specification is useful for generating docs, validating python function arguments or creating an automatic UI for the operator.
    • Yes, this should be done somehow. The operator properties should, just like other properties in Blender, get a ui min/max, strict min/max, default, tooltip, .. . How this is defined exactly can be figured out as part of the "data access api" and implemented using the same mechanism.
      • (Ton) good idea... needs some figuring indeed!
  • While operators are running modal, in theory the context could change arbitrarily, invalidating certain pointers the operator may store. Is this a practical problem, if so, how to avoid this?
    • Probably best to tackle this problem when we get there. Any operation that depends on some window/object/image not being removed should either be blocking, or check that the data it is operating on still exists.
      • (Ton) I don't see this happening, provided the poll() callback is correct, and modal() doesn't internally change context redically in internal subloops.
  • The event handling code iterates over windows, areas, regions, handlers to execute operators. What happens if these operators remove one of these while it is iterating over them?
    • If an operator runs at a level and it removes something above it, that should be done through a notifier rather than a direct function call.