Blender 的 c 语言 api 与 python api 颇为相似。[感觉像Python的超级扩展]
下面是对 blender 中的 mesh subdivide operator 代码的分析
首先我们需要在 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
, 相当于变量名, 用来访问该变量, 其他都挺直观的, 不过不太清楚 hardmax
和 softmax
有什么区别, 不过一般 hard 更大
void ED_operatortypes_mesh(void){ ... WM_operatortype_append(MESH_OT_subdivide); ...}
注册该 operator, 在这里可以看到所有注册的关于mesh的operator
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
告知信息
运行函数, 用于没有用户交互时使用, 和 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
用于通知窗口系统
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);}
现在的代码和 2.5 有点出入, 以现在的为准
// context.cRegionView3D *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;}
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;}
这里仿照这个 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