【Blender 开发】示例:模拟鼠标移动物体 | MODAL 模式

350 阅读3分钟

原文链接

【Blender开发】示例:模拟鼠标移动物体 | MODEL模式

该代码估计不是这位作者写的,代码注释有一些错误,经过相关查证我进行了修改,并对一些部分进行补充。

正文

效果图

  • 首先将下节中的代码保存在插件目录中,然后在 Blender 的 Add-on 选项中勾选 image.png

  • F3 搜索该自定义操作 微信截图_20221203202908.png

  • 物体跟随鼠标移动 JuejinGif.gif

  • 物体跟随属性变化 JuejinGif_1.gif

脚本代码与详细注释


import bpy
bl_info = {
    "name" : "test",
    "author" : "yl", 
    "description" : "",
    "blender" : (3, 0, 0),
    "version" : (0, 0, 1),
    "location" : "",
    "warning" : "",
    "category" : "Yuelili",
    "doc_url":"https://yuelili.com"
}

# 自定义类
class MoveCubeOperator(bpy.types.Operator):
    bl_idname="cube.movecube"  # ID, 必须。命名必须要带,以小写字母,下划线 数字(my_operator.my_class_name)
    bl_label = "Test Move Cube" # 标签,显示名称
    bl_description = "Description About This Tool" # 描述
    bl_options = {'REGISTER', 'UNDO'}  # 选项,UNDO 是左下角 UNDO 面板

    # 定义 RNA 属性,鼠标的 X 和 Y 位置
    mouseX : bpy.props.FloatProperty(name="float",default=0.0)
    mouseY : bpy.props.FloatProperty(name="float",default=0.0)

    # 定义初始化 X 和 Y 属性
    defX = 0.0
    defy = 0.0

    # 定义状态,为了左键退出
    isOK = False

    @classmethod
    def poll(cls,context):
        # 如果选择的物体不为空,并且是 3D 视窗下的 “物体模式”,则满足运行条件
        if context.area.ui_type == "VIEW_3D":
            if context.active_object is not None:
                if context.object.mode == "OBJECT":
                    return True

    def execute(self,context):
        # 获取对象,并把鼠标坐标赋给对象的位置
        # 鼠标横向运动,对象沿 X 轴移动
        # 鼠标纵向运动,对象沿 Y 轴移动
        obj = context.object
        obj.location[0] = self.mouseX
        obj.location[1] = self.mouseY

        # 如果左键点击了,则 OK 为真,退出
        if self.isOK:
            return {"FINISHED"}
    
    def invoke(self,context,event):
        # 记住物体初始位置
        self.defX = context.object.location[0]
        self.defY = context.object.location[1]

        # *在窗口管理器中添加一个模态处理程序,用于给定的模态操作符【见下一节】 
        # (在返回 {'RUNNING_MODAL'} 之前由 invoke() 搭配 self 调用)
        context.window_manager.modal_handler_add(self)

        return {"RUNNING_MODAL"}

    def modal(self, context, event):
        # 获取鼠标信息,并用 execute 移动物体
        self.mouseX = event.mouse_x / 50
        self.mouseY = event.mouse_y / 50
        self.execute(context)
        
        # 事件类:https://docs.blender.org/api/current/bpy.types.Event.html?highlight=event
        # 事件类型:https://docs.blender.org/api/current/bpy_types_enum_items/event_type_items.html#rna-enum-event-type-items
        
        # 右键可以取消,并把物体位置还原
        if event.type == "RIGHTMOUSE":
            print("cancel")
            self.mouseX = self.defX
            self.mouseY = self.defY
            self.execute(context)
            return {"FINISHED"}

        # 左键就生效,并退出
        if event.type == "LEFTMOUSE":
            self.isOK = True
            print("ok")
            return {"FINISHED"}
        
        # 有四大返回值 {'RUNNING_MODAL', 'CANCELLED', 'FINISHED', 'PASS_THROUGH'}
        return {"RUNNING_MODAL"}

def register():
    print("塔塔开")

    # 注册
    bpy.utils.register_class(MoveCubeOperator)
    ...

def unregister():
    print("已经不用再战斗了")
    #注销
    bpy.utils.unregister_class(MoveCubeOperator)
    ...

Blender 相关 C++ 源码

为给定的模态操作符添加模态处理程序

