您的位置:首页 > 运维架构

Blender文档翻译:Operators tutorial(操作教程)

2018-01-07 16:02 411 查看
原文:https://wiki.blender.org/index.php/Dev:2.5/Source/Architecture/Operators/Tutorial

逐行解释操作如何工作的。首先解释网格细分(mesh subdivide),一个相对简单的算子。接下来,我们将解释一个更复杂的模态操作,3D视图缩放。

网络细分(Mesh Subdivide)

注册

我们必须做的第一件事是向窗口管理器注册操作符类型。为此,我们定义了一个函数,在启动时由窗口管理器调用。

void MESH_OT_subdivide(wmOperatorType *ot)
{
PropertyRNA *prop;

/* identifiers */
ot->name = "Subdivide";
ot->description = "Subdivide selected edges";
ot->idname = "MESH_OT_subdivide";

/* api callbacks */
ot->exec = edbm_subdivide_exec;
ot->poll = ED_operator_editmesh;

/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

/* properties */
prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 100, "Number of Cuts", "", 1, 10);
/* avoid re-using last var because it can cause _very_ high poly meshes and annoy users (or worse crash) */
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}


让我们从第一行开始:

void MESH_OT_subdivide(wmOperatorType *ot)


MESH定义了操作类别,_OT_(操作类型)是操作ID名称的标准部分。函数的目的是填充wmOperatorType。

/* identifiers */
ot->name = "Subdivide";
ot->description = "Subdivide selected edges";
ot->idname = "MESH_OT_subdivide";


ot->name值表示将在用户界面中使用的字符串,它是操作的可读名称。该描述用于工具提示。idname应与函数的名称相同,它是该操作的唯一标识符。

/* api callbacks */
ot->exec = edbm_subdivide_exec;
ot->poll = ED_operator_editmesh;


API回调函数定义操作实际运行的方式。将运行poll回调来测试操作符是否可以执行,而exec回调将实际执行操作。我们稍后会详细讨论这些问题。

/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;


操作标志向窗口管理器提供如何使用操作的信息。在这里,OPTYPE_REGISTER意味着操作应在历史堆栈注册。OPTYPE_UNDO表明操作完成后应(译者:push 到undo??原文:OPTYPE_UNDO indicates that an undo push should be done after the operator has finished.)。

/* properties */
prop = RNA_def_int(ot->srna, "number_cuts", 1, 1, 100, "Number of Cuts", "", 1, 10);
/* avoid re-using last var because it can cause _very_ high poly meshes and annoy users (or worse crash) */
RNA_def_property_flag(prop, PROP_SKIP_SAVE);


操作可以定义多个属性。这些属性然后可以由用户设置,并且由操作用来修改其行为。这些是RNA属性,因此有关如何定义它们的更多信息,请参阅RNA文档。在这种情况下,我们将简单地定义一个整数,指示切口的数量。

WM

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


我们需要确保WindowManager将调用此注册函数。为此,每个操作类别都有一个函数将注册函数放入其中。

Poll

poll回调需要验证要运行操作的正确上下文是否有效。通常,许多操作将使用相同的poll回调。本例中,我们使用由大多数网格编辑操作使用的ED_operator_editmesh函数。

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;
return 0;
}


此函数从上下文中获取编辑对象,并验证它是否是网格,且edit_mesh指针是否已设置。

如果轮询函数失败,就可以给用户一个简单的警告,解释原因。

可以更改前面的示例来完成:

int ED_operator_editmesh(bContext *C)
{
...
CTX_wm_operator_poll_msg_set(C, "selected object isn't a mesh or not in editmode");
return 0;
}


Exec

exec回调用于在没有用户交互的情况下执行操作(与典型的变换操作相反)。该函数如下所示:

static int edbm_subdivide_exec(bContext *C, wmOperator *op)
{
Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);
const int cuts = RNA_int_get(op->ptr, "number_cuts");
float smooth = RNA_float_get(op->ptr, "smoothness");
const float fractal = RNA_float_get(op->ptr, "fractal") / 2.5f;
const float along_normal = RNA_float_get(op->ptr, "fractal_along_normal");

