Published

16 August 2014

Tags

Contents

via http://wiki.blender.org/index.php/Dev:2.5/Source/Architecture/Operators/Tutorial

Blender 的 c 语言 api 与 python api 颇为相似

Mesh Subdivide

下面是对 blender 中的 mesh subdivide operator 代码的分析

Registration

首先我们需要在 window manager 中注册 operator, 编写的注册函数将会在启动时调用.

void MESH_OT_subdivide(wmOperatorType *ot)
{
    /* identifiers */
    ot->name= "Subdivide";
    ot->description= "Split selected faces into smaller faces.";
    ot->idname= "MESH_OT_subdivide";
 
    /* api callbacks */
    ot->exec= subdivide_exec;
    ot->poll= ED_operator_editmesh;
 
    /* flags */
    ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;
 
    /* properties */
    RNA_def_int(ot->srna, "number_cuts", 1, 1, 10, "Number of Cuts", "", 1, INT_MAX);
}

第一行

void MESH_OT_subdivide(wmOperatorType *ot)

其中 OT 是指 operator type.

    /* identifiers */
    ot->name= "Subdivide";
    ot->description= "Split selected faces into smaller faces.";
    ot->idname= "MESH_OT_subdivide";
  • ot->name 相当于 bl_label, 用于显示ui
  • ot->description 相当于 bl_description, tooltip
  • ot->idname 相当于 bl_idname
  • MESH_OT_subdivide 相当于 mesh.subdivide, 必须唯一

    /* api callbacks */
    ot->exec= subdivide_exec;
    ot->poll= ED_operator_editmesh;
    ot->flag= OPTYPE_REGISTER|OPTYPE_UNDO;

相当于python api 中的 exec 和 poll, ot->flag 相当于 bl_options

    prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, INT_MAX, "Number of Cuts", "", 1, 10);

    PropertyRNA *RNA_def_int(StructOrFunctionRNA *cont_, const char *identifier, int default_value,
                     int hardmin, int hardmax, const char *ui_name, const char *ui_description,
                     int softmin, int softmax)

这里可以看一下 RNA_def_int 的定义, identifier, 相当于变量名, 用来访问该变量, 其他都挺直观的, 不过不太清楚 hardmaxsoftmax 有什么区别, 不过一般 hard 更大

WM

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

注册该 operator, 在这里可以看到所有注册的关于mesh的operator

Poll

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;
    CTX_wm_operator_poll_msg_set(C, "selected object isn't a mesh or not in editmode");
    return 0;
}

该函数返回是否可以运行, 首先通过 bContext 获得 object, 然后判断是否是 mesh, 是否有mesh, 以及通过 CTX_wm_operator_poll_msg_set 告知信息

Exec

运行函数, 用于没有用户交互时使用, 和 transform operator 正好相反

static int subdivide_exec(bContext *C, wmOperator *op)
{
    Scene *scene = CTX_data_scene(C);
    Object *obedit= CTX_data_edit_object(C);
    EditMesh *em= BKE_mesh_get_editmesh((Mesh *)obedit->data);
    int cuts= RNA_int_get(op->ptr,"number_cuts");
 
    esubdivideflag(obedit, em, cuts);
 
    DAG_object_flush_update(scene, obedit, OB_RECALC_DATA);
    WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_SELECT, obedit);
 
    return OPERATOR_FINISHED;
}

首先是函数声明

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

两个参数, 一个是 context, 和 python 中的 context 类似, 第二个是 wmOperator, 注意这个和注册时的 wmOperatorType 不一样, 返回值用于返回 Operator 是否运行成功

int cuts = RNA_int_get(op->ptr, "number_cuts");

op->ptr 用于获得 RNA, RNA_int_get 用于获取 int properties, number_cuts 为之前声明的变量id

DAG_object_flush_update(scene, obedit, OB_RECALC_DATA);
WM_event_add_notifier(C, NC_OBJECT|ND_GEOM_DATA, obedit);
// EDBM_update_generic(em, true, true); in new blender code

在完成操作以后, 我们需要通知对此数据有依赖的操作, DAG_id_tag_update 用于通知 dependency graph, WM_main_add_notifier 用于通知窗口系统

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_region_view3d_active;
    ot->cancel = viewzoom_cancel;

    /* flags */
    ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_POINTER;
    // OPTYPE_BLOCKING 表示获得所有鼠标事件, 包括在窗口外部

    RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
    RNA_def_int(ot->srna, "mx", 0, 0, INT_MAX, "Zoom Position X", "", 0, INT_MAX);
    RNA_def_int(ot->srna, "my", 0, 0, INT_MAX, "Zoom Position Y", "", 0, INT_MAX);
}

Poll

现在的代码和 2.5 有点出入, 以现在的为准

