Blender 源码学习 --- Operator
Published
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
, 用于显示uiot->description
相当于bl_description
, tooltipot->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 更大
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
}
}
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