Blender文档翻译:Operators tutorial(操作教程)

Posted 平凡的程序人生

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Blender文档翻译:Operators tutorial(操作教程)相关的知识,希望对你有一定的参考价值。

原文:https://wiki.blender.org/index.php/Dev:2.5/Source/Architecture/Operators/Tutorial

逐行解释操作如何工作的。首先解释网格细分(mesh subdivide),一个相对简单的算子。接下来,我们将解释一个更复杂的模态操作,3D视图缩放。

网络细分(Mesh Subdivide)

 注册

我们必须做的第一件事是向窗口管理器注册操作符类型。为此,我们定义了一个函数,在启动时由窗口管理器调用。

 1 void MESH_OT_subdivide(wmOperatorType *ot)
 2 {
 3     PropertyRNA *prop;
 4  
 5     /* identifiers */
 6     ot->name = "Subdivide";
 7     ot->description = "Subdivide selected edges";
 8     ot->idname = "MESH_OT_subdivide";
 9  
10     /* api callbacks */
11     ot->exec = edbm_subdivide_exec;
12     ot->poll = ED_operator_editmesh;
13  
14     /* flags */
15     ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
16  
17     /* properties */
18     prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 100, "Number of Cuts", "", 1, 10);
19     /* avoid re-using last var because it can cause _very_ high poly meshes and annoy users (or worse crash) */
20     RNA_def_property_flag(prop, PROP_SKIP_SAVE);
21 }

让我们从第一行开始:

void MESH_OT_subdivide(wmOperatorType *ot)

MESH定义了操作类别,_OT_(操作类型)是操作ID名称的标准部分。函数的目的是填充wmOperatorType。

    /* identifiers */
    ot->name = "Subdivide";
    ot->description = "Subdivide selected edges";
    ot->idname = "MESH_OT_subdivide";

ot->name值表示将在用户界面中使用的字符串,它是操作的可读名称。该描述用于工具提示。idname应与函数的名称相同,它是该操作的唯一标识符。

    /* api callbacks */
    ot->exec = edbm_subdivide_exec;
    ot->poll = ED_operator_editmesh;

API回调函数定义操作实际运行的方式。将运行poll回调来测试操作符是否可以执行,而exec回调将实际执行操作。我们稍后会详细讨论这些问题。

    /* flags */
    ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

操作标志向窗口管理器提供如何使用操作的信息。在这里,OPTYPE_REGISTER意味着操作应在历史堆栈注册。OPTYPE_UNDO表明操作完成后应(译者:push 到undo??原文:OPTYPE_UNDO indicates that an undo push should be done after the operator has finished.)。

    /* properties */
    prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 100, "Number of Cuts", "", 1, 10);
    /* avoid re-using last var because it can cause _very_ high poly meshes and annoy users (or worse crash) */
    RNA_def_property_flag(prop, PROP_SKIP_SAVE);

操作可以定义多个属性。这些属性然后可以由用户设置,并且由操作用来修改其行为。这些是RNA属性,因此有关如何定义它们的更多信息,请参阅RNA文档。在这种情况下,我们将简单地定义一个整数,指示切口的数量。

WM

void ED_operatortypes_mesh(void)
{
    ...
    WM_operatortype_append(MESH_OT_subdivide);
    ...
}

 

void ED_operatortypes_mesh(void)
{
    ...
    WM_operatortype_append(MESH_OT_subdivide);
    ...
}

We need to ensure the windowmanager will call this registration function. For this, each operator category has a function to put the registration functions in.

Poll

The poll callback needs to verify the right context is available for the operator to run. Usually many operators will use the same poll callback. In this case we use the ED_operator_editmesh function which is used by most mesh editing operators.

int ED_operator_editmesh(bContext *C)
{
    Object *obedit = CTX_data_edit_object(C);
    if(obedit && obedit->type == OB_MESH)
        return NULL != ((Mesh *)obedit->data)->edit_mesh;
    return 0;
}