1. 相关函数

  func = RNA_def_function(srna, "modal_handler_add", "rna_event_modal_handler_add");
  
  RNA_def_function_ui_description(
      func,
      "Add a modal handler to the window manager, for the given modal operator "
      "(called by invoke() with self, just before returning {'RUNNING_MODAL'})");
      
  RNA_def_function_flag(func, FUNC_NO_SELF | FUNC_USE_CONTEXT);
  
  parm = RNA_def_pointer(func, "operator", "Operator", "", "Operator to call");
  
  RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
  
  RNA_def_function_return(
      func, RNA_def_boolean(func, "handle", 1, "", "Whether adding the handler was successful"));
static bool rna_event_modal_handler_add(struct bContext *C, struct wmOperator *operator)
{
  return WM_event_add_modal_handler(C, operator) != NULL;
}
wmEventHandler_Op *WM_event_add_modal_handler(bContext *C, wmOperator *op)
{
  wmEventHandler_Op *handler = MEM_cnew<wmEventHandler_Op>(__func__);
  handler->head.type = WM_HANDLER_TYPE_OP;
  wmWindow *win = CTX_wm_window(C);

  /* Operator was part of macro. */
  if (op->opm) {
    /* Give the mother macro to the handler. */
    handler->op = op->opm;
    /* Mother macro `opm` becomes the macro element. */
    handler->op->opm = op;
  }
  else {
    handler->op = op;
  }

  handler->context.area = CTX_wm_area(C); /* Means frozen screen context for modal handlers! */
  handler->context.region = CTX_wm_region(C);
  handler->context.region_type = handler->context.region ? handler->context.region->regiontype :
                                                           -1;
  // *
  BLI_addhead(&win->modalhandlers, handler);

  if (op->type->modalkeymap) {
    WM_window_status_area_tag_redraw(win);
  }

  return handler;
}
void BLI_addhead(ListBase *listbase, void *vlink)
{
  Link *link = vlink;

  if (link == NULL) {
    return;
  }

  link->next = listbase->first;
  link->prev = NULL;

  if (listbase->first) {
    ((Link *)listbase->first)->prev = link;
  }
  if (listbase->last == NULL) {
    listbase->last = link;
  }
  listbase->first = link;
}

2. 相关结构体

/** #WM_HANDLER_TYPE_OP */
typedef struct wmEventHandler_Op {
  wmEventHandler head;

  /** Operator can be NULL. */
  wmOperator *op;

  /** Hack, special case for file-select. */
  bool is_fileselect;

  /** Store context for this handler for derived/modal handlers. */
  struct {
    /* To override the window, and hence the screen. Set for few cases only, usually window/screen
     * can be taken from current context. */
    struct wmWindow *win;

    struct ScrArea *area;
    struct ARegion *region;
    short region_type;
  } context;
} wmEventHandler_Op;
/**
 * This one is the operator itself, stored in files for macros etc.
 * operator + operator-type should be able to redo entirely, but for different context's.
 */
typedef struct wmOperator {
  struct wmOperator *next, *prev;

  /* saved */
  /** Used to retrieve type pointer. */
  char idname[64];
  /** Saved, user-settable properties. */
  IDProperty *properties;

  /* runtime */
  /** Operator type definition from idname. */
  struct wmOperatorType *type;
  /** Custom storage, only while operator runs. */
  void *customdata;
  /** Python stores the class instance here. */
  void *py_instance;

  /** Rna pointer to access properties. */
  struct PointerRNA *ptr;
  /** Errors and warnings storage. */
  struct ReportList *reports;

  /** List of operators, can be a tree. */
  ListBase macro;
  /** Current running macro, not saved. */
  struct wmOperator *opm;
  /** Runtime for drawing. */
  struct uiLayout *layout;
  short flag;
  char _pad[6];
} wmOperator;
struct bContext {
  int thread;

  /* windowmanager context */
  struct {
    struct wmWindowManager *manager;
    struct wmWindow *window;
    struct WorkSpace *workspace;
    struct bScreen *screen;
    struct ScrArea *area;
    struct ARegion *region;
    struct ARegion *menu;
    struct wmGizmoGroup *gizmo_group;
    struct bContextStore *store;

    /* Operator poll. */
    /**
     * Store the reason the poll function fails (static string, not allocated).
     * For more advanced formatting use `operator_poll_msg_dyn_params`.
     */
    const char *operator_poll_msg;
    /**
     * Store values to dynamically to create the string (called when a tool-tip is shown).
     */
    struct bContextPollMsgDyn_Params operator_poll_msg_dyn_params;
  } wm;

  /* data context */
  struct {
    struct Main *main;
    struct Scene *scene;

    int recursion;
    /** True if python is initialized. */
    bool py_init;
    void *py_context;
    /**
     * If we need to remove members, do so in a copy
     * (keep this to check if the copy needs freeing).
     */
    void *py_context_orig;
  } data;
};