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);
    ...
}

 我们需要确保WindowManager将调用此注册函数。为此,每个操作类别都有一个函数将注册函数放入其中。

Poll

poll回调需要验证要运行操作的正确上下文是否有效。通常,许多操作将使用相同的poll回调。本例中,我们使用由大多数网格编辑操作使用的ED_operator_editmesh函数。

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;
}

 此函数从上下文中获取编辑对象,并验证它是否是网格,且edit_mesh指针是否已设置。

如果轮询函数失败,就可以给用户一个简单的警告,解释原因。

可以更改前面的示例来完成:

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

exec回调用于在没有用户交互的情况下执行操作(与典型的变换操作相反)。该函数如下所示:

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;
}

让我们从函数声明开始。

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

此函数获取两个参数、从中获取数据的上下文和操作的实例。wmOperator 是当前运行的操作,并存储其状态和属性(不要与用于创建wmOperator的wmOoperatorType相混淆)。

函数返回值用于指示运算符是否成功完成或取消。

    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");

接下来,我们使用RNA访问器函数获得操作属性。

    BM_mesh_esubdivide(...);

此函数实际上将更改编辑并执行细分。如何工作的细节与当前不相关。

    EDBM_update_generic(em, true, true);

请参阅此函数的源代码。

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
}

执行操作后,我们需要更新依赖的图并发送通知。我们将呼叫依赖图并告诉它数据已改变,这将导致任何依赖于该网格几何体内容的,例如修饰器重新执行。

notifier调用用于更新用户界面的其他部分。在这里,我们表明我们已经改变了一个物体的几何数据。例如,3D视图将接收此notifier并请求重绘。

    return OPERATOR_FINISHED;

最后,我们返回操作符已经成功完成。在其他情况下,我们可能希望返回OPERATOR_CANCELLED,以指示什么都没有做。因为我们返回OPERATOR_FINISHED,这将导致撤销推送,并意味着将注册该操作。

 重新执行

这个操作可以从最后一个操作面板重新执行。这是自动实现的,因为操作有一个exec回调。对于交互式操作来说,还需要更多的服务,我们将在下面看到这一点。

3D View Zoom(3D视图绽放)

注册

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);
}

这与网格细分操作非常相似,但我们将讨论两个不同之处。

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

除了exec和poll回调之外,这个操作符还具有invoke和modal回调。这些是用来使操作符交互,对像鼠标移动这样的事件作出反应。我们稍后再讨论这些问题。

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

flag是不同的。我们不希望在历史堆栈中注册这个操作,也不希望它导致撤销推送。OPTYPE_BLOCKING标志指示这个操作应该捕获所有鼠标移动,即使它超出了窗口。

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;
}

这里的轮询回调不测试数据,但确保我们处于正确的空间类型,因为这是我们将要编辑的内容。

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;
    }
}

invoke函数在运行时由用户调用,如果它不存在则使用exec。

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

 与exec回调相较不同之处在于事件。例如,这是导致调用操作的事件,它可以用来获取鼠标坐标。

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

 首先,如果已经设置了所有属性,则操作员尝试执行exec。这不是必需的行为,但在某些情况下可能很方便。

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

 否则,我们将开始一个modal操作。使用事件当前鼠标的位置,初始状态将被保存在OP -> customdata。这是一个可以用来存储任何数据的void *属性,用来存储操作时间。存放具体数据的细节在这里并不重要。

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

 接下来,我们将自身注册为窗口级别的modal处理器。这意味着此窗口中的所有事件都将首先通过该操作,从而阻止所有其他事件处理器。

        return OPERATOR_RUNNING_MODAL;
    }

 最后,我们标示操作现在正在运行modal,因此尚未完成。

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;
}

modal回调可在任何事件上调用,然后我们可以决定是否处理。

    ViewOpsData *vod = op->customdata;

首先,我们获取invoke中创建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;

 我们请求3D视图更新,因为我们改变了它。我们也需要释放我们临时储存的customdata。

                return OPERATOR_FINISHED;
            }

 标示此修饰器已完成操作,其处理器现在可移除。

    return OPERATOR_RUNNING_MODAL;

如果操作尚未完成,则执行此行,标示我们要继续接收事件。

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;
}

 这很类似网格细分exec。我们从上下文中获取一些数据,获得操作属性。接着我们执行操作,然后发出一些信号来更新和重绘。

如果我们希望操作是可重复的,我们需要在invokel回调实现后,接着实现exec回调回,如果不能,我们可以把它放到一边。注意,modal回调应该在完成操作时设置delta(在我们的例子中,它在每次鼠标移动中设置它),这样重复执行可以使用它来缩放相同的数量。

原文地址:https://www.cnblogs.com/jiaping/p/8228252.html