if (RNA_boolean_get(op->ptr, "quadtri") &&
RNA_enum_get(op->ptr, "quadcorner") == SUBD_CORNER_STRAIGHT_CUT)
{
RNA_enum_set(op->ptr, "quadcorner", SUBD_CORNER_INNERVERT);
}

BM_mesh_esubdivide(em->bm, BM_ELEM_SELECT,
smooth, SUBD_FALLOFF_LIN, false,
fractal, along_normal,
cuts,
SUBDIV_SELECT_ORIG, RNA_enum_get(op->ptr, "quadcorner"),
RNA_boolean_get(op->ptr, "quadtri"), true, false,
RNA_int_get(op->ptr, "seed"));

EDBM_update_generic(em, true, true);

return OPERATOR_FINISHED;
}


让我们从函数声明开始。

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


此函数获取两个参数、从中获取数据的上下文和操作的实例。
wmOperator
是当前运行的操作,并存储其状态和属性(不要与用于创建wmOperator的wmOoperatorType相混淆)。

函数返回值用于指示运算符是否成功完成或取消。

Object *obedit = CTX_data_edit_object(C);
BMEditMesh *em = BKE_editmesh_from_object(obedit);


通常,在执行操作符时,首先要做的就是从上下文中获取相关数据。在这里,我们获得了场景,编辑对象和编辑网格。

const int cuts = RNA_int_get(op->ptr, "number_cuts");
float smooth = RNA_float_get(op->ptr, "smoothness");
const float fractal = RNA_float_get(op->ptr, "fractal") / 2.5f;
const float along_normal = RNA_float_get(op->ptr, "fractal_along_normal");


接下来,我们使用RNA访问器函数获得操作属性。

BM_mesh_esubdivide(...);


此函数实际上将更改编辑并执行细分。如何工作的细节与当前不相关。

EDBM_update_generic(em, true, true);


请参阅此函数的源代码。

void EDBM_update_generic(BMEditMesh *em, const bool do_tessface, const bool is_destructive)
{
Object *ob = em->ob;
/* order of calling isn't important */
DAG_id_tag_update(ob->data, OB_RECALC_DATA);
WM_main_add_notifier(NC_GEOM | ND_DATA, ob->data);

if (do_tessface) {
BKE_editmesh_tessface_calc(em);
}

if (is_destructive) {
/* TODO. we may be able to remove this now! - Campbell */
// BM_mesh_elem_table_free(em->bm, BM_ALL_NOLOOP);
}
else {
/* in debug mode double check we didn't need to recalculate */
BLI_assert(BM_mesh_elem_table_check(em->bm) == true);
}

/* don't keep stale derivedMesh data around, see: [#38872] */
BKE_editmesh_free_derivedmesh(em);

#ifdef DEBUG
{
BMEditSelection *ese;
for (ese = em->bm->selected.first; ese; ese = ese->next) {
BLI_assert(BM_elem_flag_test(ese->ele, BM_ELEM_SELECT));
}
}
#endif
}


执行操作后,我们需要更新依赖的图并发送通知。我们将呼叫依赖图并告诉它数据已改变,这将导致任何依赖于该网格几何体内容的,例如修饰器重新执行。

notifier调用用于更新用户界面的其他部分。在这里,我们表明我们已经改变了一个物体的几何数据。例如,3D视图将接收此notifier并请求重绘。

return OPERATOR_FINISHED;


最后,我们返回操作符已经成功完成。在其他情况下,我们可能希望返回OPERATOR_CANCELLED,以指示什么都没有做。因为我们返回OPERATOR_FINISHED,这将导致撤销推送,并意味着将注册该操作。

重新执行

这个操作可以从最后一个操作面板重新执行。这是自动实现的,因为操作有一个exec回调。对于交互式操作来说,还需要更多的服务,我们将在下面看到这一点。

3D View Zoom(3D视图绽放)

注册

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_view3d_active;

/* flags */
ot->flag = OPTYPE_BLOCKING;

/* properties */
RNA_def_int(ot->srna, "delta", 0, INT_MIN, INT_MAX, "Delta", "", INT_MIN, INT_MAX);
}


这与网格细分操作非常相似,但我们将讨论两个不同之处。

/* api callbacks */
ot->invoke = viewzoom_invoke;
ot->exec = viewzoom_exec;
ot->modal = viewzoom_modal;
ot->poll = ED_operator_view3d_active;


