Blender 源码学习--Operator

Blender 的 c 语言 api 与 python api 颇为相似。[感觉像Python的超级扩展]

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

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
时间: 2024-08-11 19:45:38

Blender 源码学习--Operator的相关文章

caffe源码学习之Proto数据格式【1】

前言: 由于业务需要,接触caffe已经有接近半年,一直忙着阅读各种论文,重现大大小小的模型. 期间也总结过一些caffe源码学习笔记,断断续续,这次打算系统的记录一下caffe源码学习笔记,巩固一下C++,同时也梳理一下自己之前的理解. 正文: 我们先不看caffe的框架结构,先介绍一下caffe.proto,是google开源的一种数据交互格式--Google Protobuf,这种数据的格式,我们可以看到caffe.proto中内容: syntax = "proto2"; pac

FireMonkey 源码学习(5)

(5)UpdateCharRec 该函数的源码分析如下: procedure TTextLayoutNG.UpdateCharRec(const ACanvas: TCanvas; NeedBitmap: Boolean; var NewRec: PCharRec; HasItem: Boolean; const CharDic: TCharDic; const AFont: TFont; const Ch: UCS4Char; const NeedPath: Boolean = False);

jquery源码学习

jQuery 源码学习是对js的能力提升很有帮助的一个方法,废话不说,我们来开始学习啦 我们学习的源码是jquery-2.0.3已经不支持IE6,7,8了,因为可以少学很多hack和兼容的方法. jquery-2.0.3的代码结构如下 首先最外层为一个闭包, 代码执行的最后一句为window.$ = window.jquery = jquery 让闭包中的变量暴露倒全局中. 传参传入window是为了便于压缩 传入undefined是为了undifined被修改,他是window的属性,可以被修

Hadoop源码学习笔记(1) ——第二季开始——找到Main函数及读一读Configure类

Hadoop源码学习笔记(1) ——找到Main函数及读一读Configure类 前面在第一季中,我们简单地研究了下Hadoop是什么,怎么用.在这开源的大牛作品的诱惑下,接下来我们要研究一下它是如何实现的. 提前申明,本人是一直搞.net的,对java略为生疏,所以在学习该作品时,会时不时插入对java的学习,到时也会摆一些上来,包括一下设计模式之类的.欢迎高手指正. 整个学习过程,我们主要通过eclipse来学习,之前已经讲过如何在eclipse中搭建调试环境,这里就不多述了. 在之前源码初

HSQLDB源码学习——数据库安装启动及JDBC连接

HSQLDB 是一个轻量级的纯Java开发的开放源代码的关系数据库系统.因为HSQLDB的轻量(占用空间小),使用简单,支持内存运行方式等特点,HSQLDB被广泛用于开发环境和某些中小型系统中. 在http://sourceforge.net/projects/hsqldb/files/下载了HSQLDB 1.8.0版本.把下载的zip文件解压缩至任意目录例如c:\hsqldb1.8便完成安装. hsqldb有四种运行模式: 一.内存(Memory-Only)模式:所有数据都在内存里操作.应用程

lodash源码学习(10)

_.delay(func, wait, [args]) 延迟wait毫秒之后调用该函数,添加的参数为函数调用时的参数 //delay.js var baseDelay = require('./_baseDelay'),//baseDelay方法 baseRest = require('./_baseRest'),//创建使用rest参数方法 toNumber = require('./toNumber');//转化为数字 /** * * @param {Function} func 需要延迟执

lodash源码学习(2)

继续学习lodash,依然是数组的方法 “Array” Methods _.indexOf(array, value, [fromIndex=0]) 获取value在数组 array所在的索引值 使用 SameValueZero方式比较(第一个全等===的元素). 如果 fromIndex 值是负数, 则从array末尾起算 该方法依赖于strictIndexOf和baseIndexOf方法,先看它们的源码 //_strictIndexOf.js /** * _.indexOf的专业版本,对元素

jQuery源码学习感想

还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码,那时我不明白他们为何要求那么高,现在才知道,原来没那么高,他问的都是jQuery最基本的框架架构,不过对于不知道的来说,再简单我也是不知道,那时写了一篇博文去吐槽了一下,那时候也是我自己真正激发自己的时候,那时候我说我一定要搞好自己的jQuery基础,没想到那么快就实现了,一个月的源码学习时间就结束

Redis源码学习-Lua脚本

Redis源码学习-Lua脚本 1.Sublime Text配置 我是在Win7下,用Sublime Text + Cygwin开发的,配置方法请参考<Sublime Text 3下C/C++开发环境搭建>. 要注意的是:在Cygwin中安装Lua解析器后,SublimeClang插件就能识别出可饮用的Lua头文件了,因为Build System中我们已经配置过"-I", "D:\\cygwin64\\usr\\include",而新安装的Lua头文件会