d64c1206d99ee8ede05df11fb27e421ccf5addbc
[blender.git] / source / blender / python / bmesh / bmesh_py_ops.c
1 /*
2  * ***** BEGIN GPL LICENSE BLOCK *****
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * The Original Code is Copyright (C) 2012 Blender Foundation.
19  * All rights reserved.
20  *
21  * Contributor(s): Campbell Barton
22  *
23  * ***** END GPL LICENSE BLOCK *****
24  */
25
26 /** \file blender/python/bmesh/bmesh_py_ops.c
27  *  \ingroup pybmesh
28  *
29  * This file defines the 'bmesh.ops' module.
30  * Operators from 'opdefines' are wrapped.
31  */
32
33 #include <Python.h>
34
35 #include "BLI_utildefines.h"
36
37 #include "MEM_guardedalloc.h"
38
39 #include "../generic/py_capi_utils.h"
40
41 #include "../mathutils/mathutils.h"
42
43 #include "bmesh.h"
44
45 #include "bmesh_py_types.h"
46
47 #include "bmesh_py_utils.h" /* own include */
48
49 static int bpy_bm_op_as_py_error(BMesh *bm)
50 {
51         if (BMO_error_occurred(bm)) {
52                 const char *errmsg;
53                 if (BMO_error_get(bm, &errmsg, NULL)) {
54                         PyErr_Format(PyExc_RuntimeError,
55                                      "bmesh operator: %.200s",
56                                      errmsg);
57                         return -1;
58                 }
59         }
60         return 0;
61 }
62
63 /* bmesh operator 'bmesh.ops.*' callable types
64  * ******************************************* */
65 PyTypeObject bmesh_op_Type;
66
67 typedef struct {
68         PyObject_HEAD /* required python macro   */
69         const char *opname;
70 } BPy_BMeshOpFunc;
71
72 PyObject *bpy_bmesh_op_CreatePyObject(const char *opname)
73 {
74         BPy_BMeshOpFunc *self = PyObject_New(BPy_BMeshOpFunc, &bmesh_op_Type);
75
76         self->opname = opname;
77
78         return (PyObject *)self;
79 }
80
81 static PyObject *bpy_bmesh_op_repr(BPy_BMeshOpFunc *self)
82 {
83         return PyUnicode_FromFormat("<%.200s bmesh.ops.%.200s()>",
84                                     Py_TYPE(self)->tp_name,
85                                     self->opname);
86 }
87
88
89 static PyObject *pyrna_op_call(BPy_BMeshOpFunc *self, PyObject *args, PyObject *kw)
90 {
91         BPy_BMesh *py_bm;
92         BMesh *bm;
93
94         BMOperator bmop;
95
96         if ((PyTuple_GET_SIZE(args) == 1) &&
97             (py_bm = (BPy_BMesh *)PyTuple_GET_ITEM(args, 0)) &&
98             (BPy_BMesh_Check(py_bm))
99                 )
100         {
101                 BPY_BM_CHECK_OBJ(py_bm);
102                 bm = py_bm->bm;
103         }
104         else {
105                 PyErr_SetString(PyExc_TypeError,
106                                 "calling a bmesh operator expects a single BMesh (non keyword) "
107                                 "as the first argument");
108                 return NULL;
109         }
110
111         /* TODO - error check this!, though we do the error check on attribute access */
112         BMO_op_init(bm, &bmop, self->opname);
113
114         if (kw && PyDict_Size(kw) > 0) {
115                 /* setup properties, see bpy_rna.c: pyrna_py_to_prop()
116                  * which shares this logic for parsing properties */
117
118                 PyObject *key, *value;
119                 Py_ssize_t pos = 0;
120                 while (PyDict_Next(kw, &pos, &key, &value)) {
121                         const char *slot_name = _PyUnicode_AsString(key);
122                         BMOpSlot *slot = BMO_slot_get(&bmop, slot_name);
123
124                         if (slot == NULL) {
125                                 PyErr_Format(PyExc_TypeError,
126                                              "%.200s: keyword \"%.200s\" is invalid for this operator",
127                                              self->opname, slot_name);
128                                 return NULL;
129                         }
130
131                         /* now assign the value */
132                         switch (slot->slot_type) {
133                                 case BMO_OP_SLOT_BOOL:
134                                 {
135                                         int param;
136
137                                         param = PyLong_AsLong(value);
138
139                                         if (param < 0) {
140                                                 PyErr_Format(PyExc_TypeError,
141                                                              "%.200s: keyword \"%.200s\" expected True/False or 0/1, not %.200s",
142                                                              self->opname, slot_name, Py_TYPE(value)->tp_name);
143                                                 return NULL;
144                                         }
145                                         else {
146                                                 slot->data.i = param;
147                                         }
148
149                                         break;
150                                 }
151                                 case BMO_OP_SLOT_INT:
152                                 {
153                                         int overflow;
154                                         long param = PyLong_AsLongAndOverflow(value, &overflow);
155                                         if (overflow || (param > INT_MAX) || (param < INT_MIN)) {
156                                                 PyErr_Format(PyExc_ValueError,
157                                                              "%.200s: keyword \"%.200s\" value not in 'int' range "
158                                                              "(" STRINGIFY(INT_MIN) ", " STRINGIFY(INT_MAX) ")",
159                                                              self->opname, slot_name, Py_TYPE(value)->tp_name);
160                                                 return NULL;
161                                         }
162                                         else if (param == -1 && PyErr_Occurred()) {
163                                                 PyErr_Format(PyExc_TypeError,
164                                                              "%.200s: keyword \"%.200s\" expected an int, not %.200s",
165                                                              self->opname, slot_name, Py_TYPE(value)->tp_name);
166                                         }
167                                         else {
168                                                 slot->data.i = (int)param;
169                                         }
170                                         break;
171                                 }
172                                 case BMO_OP_SLOT_FLT:
173                                 {
174                                         float param = PyFloat_AsDouble(value);
175                                         if (param == -1 && PyErr_Occurred()) {
176                                                 PyErr_Format(PyExc_TypeError,
177                                                              "%.200s: keyword \"%.200s\" expected a float, not %.200s",
178                                                              self->opname, slot_name, Py_TYPE(value)->tp_name);
179                                         }
180                                         else {
181                                                 slot->data.f = param;
182                                         }
183                                         break;
184                                 }
185                                 case BMO_OP_SLOT_ELEMENT_BUF:
186                                 {
187                                         /* there are many ways we could interpret arguments, for now...
188                                          * - verts/edges/faces from the mesh direct,
189                                          *   this way the operator takes every item.
190                                          * - `TODO` a plain python sequence (list) of elements.
191                                          * - `TODO`  an iterator. eg.
192                                          *   face.verts
193                                          * - `TODO`  (type, flag) pair, eg.
194                                          *   ('VERT', {'TAG'})
195                                          */
196
197 #define BPY_BM_GENERIC_MESH_TEST(type_string)                                             \
198         if (((BPy_BMGeneric *)value)->bm != bm) {                                             \
199                 PyErr_Format(PyExc_NotImplementedError,                                           \
200                                          "%.200s: keyword \"%.200s\" " type_string " are from another bmesh", \
201                                          self->opname, slot_name, slot->slot_type);                           \
202                 return NULL;                                                                      \
203         }
204
205                                         if (BPy_BMVertSeq_Check(value)) {
206                                                 BPY_BM_GENERIC_MESH_TEST("verts")
207                                                 BMO_slot_buffer_from_all(bm, &bmop, slot_name, BM_VERT);
208                                         }
209                                         else if (BPy_BMEdgeSeq_Check(value)) {
210                                                 BPY_BM_GENERIC_MESH_TEST("edges")
211                                                 BMO_slot_buffer_from_all(bm, &bmop, slot_name, BM_EDGE);
212                                         }
213                                         else if (BPy_BMFaceSeq_Check(value)) {
214                                                 BPY_BM_GENERIC_MESH_TEST("faces")
215                                                 BMO_slot_buffer_from_all(bm, &bmop, slot_name, BM_FACE);
216                                         }
217                                         else if (BPy_BMElemSeq_Check(value)) {
218                                                 BPY_BM_GENERIC_MESH_TEST("elements")
219
220                                                 PyErr_Format(PyExc_NotImplementedError,
221                                                              "%.200s: keyword \"%.200s\" parsing element lists not working yet!",
222                                                              self->opname, slot_name, slot->slot_type);
223                                                 return NULL;
224                                         }
225                                         else {
226                                                 PyErr_Format(PyExc_TypeError,
227                                                              "%.200s: keyword \"%.200s\" expected "
228                                                              "a bmesh sequence, list, (htype, flag) pair, not %.200s",
229                                                              self->opname, slot_name, Py_TYPE(value)->tp_name);
230                                         }
231
232 #undef BPY_BM_GENERIC_MESH_TEST
233
234                                         break;
235                                 }
236                                 default:
237                                         /* TODO --- many others */
238                                         PyErr_Format(PyExc_NotImplementedError,
239                                                      "%.200s: keyword \"%.200s\" type %d not working yet!",
240                                                      self->opname, slot_name, slot->slot_type);
241                                         return NULL;
242                                         break;
243                         }
244                 }
245         }
246
247         BMO_op_exec(bm, &bmop);
248         BMO_op_finish(bm, &bmop);
249
250         if (bpy_bm_op_as_py_error(bm) == -1) {
251                 return NULL;
252         }
253
254         Py_RETURN_NONE;
255 }
256
257
258 PyTypeObject bmesh_op_Type = {
259         PyVarObject_HEAD_INIT(NULL, 0)
260         "BMeshOpFunc",              /* tp_name */
261         sizeof(BPy_BMeshOpFunc),    /* tp_basicsize */
262         0,                          /* tp_itemsize */
263         /* methods */
264         NULL,                       /* tp_dealloc */
265         NULL,                       /* printfunc tp_print; */
266         NULL,                       /* getattrfunc tp_getattr; */
267         NULL,                       /* setattrfunc tp_setattr; */
268         NULL,                       /* tp_compare */ /* DEPRECATED in python 3.0! */
269         (reprfunc) bpy_bmesh_op_repr, /* tp_repr */
270
271         /* Method suites for standard classes */
272
273         NULL,                       /* PyNumberMethods *tp_as_number; */
274         NULL,                       /* PySequenceMethods *tp_as_sequence; */
275         NULL,                       /* PyMappingMethods *tp_as_mapping; */
276
277         /* More standard operations (here for binary compatibility) */
278
279         NULL,                       /* hashfunc tp_hash; */
280         (ternaryfunc)pyrna_op_call, /* ternaryfunc tp_call; */
281         NULL,                       /* reprfunc tp_str; */
282
283         /* will only use these if this is a subtype of a py class */
284         NULL,                       /* getattrofunc tp_getattro; */
285         NULL,                       /* setattrofunc tp_setattro; */
286
287         /* Functions to access object as input/output buffer */
288         NULL,                       /* PyBufferProcs *tp_as_buffer; */
289
290         /*** Flags to define presence of optional/expanded features ***/
291         Py_TPFLAGS_DEFAULT,         /* long tp_flags; */
292
293         NULL,                       /*  char *tp_doc;  Documentation string */
294         /*** Assigned meaning in release 2.0 ***/
295         /* call function for all accessible objects */
296         NULL,                       /* traverseproc tp_traverse; */
297
298         /* delete references to contained objects */
299         NULL,                       /* inquiry tp_clear; */
300
301         /***  Assigned meaning in release 2.1 ***/
302         /*** rich comparisons ***/
303         NULL,                       /* richcmpfunc tp_richcompare; */
304
305         /***  weak reference enabler ***/
306         0,
307         /*** Added in release 2.2 ***/
308         /*   Iterators */
309         NULL,                       /* getiterfunc tp_iter; */
310         NULL,                       /* iternextfunc tp_iternext; */
311
312         /*** Attribute descriptor and subclassing stuff ***/
313         NULL,                       /* struct PyMethodDef *tp_methods; */
314         NULL,                       /* struct PyMemberDef *tp_members; */
315         NULL,                       /* struct PyGetSetDef *tp_getset; */
316         NULL,                       /* struct _typeobject *tp_base; */
317         NULL,                       /* PyObject *tp_dict; */
318         NULL,                       /* descrgetfunc tp_descr_get; */
319         NULL,                       /* descrsetfunc tp_descr_set; */
320         0,                          /* long tp_dictoffset; */
321         NULL,                       /* initproc tp_init; */
322         NULL,                       /* allocfunc tp_alloc; */
323         NULL,                       /* newfunc tp_new; */
324         /*  Low-level free-memory routine */
325         NULL,                       /* freefunc tp_free;  */
326         /* For PyObject_IS_GC */
327         NULL,                       /* inquiry tp_is_gc;  */
328         NULL,                       /* PyObject *tp_bases; */
329         /* method resolution order */
330         NULL,                       /* PyObject *tp_mro;  */
331         NULL,                       /* PyObject *tp_cache; */
332         NULL,                       /* PyObject *tp_subclasses; */
333         NULL,                       /* PyObject *tp_weaklist; */
334         NULL
335 };
336
337
338 /* bmesh fake module 'bmesh.ops'
339  * ***************************** */
340
341 static PyObject *bpy_bmesh_fmod_getattro(PyObject *UNUSED(self), PyObject *pyname)
342 {
343         const unsigned int tot = bmesh_total_ops;
344         unsigned int i;
345         const char *name = _PyUnicode_AsString(pyname);
346
347         for (i = 0; i < tot; i++) {
348                 if (strcmp(opdefines[i]->name, name) == 0) {
349                         return bpy_bmesh_op_CreatePyObject(opdefines[i]->name);
350                 }
351         }
352
353         PyErr_Format(PyExc_AttributeError,
354                      "BMeshOpsModule: , operator \"%.200s\" doesn't exist",
355                      name);
356         return NULL;
357 }
358
359 static PyObject *bpy_bmesh_fmod_dir(PyObject *UNUSED(self))
360 {
361         const unsigned int tot = bmesh_total_ops;
362         unsigned int i;
363         PyObject *ret;
364
365         ret = PyList_New(bmesh_total_ops);
366
367         for (i = 0; i < tot; i++) {
368                 PyList_SET_ITEM(ret, i, PyUnicode_FromString(opdefines[i]->name));
369         }
370
371         return ret;
372 }
373
374 static struct PyMethodDef bpy_bmesh_fmod_methods[] = {
375         {"__dir__", (PyCFunction)bpy_bmesh_fmod_dir, METH_NOARGS, NULL},
376         {NULL, NULL, 0, NULL}
377 };
378
379 static PyTypeObject bmesh_ops_fakemod_Type = {
380         PyVarObject_HEAD_INIT(NULL, 0)
381         "BMeshOpsModule",           /* tp_name */
382         0,                          /* tp_basicsize */
383         0,                          /* tp_itemsize */
384         /* methods */
385         NULL,                       /* tp_dealloc */
386         NULL,                       /* printfunc tp_print; */
387         NULL,                       /* getattrfunc tp_getattr; */
388         NULL,                       /* setattrfunc tp_setattr; */
389         NULL,                       /* tp_compare */ /* DEPRECATED in python 3.0! */
390         NULL,                       /* tp_repr */
391
392         /* Method suites for standard classes */
393
394         NULL,                       /* PyNumberMethods *tp_as_number; */
395         NULL,                       /* PySequenceMethods *tp_as_sequence; */
396         NULL,                       /* PyMappingMethods *tp_as_mapping; */
397
398         /* More standard operations (here for binary compatibility) */
399
400         NULL,                       /* hashfunc tp_hash; */
401         NULL,                       /* ternaryfunc tp_call; */
402         NULL,                       /* reprfunc tp_str; */
403
404         /* will only use these if this is a subtype of a py class */
405         bpy_bmesh_fmod_getattro,    /* getattrofunc tp_getattro; */
406         NULL,                       /* setattrofunc tp_setattro; */
407
408         /* Functions to access object as input/output buffer */
409         NULL,                       /* PyBufferProcs *tp_as_buffer; */
410
411         /*** Flags to define presence of optional/expanded features ***/
412         Py_TPFLAGS_DEFAULT,         /* long tp_flags; */
413
414         NULL,                       /*  char *tp_doc;  Documentation string */
415         /*** Assigned meaning in release 2.0 ***/
416         /* call function for all accessible objects */
417         NULL,                       /* traverseproc tp_traverse; */
418
419         /* delete references to contained objects */
420         NULL,                       /* inquiry tp_clear; */
421
422         /***  Assigned meaning in release 2.1 ***/
423         /*** rich comparisons ***/
424         NULL, /* subclassed */          /* richcmpfunc tp_richcompare; */
425
426         /***  weak reference enabler ***/
427         0,
428         /*** Added in release 2.2 ***/
429         /*   Iterators */
430         NULL,                       /* getiterfunc tp_iter; */
431         NULL,                       /* iternextfunc tp_iternext; */
432
433         /*** Attribute descriptor and subclassing stuff ***/
434         bpy_bmesh_fmod_methods,  /* struct PyMethodDef *tp_methods; */
435         NULL,                       /* struct PyMemberDef *tp_members; */
436         NULL,                       /* struct PyGetSetDef *tp_getset; */
437         NULL,                       /* struct _typeobject *tp_base; */
438         NULL,                       /* PyObject *tp_dict; */
439         NULL,                       /* descrgetfunc tp_descr_get; */
440         NULL,                       /* descrsetfunc tp_descr_set; */
441         0,                          /* long tp_dictoffset; */
442         NULL,                       /* initproc tp_init; */
443         NULL,                       /* allocfunc tp_alloc; */
444         NULL,                       /* newfunc tp_new; */
445         /*  Low-level free-memory routine */
446         NULL,                       /* freefunc tp_free;  */
447         /* For PyObject_IS_GC */
448         NULL,                       /* inquiry tp_is_gc;  */
449         NULL,                       /* PyObject *tp_bases; */
450         /* method resolution order */
451         NULL,                       /* PyObject *tp_mro;  */
452         NULL,                       /* PyObject *tp_cache; */
453         NULL,                       /* PyObject *tp_subclasses; */
454         NULL,                       /* PyObject *tp_weaklist; */
455         NULL
456 };
457
458 PyObject *BPyInit_bmesh_ops(void)
459 {
460         PyObject *submodule;
461
462         if (PyType_Ready(&bmesh_ops_fakemod_Type) < 0)
463                 return NULL;
464
465         if (PyType_Ready(&bmesh_op_Type) < 0)
466                 return NULL;
467
468         submodule = PyObject_New(PyObject, &bmesh_ops_fakemod_Type);
469
470         /* prevent further creation of instances */
471         bmesh_ops_fakemod_Type.tp_init = NULL;
472         bmesh_ops_fakemod_Type.tp_new = NULL;
473
474         return submodule;
475 }