// context.c
RegionView3D *CTX_wm_region_view3d(const bContext *C)
{
    ScrArea *sa = CTX_wm_area(C);
    // 相当于 context.area
    ARegion *ar = CTX_wm_region(C);
    // 相当于 context.region
    // region 是 area 的 subcontext

    if (sa && sa->spacetype == SPACE_VIEW3D)
    // context.area.space.type == 'VIEW_3D'
        if (ar)
            return ar->regiondata;
            // context.region_data
    return NULL;
}

int ED_operator_region_view3d_active(bContext *C)
{
    if (CTX_wm_region_view3d(C))
        return true;

    CTX_wm_operator_poll_msg_set(C, "expected a view3d region");
    return false;
}

Invoke

static int viewzoom_invoke(bContext *C, wmOperator *op, wmEvent *event)
// 和 exec 不同的是, 多了一个 event, 可以用来获得鼠标事件
{
    if(RNA_property_is_set(op->ptr, "delta")) {
        return viewzoom_exec(C, op);
    }
    // 如果已经定义了 delta property, 直接运行
    else {
        /* makes op->customdata */
        viewops_data(C, op, event);
        // 初始化信息, 存入 op->customdata, 这是一个 void* 指针, 可以用于存临时信息
 
        /* add temp handler */
        WM_event_add_modal_handler(C, &CTX_wm_window(C)->   handlers, op);
        // WM_event_add_modal_handler(C, op); in blender 2.72
        // 添加一个 modal hander, 和 python 中的 context.window_manager.modal_handler_add(self) 一样, 这个语句同时会拦截其他 event handler
 
        return OPERATOR_RUNNING_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;
        // 将鼠标移动应用给 viewzoom
 
        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;
}

简易实现 translate

这里仿照这个 Python 脚本实现一个C版本, 作为练习, 因为新的 api 和 wiki 有些出入

/* my transform begin */
#include "BKE_object.h"
#include "BKE_depsgraph.h"

static int mytransform_exec(bContext *C, wmOperator *op)
{
    float value[2];
    PropertyRNA *prop = RNA_struct_find_property(op->ptr, "value");
    RNA_property_float_get_array(op->ptr, prop, value);
    // 从 property 获得变量

    Object *obj = CTX_data_active_object(C);
    obj->loc[0] = value[0];
    obj->loc[1] = value[1];
    // 设置 object location

    DAG_id_tag_update(&obj->id, OB_RECALC_OB);
    WM_event_add_notifier(C, NC_OBJECT | ND_TRANSFORM, obj);
    // 更新 dependency, 添加 notifier

    return OPERATOR_FINISHED;
}

static int mytransform_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
    if (event->type == LEFTMOUSE)
        return OPERATOR_FINISHED;

    if (event->type == RIGHTMOUSE)
        return OPERATOR_CANCELLED;

    if (event->type != MOUSEMOVE)
        printf("Uncatched event type %d\n", (int)(event->type));

    PropertyRNA *prop = RNA_struct_find_property(op->ptr, "value");
    float value[2] = { event->x / 100.0f, event->y / 100.0f };

    RNA_property_float_set_array(op->ptr, prop, value);
    // 获得鼠标事件, 设置 property

    mytransform_exec(C, op);

    return OPERATOR_RUNNING_MODAL;
}

static int mytransform_poll(bContext *C)
{
    Object *obj = CTX_data_active_object(C);
    if (obj == NULL) {
        CTX_wm_operator_poll_msg_set(C, "No Active Object in context.");
        return false;
    }
    // 判断 active object 是否存在
    else return true;
}

static int mytransform_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
    PropertyRNA *prop = RNA_struct_find_property(op->ptr, "value");

    if (RNA_property_is_set(op->ptr, prop))
        return mytransform_exec(C, op);

    WM_event_add_modal_handler(C, op);
    // 添加 modal handler

    return OPERATOR_RUNNING_MODAL;
}

static void TRANSFORM_OT_mytransform(struct wmOperatorType *ot) 
{
    /* identifiers */
    ot->name = "MyTransform";
    ot->description = "Move object in XY plane by mouse move";
    ot->idname = "TRANSFORM_OT_my_transform";
    ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_BLOCKING;

    /* api callbacks */
    ot->invoke = mytransform_invoke;
    ot->exec = mytransform_exec;
    ot->poll = mytransform_poll;
    ot->modal = mytransform_modal;

    RNA_def_float_vector(
        ot->srna, "value", 2, NULL, -FLT_MAX, FLT_MAX,
        "Value", "Vector that add to object", -FLT_MAX, FLT_MAX);
}

/* my transform end */

void transform_operatortypes(void)
{
    .......
    WM_operatortype_append(TRANSFORM_OT_mytransform);
    // 注册 operator
}

  • BKE_context.h, 很多操作 context 的函数
  • RNA_access.h, RNA 数据操作函数
  • RNA_struct_find_property
  • event->type == EVT_MODAL_MAP
  • BKE_object.h


blog comments powered by Disqus