• 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(在我们的例子中,它在每次鼠标移动中设置它),这样重复执行可以使用它来缩放相同的数量。

  • 相关阅读:
    jquery-追加元素
    mssql-异常value '0000-00-00' can not be represented as java.sql.Date
    lucene-Field.Store解析
    mysql-删除日志文件命令详解
    js-读取上传文件后缀
    js-处理回车事件
    maven-腾讯SDK(QQ)接口java引入pom配置
    ps制作gif图片
    java-commons-HttpClient超时设置setConnectionTimeout和setSoTimeout
    js-比较两个日期的大小
  • 原文地址:https://www.cnblogs.com/jiaping/p/8228252.html
Copyright © 2020-2023  润新知