This functions get the edit object from the context, and verifies that it is a mesh, and that the edit_mesh pointer is set.

If the poll function fails, it‘s possible to give the user a simple warning that explains why.

This can be done changing the previous example:

int ED_operator_editmesh(bContext *C)
{
    ...
    CTX_wm_operator_poll_msg_set(C, "selected object isn‘t a mesh or not in editmode");
    return 0;
}

Exec

The exec callback is used to execute operators without user interaction (as opposed to e.g. a typical transform operator). The function looks like this:

static int edbm_subdivide_exec(bContext *C, wmOperator *op)
{
    Object *obedit = CTX_data_edit_object(C);
    BMEditMesh *em = BKE_editmesh_from_object(obedit);
    const int cuts = RNA_int_get(op->ptr, "number_cuts");
    float smooth = RNA_float_get(op->ptr, "smoothness");
    const float fractal = RNA_float_get(op->ptr, "fractal") / 2.5f;
    const float along_normal = RNA_float_get(op->ptr, "fractal_along_normal");
 
    if (RNA_boolean_get(op->ptr, "quadtri") && 
        RNA_enum_get(op->ptr, "quadcorner") == SUBD_CORNER_STRAIGHT_CUT)
    {
        RNA_enum_set(op->ptr, "quadcorner", SUBD_CORNER_INNERVERT);
    }
 
    BM_mesh_esubdivide(em->bm, BM_ELEM_SELECT,
                       smooth, SUBD_FALLOFF_LIN, false,
                       fractal, along_normal,
                       cuts,
                       SUBDIV_SELECT_ORIG, RNA_enum_get(op->ptr, "quadcorner"),
                       RNA_boolean_get(op->ptr, "quadtri"), true, false,
                       RNA_int_get(op->ptr, "seed"));
 
    EDBM_update_generic(em, true, true);
 
    return OPERATOR_FINISHED;
}

Let‘s start with the function declaration.

static int edbm_subdivide_exec(bContext *C, wmOperator *op)

This functions gets two arguments, the context to get data from, and an instance of the operator. wmOperator is the operator that is currently running and stores its state and properties (not to be confused with the wmOperatorType which is used to create the wmOperator).

The function return value is used to indicate if the operator finished successfully or canceled.

    Object *obedit = CTX_data_edit_object(C);
    BMEditMesh *em = BKE_editmesh_from_object(obedit);

Typically the first thing to do on operator execution is get the relevant data from the context. Here we obtain the scene, edit object and edit mesh.

    const int cuts = RNA_int_get(op->ptr, "number_cuts");
    float smooth = RNA_float_get(op->ptr, "smoothness");
    const float fractal = RNA_float_get(op->ptr, "fractal") / 2.5f;
    const float along_normal = RNA_float_get(op->ptr, "fractal_along_normal");

Next we get the operator properties using the RNA accessor functions.

    BM_mesh_esubdivide(...);

This function will actually change the editmesh and perform the subdivision. The specifics of how this works are not relevant here.

    EDBM_update_generic(em, true, true);

See the source for this function.

void EDBM_update_generic(BMEditMesh *em, const bool do_tessface, const bool is_destructive)
{
    Object *ob = em->ob;
    /* order of calling isn‘t important */
    DAG_id_tag_update(ob->data, OB_RECALC_DATA);
    WM_main_add_notifier(NC_GEOM | ND_DATA, ob->data);
 
    if (do_tessface) {
        BKE_editmesh_tessface_calc(em);
    }
 
    if (is_destructive) {
        /* TODO. we may be able to remove this now! - Campbell */
        // BM_mesh_elem_table_free(em->bm, BM_ALL_NOLOOP);
    }
    else {
        /* in debug mode double check we didn‘t need to recalculate */
        BLI_assert(BM_mesh_elem_table_check(em->bm) == true);
    }
 
    /* don‘t keep stale derivedMesh data around, see: [#38872] */
    BKE_editmesh_free_derivedmesh(em);
 
#ifdef DEBUG
    {
        BMEditSelection *ese;
        for (ese = em->bm->selected.first; ese; ese = ese->next) {
            BLI_assert(BM_elem_flag_test(ese->ele, BM_ELEM_SELECT));
        }
    }
#endif
}

After performing the operation, we need to update the dependency graph and send a notifier. We call the dependency graph saying the object‘s data has changed, which will cause for example modifiers to be re-executed, or anything else that depends on the mesh geometry.

The notifier call is used to update other parts of the user interface. Here we indicate that we have changed an object‘s geometry data. For example 3D views will receive this notifier and request a redraw.

    return OPERATOR_FINISHED;

Lastly, we return that the operator has finished successfully. In other cases we may want to return OPERATOR_CANCELLED, to indicate that nothing was done. Since we return OPERATOR_FINISHED, this will cause an undo push, and means the operator will be registered.

Re-Execution

This operator can be re-executed from the last operator panel. This is automatically possible because the operator has an exec callback. For interactive operators some more is needed, as we will see next.

3D View Zoom

Registration

void VIEW3D_OT_zoom(wmOperatorType *ot)
{
    /* identifiers */
    ot->name = "Zoom view";
    ot->description = "Zoom in/out in the view.";
    ot->idname = "VIEW3D_OT_zoom";
 
    /* api callbacks */
    ot->invoke = viewzoom_invoke;
    ot->exec = viewzoom_exec;
    ot->modal = viewzoom_modal;
    ot->poll = ED_operator_view3d_active;
 
    /* flags */
    ot->flag = OPTYPE_BLOCKING;
 
    /* properties */
    RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
}

This is quite similar to the mesh subdivide operator, but we will discuss two differences.

    /* api callbacks */
    ot->invoke = viewzoom_invoke;
    ot->exec = viewzoom_exec;
    ot->modal = viewzoom_modal;
    ot->poll = ED_operator_view3d_active;

Next to the exec and poll callbacks, this operator also has invoke and modal callbacks. These are used to make the operator interactive, reacting to events like mouse move. We‘ll discuss these further later.

    /* flags */
    ot->flag = OPTYPE_BLOCKING;

The flags are also different. We do not want to register this operator in the history stack, nor do we want it to cause an undo push. The OPTYPE_BLOCKING flag indicates that this operator should capture all mouse moves, even if it goes outside the window.

Poll

int ED_operator_view3d_active(bContext *C)
{
    if(ED_operator_areaactive(C)) {
        SpaceLink *sl = (SpaceLink *)CTX_wm_space_data(C);
        return sl && (sl->spacetype == SPACE_VIEW3D);
    }
    return 0;
}

The poll callback here does not test for data, but ensure we are in the right space type, since that is what we will be editing.

Invoke

static int viewzoom_invoke(bContext *C, wmOperator *op, wmEvent *event)
{
    if(RNA_property_is_set(op->ptr, "delta")) {
        return viewzoom_exec(C, op);
    }
    else {
        /* makes op->customdata */
        viewops_data(C, op, event);
 
        /* add temp handler */
        WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op);
 
        return OPERATOR_RUNNING_MODAL;
    }
}

The invoke function is called when the operator is run by the user, if it does not exist exec will be used.

static int viewzoom_invoke(bContext *C, wmOperator *op, wmEvent *event)

What‘s different here compared to an exec callback is the event. This is the event that caused the operator to be invoked, which can be used to get the mouse coordinates for example.

    if(RNA_property_is_set(op->ptr, "delta")) {
        return viewzoom_exec(C, op);
    }

First the operator tries to exec if all properties are already set. This is not required behavior, but may be convenient in some cases.

    else {
        /* makes op->customdata */
        viewops_data(C, op, event);

Otherwise, we will start this as a modal operator. Using the current mouse location from event, the initial state will be saved in op->customdata. This is a void* property that can be used to store any data for the duration of the operator. The specifics of what is stored here are not important.

        /* add temp handler */
        WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op);

Next we register ourselfs as a modal handler at the window level. This means that all events in this window will first go through this operator, blocking all other event handlers.

        return OPERATOR_RUNNING_MODAL;
    }

Lastly, we indicate that the operator is now running modal, and hence not finished yet.

Modal

static int viewzoom_modal(bContext *C, wmOperator *op, wmEvent *event)
{
    ViewOpsData *vod = op->customdata;
 
    /* execute the events */
    switch(event->type) {
        case MOUSEMOVE:
            viewzoom_apply(vod, event->x, event->y);
            break;
 
        default:
            /* origkey may be zero when invoked from a button */
            if(ELEM3(event->type, ESCKEY, LEFTMOUSE, RIGHTMOUSE) || (event->type==vod->origkey && event->val==0)) {
                request_depth_update(CTX_wm_region_view3d(C));
 
                MEM_freeN(vod);
                op->customdata = NULL;
 
                return OPERATOR_FINISHED;
            }
    }
 
    return OPERATOR_RUNNING_MODAL;
}

The modal callback will be called on any event, which we can then decide to handle or not.

    ViewOpsData *vod = op->customdata;

First we retrieve the customdata that we created in invoke. Among other things, this is used to get the original mouse position so that we know how the mouse has moved.

    /* execute the events */
    switch(event->type) {
        case MOUSEMOVE:
            viewzoom_apply(vod, event->x, event->y);
            break;

Next we look for interesting events. In case of mouse move, we will pass along the mouse coordinates and apply the zoom. The internal working of this function again is not relevant here.

        default:
            /* origkey may be zero when invoked from a button */
            if(ELEM3(event->type, ESCKEY, LEFTMOUSE, RIGHTMOUSE) || (event->type==vod->origkey && event->val==0)) {

This line checks for events to stop the operator. Escape, left mouse and right mouse will always cancel. Additionally releasing the key we originally pressed (if the operator was tied to the keyboard instead of the mouse) , will stop the operator.

                request_depth_update(CTX_wm_region_view3d(C));
 
                MEM_freeN(vod);
                op->customdata = NULL;

We request some updates in the 3d view since we changed it. We also need to free the customdata that we stored temporarily.

                return OPERATOR_FINISHED;
            }

Indicate that this modifier has finished operation, and it‘s handlers can now be removed.

    return OPERATOR_RUNNING_MODAL;

This line gets executed if the operator is not finished yet, indicating we want to continue receiving events.

Exec

static int viewzoom_exec(bContext *C, wmOperator *op)
{
    View3D *v3d = CTX_wm_view3d(C);
    RegionView3D *rv3d = CTX_wm_region_view3d(C);
    int delta = RNA_int_get(op->ptr, "delta");
 
    ...
 
    request_depth_update(CTX_wm_region_view3d(C));
    ED_region_tag_redraw(CTX_wm_region(C));
 
    return OPERATOR_FINISHED;
}

This works quite similar to mesh subdivide exec. We get some data from the context, get the operator properties. Then we do the operator, and afterwards sends some signals to update and redraw things.

If we want the operator to be repeatable, we need to implement this exec callback next to the invoke callback, if not we can leave it out. Note that the modal callback should set delta by the time it has finished operation (in our case it sets it on each mouse move), so that a repeated execution can use it to zoom by the same amount.

以上是关于Blender文档翻译:Operators tutorial(操作教程)的主要内容,如果未能解决你的问题,请参考以下文章

需要在搅拌机中自定义模型导出脚本

4.Operators-操作符(Dart中文文档)

如何正确地将 .pdb 导入 Blender?

blender 命令行渲染

在 Blender 中运行 python 脚本

blender学习笔记:python脚本的使用