除了exec和poll回调之外,这个操作符还具有invoke和modal回调。这些是用来使操作符交互,对像鼠标移动这样的事件作出反应。我们稍后再讨论这些问题。

/* flags */
ot->flag = OPTYPE_BLOCKING;


flag是不同的。我们不希望在历史堆栈中注册这个操作,也不希望它导致撤销推送。OPTYPE_BLOCKING标志指示这个操作应该捕获所有鼠标移动,即使它超出了窗口。

Poll

int ED_operator_view3d_active(bContext *C)
{
if(ED_operator_areaactive(C)) {
SpaceLink *sl = (SpaceLink *)CTX_wm_space_data(C);
return sl && (sl->spacetype == SPACE_VIEW3D);
}
return 0;
}


这里的轮询回调不测试数据,但确保我们处于正确的空间类型,因为这是我们将要编辑的内容。

Invoke

static int viewzoom_invoke(bContext *C, wmOperator *op, wmEvent *event)
{
if(RNA_property_is_set(op->ptr, "delta")) {
return viewzoom_exec(C, op);
}
else {
/* makes op->customdata */
viewops_data(C, op, event);

/* add temp handler */
WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op);

return OPERATOR_RUNNING_MODAL;
}
}


invoke函数在运行时由用户调用,如果它不存在则使用exec。

static int viewzoom_invoke(bContext *C, wmOperator *op, wmEvent *event)


与exec回调相较不同之处在于事件。例如,这是导致调用操作的事件,它可以用来获取鼠标坐标。

if(RNA_property_is_set(op->ptr, "delta")) {
return viewzoom_exec(C, op);
}


首先,如果已经设置了所有属性,则操作员尝试执行exec。这不是必需的行为,但在某些情况下可能很方便。

else {
/* makes op->customdata */
viewops_data(C, op, event);


否则,我们将开始一个modal操作。使用事件当前鼠标的位置,初始状态将被保存在OP -> customdata。这是一个可以用来存储任何数据的void *属性,用来存储操作时间。存放具体数据的细节在这里并不重要。

/* add temp handler */
WM_event_add_modal_handler(C, &CTX_wm_window(C)->handlers, op);


接下来,我们将自身注册为窗口级别的modal处理器。这意味着此窗口中的所有事件都将首先通过该操作,从而阻止所有其他事件处理器。

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;

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


modal回调可在任何事件上调用,然后我们可以决定是否处理。

ViewOpsData *vod = op->customdata;


首先,我们获取invoke中创建customdata。在其他方面,这是用来获取原始的鼠标位置,以便我们知道鼠标如何移动的。

/* execute the events */
switch(event->type) {
case MOUSEMOVE:
viewzoom_apply(vod, event->x, event->y);
break;


接下来,我们将寻找感兴趣的事件。如果鼠标移动,我们将传递鼠标坐标并应用缩放。函数的内部运作在这里也与我们无关。

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;


我们请求3D视图更新,因为我们改变了它。我们也需要释放我们临时储存的customdata。

            return OPERATOR_FINISHED;
}


标示此修饰器已完成操作,其处理器现在可移除。

return OPERATOR_RUNNING_MODAL;


如果操作尚未完成,则执行此行,标示我们要继续接收事件。

Exec

static int viewzoom_exec(bContext *C, wmOperator *op)
{
View3D *v3d = CTX_wm_view3d(C);
RegionView3D *rv3d = CTX_wm_region_view3d(C);
int delta = RNA_int_get(op->ptr, "delta");

...

request_depth_update(CTX_wm_region_view3d(C));
ED_region_tag_redraw(CTX_wm_region(C));

return OPERATOR_FINISHED;
}


这很类似网格细分exec。我们从上下文中获取一些数据,获得操作属性。接着我们执行操作,然后发出一些信号来更新和重绘。

如果我们希望操作是可重复的,我们需要在invokel回调实现后,接着实现exec回调回,如果不能,我们可以把它放到一边。注意,modal回调应该在完成操作时设置delta(在我们的例子中,它在每次鼠标移动中设置它),这样重复执行可以使用它来缩放相同的数量。

Category: Script
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: