Cleanup: rename gizmo access functions
[blender.git] / source / blender / python / intern / bpy_rna_gizmo.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  * ***** END GPL LICENSE BLOCK *****
19  */
20
21 /** \file blender/python/intern/bpy_rna_gizmo.c
22  *  \ingroup pythonintern
23  *
24  * .
25  */
26
27 #include <Python.h>
28 #include <stddef.h>
29
30 #include "MEM_guardedalloc.h"
31
32 #include "BLI_utildefines.h"
33 #include "BLI_alloca.h"
34
35 #include "BKE_main.h"
36
37 #include "WM_api.h"
38 #include "WM_types.h"
39
40 #include "bpy_capi_utils.h"
41 #include "bpy_rna_gizmo.h"
42
43 #include "../generic/py_capi_utils.h"
44 #include "../generic/python_utildefines.h"
45
46 #include "RNA_access.h"
47 #include "RNA_types.h"
48 #include "RNA_enum_types.h"
49
50 #include "bpy_rna.h"
51
52
53 /* -------------------------------------------------------------------- */
54 /** \name Gizmo Target Property Define API
55  * \{ */
56
57 enum {
58         BPY_GIZMO_FN_SLOT_GET = 0,
59         BPY_GIZMO_FN_SLOT_SET,
60         BPY_GIZMO_FN_SLOT_RANGE_GET,
61 };
62 #define BPY_GIZMO_FN_SLOT_LEN (BPY_GIZMO_FN_SLOT_RANGE_GET + 1)
63
64 struct BPyGizmoHandlerUserData {
65
66         PyObject *fn_slots[BPY_GIZMO_FN_SLOT_LEN];
67 };
68
69 static void py_rna_gizmo_handler_get_cb(
70         const wmGizmo *UNUSED(gz), wmGizmoProperty *gz_prop,
71         void *value_p)
72 {
73         PyGILState_STATE gilstate = PyGILState_Ensure();
74
75         struct BPyGizmoHandlerUserData *data = gz_prop->custom_func.user_data;
76         PyObject *ret = PyObject_CallObject(data->fn_slots[BPY_GIZMO_FN_SLOT_GET], NULL);
77         if (ret == NULL) {
78                 goto fail;
79         }
80
81         if (gz_prop->type->data_type == PROP_FLOAT) {
82                 float *value = value_p;
83                 if (gz_prop->type->array_length == 1) {
84                         if ((*value = PyFloat_AsDouble(ret)) == -1.0f && PyErr_Occurred()) {
85                                 goto fail;
86                         }
87                 }
88                 else {
89                         if (PyC_AsArray(value, ret, gz_prop->type->array_length, &PyFloat_Type, false,
90                                         "Gizmo get callback: ") == -1)
91                         {
92                                 goto fail;
93                         }
94                 }
95         }
96         else {
97                 PyErr_SetString(PyExc_AttributeError, "internal error, unsupported type");
98                 goto fail;
99         }
100
101         Py_DECREF(ret);
102
103         PyGILState_Release(gilstate);
104         return;
105
106 fail:
107         PyErr_Print();
108         PyErr_Clear();
109
110         PyGILState_Release(gilstate);
111 }
112
113 static void py_rna_gizmo_handler_set_cb(
114         const wmGizmo *UNUSED(gz), wmGizmoProperty *gz_prop,
115         const void *value_p)
116 {
117         PyGILState_STATE gilstate = PyGILState_Ensure();
118
119         struct BPyGizmoHandlerUserData *data = gz_prop->custom_func.user_data;
120
121         PyObject *args = PyTuple_New(1);
122
123         if (gz_prop->type->data_type == PROP_FLOAT) {
124                 const float *value = value_p;
125                 PyObject *py_value;
126                 if (gz_prop->type->array_length == 1) {
127                         py_value = PyFloat_FromDouble(*value);
128                 }
129                 else {
130                         py_value =  PyC_Tuple_PackArray_F32(value, gz_prop->type->array_length);
131                 }
132                 if (py_value == NULL) {
133                         goto fail;
134                 }
135                 PyTuple_SET_ITEM(args, 0, py_value);
136         }
137         else {
138                 PyErr_SetString(PyExc_AttributeError, "internal error, unsupported type");
139                 goto fail;
140         }
141
142         PyObject *ret = PyObject_CallObject(data->fn_slots[BPY_GIZMO_FN_SLOT_SET], args);
143         if (ret == NULL) {
144                 goto fail;
145         }
146         Py_DECREF(ret);
147
148         PyGILState_Release(gilstate);
149         return;
150
151 fail:
152         PyErr_Print();
153         PyErr_Clear();
154
155         Py_DECREF(args);
156
157         PyGILState_Release(gilstate);
158 }
159
160 static void py_rna_gizmo_handler_range_get_cb(
161         const wmGizmo *UNUSED(gz), wmGizmoProperty *gz_prop,
162         void *value_p)
163 {
164         struct BPyGizmoHandlerUserData *data = gz_prop->custom_func.user_data;
165
166         PyGILState_STATE gilstate = PyGILState_Ensure();
167
168         PyObject *ret = PyObject_CallObject(data->fn_slots[BPY_GIZMO_FN_SLOT_RANGE_GET], NULL);
169         if (ret == NULL) {
170                 goto fail;
171         }
172
173         if (!PyTuple_Check(ret)) {
174                 PyErr_Format(PyExc_TypeError,
175                              "Expected a tuple, not %.200s",
176                              Py_TYPE(ret)->tp_name);
177                 goto fail;
178         }
179
180         if (PyTuple_GET_SIZE(ret) != 2) {
181                 PyErr_Format(PyExc_TypeError,
182                              "Expected a tuple of size 2, not %d",
183                              PyTuple_GET_SIZE(ret));
184                 goto fail;
185         }
186
187         if (gz_prop->type->data_type == PROP_FLOAT) {
188                 float range[2];
189                 for (int i = 0; i < 2; i++) {
190                         if (((range[i] = PyFloat_AsDouble(PyTuple_GET_ITEM(ret, i))) == -1.0f && PyErr_Occurred()) == 0) {
191                                 /* pass */
192                         }
193                         else {
194                                 goto fail;
195                         }
196                 }
197                 memcpy(value_p, range, sizeof(range));
198         }
199         else {
200                 PyErr_SetString(PyExc_AttributeError, "internal error, unsupported type");
201                 goto fail;
202         }
203
204         Py_DECREF(ret);
205         PyGILState_Release(gilstate);
206         return;
207
208 fail:
209         Py_XDECREF(ret);
210
211         PyErr_Print();
212         PyErr_Clear();
213
214         PyGILState_Release(gilstate);
215 }
216
217 static void py_rna_gizmo_handler_free_cb(
218         const wmGizmo *UNUSED(gz), wmGizmoProperty *gz_prop)
219 {
220         struct BPyGizmoHandlerUserData *data = gz_prop->custom_func.user_data;
221
222         PyGILState_STATE gilstate = PyGILState_Ensure();
223         for (int i = 0; i < BPY_GIZMO_FN_SLOT_LEN; i++) {
224                 Py_XDECREF(data->fn_slots[i]);
225         }
226         PyGILState_Release(gilstate);
227
228         MEM_freeN(data);
229
230 }
231
232 PyDoc_STRVAR(bpy_gizmo_target_set_handler_doc,
233 ".. method:: target_set_handler(target, get, set, range=None):\n"
234 "\n"
235 "   Assigns callbacks to a gizmos property.\n"
236 "\n"
237 "   :arg get: Function that returns the value for this property (single value or sequence).\n"
238 "   :type get: callable\n"
239 "   :arg set: Function that takes a single value argument and applies it.\n"
240 "   :type set: callable\n"
241 "   :arg range: Function that returns a (min, max) tuple for gizmos that use a range.\n"
242 "   :type range: callable\n"
243 );
244 static PyObject *bpy_gizmo_target_set_handler(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
245 {
246         PyGILState_STATE gilstate = PyGILState_Ensure();
247
248         struct {
249                 PyObject *self;
250                 char *target;
251                 PyObject *py_fn_slots[BPY_GIZMO_FN_SLOT_LEN];
252         } params = {
253                 .self = NULL,
254                 .target = NULL,
255                 .py_fn_slots = {NULL},
256         };
257
258         /* Note: this is a counter-part to functions:
259          * 'Gizmo.target_set_prop & target_set_operator'
260          * (see: rna_wm_gizmo_api.c). conventions should match. */
261         static const char * const _keywords[] = {"self", "target", "get", "set", "range", NULL};
262         static _PyArg_Parser _parser = {"Os|$OOO:target_set_handler", _keywords, 0};
263         if (!_PyArg_ParseTupleAndKeywordsFast(
264                 args, kw, &_parser,
265                 &params.self,
266                 &params.target,
267                 &params.py_fn_slots[BPY_GIZMO_FN_SLOT_GET],
268                 &params.py_fn_slots[BPY_GIZMO_FN_SLOT_SET],
269                 &params.py_fn_slots[BPY_GIZMO_FN_SLOT_RANGE_GET]))
270         {
271                 goto fail;
272         }
273
274         wmGizmo *gz = ((BPy_StructRNA *)params.self)->ptr.data;
275
276         const wmGizmoPropertyType *gz_prop_type =
277                 WM_gizmotype_target_property_find(gz->type, params.target);
278         if (gz_prop_type == NULL) {
279                 PyErr_Format(PyExc_ValueError,
280                              "Gizmo target property '%s.%s' not found",
281                              gz->type->idname, params.target);
282                 goto fail;
283         }
284
285         {
286                 const int slots_required = 2;
287                 const int slots_start = 2;
288                 for (int i = 0; i < BPY_GIZMO_FN_SLOT_LEN; i++) {
289                         if (params.py_fn_slots[i] == NULL) {
290                                 if (i < slots_required) {
291                                         PyErr_Format(PyExc_ValueError, "Argument '%s' not given", _keywords[slots_start + i]);
292                                         goto fail;
293                                 }
294                         }
295                         else if (!PyCallable_Check(params.py_fn_slots[i])) {
296                                 PyErr_Format(PyExc_ValueError, "Argument '%s' not callable", _keywords[slots_start + i]);
297                                 goto fail;
298                         }
299                 }
300         }
301
302         struct BPyGizmoHandlerUserData *data = MEM_callocN(sizeof(*data), __func__);
303
304         for (int i = 0; i < BPY_GIZMO_FN_SLOT_LEN; i++) {
305                 data->fn_slots[i] = params.py_fn_slots[i];
306                 Py_XINCREF(params.py_fn_slots[i]);
307         }
308
309         WM_gizmo_target_property_def_func_ptr(
310                 gz, gz_prop_type,
311                 &(const struct wmGizmoPropertyFnParams) {
312                     .value_get_fn = py_rna_gizmo_handler_get_cb,
313                     .value_set_fn = py_rna_gizmo_handler_set_cb,
314                     .range_get_fn = py_rna_gizmo_handler_range_get_cb,
315                     .free_fn = py_rna_gizmo_handler_free_cb,
316                     .user_data = data,
317                 });
318
319         PyGILState_Release(gilstate);
320
321         Py_RETURN_NONE;
322
323 fail:
324         PyGILState_Release(gilstate);
325         return NULL;
326 }
327
328 /** \} */
329
330 /* -------------------------------------------------------------------- */
331 /** \name Gizmo Target Property Access API
332  * \{ */
333
334 PyDoc_STRVAR(bpy_gizmo_target_get_value_doc,
335 ".. method:: target_get_value(target):\n"
336 "\n"
337 "   Get the value of this target property.\n"
338 "\n"
339 "   :arg target: Target property name.\n"
340 "   :type target: string\n"
341 "   :return: The value of the target property.\n"
342 "   :rtype: Single value or array based on the target type\n"
343 );
344 static PyObject *bpy_gizmo_target_get_value(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
345 {
346         struct {
347                 PyObject *self;
348                 char *target;
349         } params = {
350                 .self = NULL,
351                 .target = NULL,
352         };
353
354         static const char * const _keywords[] = {"self", "target", NULL};
355         static _PyArg_Parser _parser = {"Os:target_get_value", _keywords, 0};
356         if (!_PyArg_ParseTupleAndKeywordsFast(
357                 args, kw, &_parser,
358                 &params.self,
359                 &params.target))
360         {
361                 goto fail;
362         }
363
364         wmGizmo *gz = ((BPy_StructRNA *)params.self)->ptr.data;
365
366         wmGizmoProperty *gz_prop =
367                 WM_gizmo_target_property_find(gz, params.target);
368         if (gz_prop == NULL) {
369                 PyErr_Format(PyExc_ValueError,
370                              "Gizmo target property '%s.%s' not found",
371                              gz->type->idname, params.target);
372                 goto fail;
373         }
374
375         const int array_len = WM_gizmo_target_property_array_length(gz, gz_prop);
376         switch (gz_prop->type->data_type) {
377                 case PROP_FLOAT:
378                 {
379                         if (array_len != 0) {
380                                 float *value = BLI_array_alloca(value, array_len);
381                                 WM_gizmo_target_property_float_get_array(gz, gz_prop, value);
382                                 return PyC_Tuple_PackArray_F32(value, array_len);
383                         }
384                         else {
385                                 float value = WM_gizmo_target_property_float_get(gz, gz_prop);
386                                 return PyFloat_FromDouble(value);
387                         }
388                         break;
389                 }
390                 default:
391                 {
392                         PyErr_SetString(PyExc_RuntimeError, "Not yet supported type");
393                         goto fail;
394                 }
395         }
396
397 fail:
398         return NULL;
399 }
400
401 PyDoc_STRVAR(bpy_gizmo_target_set_value_doc,
402 ".. method:: target_set_value(target):\n"
403 "\n"
404 "   Set the value of this target property.\n"
405 "\n"
406 "   :arg target: Target property name.\n"
407 "   :type target: string\n"
408 );
409 static PyObject *bpy_gizmo_target_set_value(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
410 {
411         struct {
412                 PyObject *self;
413                 char *target;
414                 PyObject *value;
415         } params = {
416                 .self = NULL,
417                 .target = NULL,
418                 .value = NULL,
419         };
420
421         static const char * const _keywords[] = {"self", "target", "value", NULL};
422         static _PyArg_Parser _parser = {"OsO:target_set_value", _keywords, 0};
423         if (!_PyArg_ParseTupleAndKeywordsFast(
424                 args, kw, &_parser,
425                 &params.self,
426                 &params.target,
427                 &params.value))
428         {
429                 goto fail;
430         }
431
432         wmGizmo *gz = ((BPy_StructRNA *)params.self)->ptr.data;
433
434         wmGizmoProperty *gz_prop =
435                 WM_gizmo_target_property_find(gz, params.target);
436         if (gz_prop == NULL) {
437                 PyErr_Format(PyExc_ValueError,
438                              "Gizmo target property '%s.%s' not found",
439                              gz->type->idname, params.target);
440                 goto fail;
441         }
442
443         const int array_len = WM_gizmo_target_property_array_length(gz, gz_prop);
444         switch (gz_prop->type->data_type) {
445                 case PROP_FLOAT:
446                 {
447                         if (array_len != 0) {
448                                 float *value = BLI_array_alloca(value, array_len);
449                                 if (PyC_AsArray(value, params.value, gz_prop->type->array_length, &PyFloat_Type, false,
450                                                 "Gizmo target property array") == -1)
451                                 {
452                                         goto fail;
453                                 }
454                                 WM_gizmo_target_property_float_set_array(BPy_GetContext(), gz, gz_prop, value);
455                         }
456                         else {
457                                 float value;
458                                 if ((value = PyFloat_AsDouble(params.value)) == -1.0f && PyErr_Occurred()) {
459                                         goto fail;
460                                 }
461                                 WM_gizmo_target_property_float_set(BPy_GetContext(), gz, gz_prop, value);
462                         }
463                         Py_RETURN_NONE;
464                 }
465                 default:
466                 {
467                         PyErr_SetString(PyExc_RuntimeError, "Not yet supported type");
468                         goto fail;
469                 }
470         }
471
472 fail:
473         return NULL;
474 }
475
476
477 PyDoc_STRVAR(bpy_gizmo_target_get_range_doc,
478 ".. method:: target_get_range(target):\n"
479 "\n"
480 "   Get the range for this target property.\n"
481 "\n"
482 "   :arg target: Target property name.\n"
483 "   :return: The range of this property (min, max).\n"
484 "   :rtype: tuple pair.\n"
485 );
486 static PyObject *bpy_gizmo_target_get_range(PyObject *UNUSED(self), PyObject *args, PyObject *kw)
487 {
488         struct {
489                 PyObject *self;
490                 char *target;
491         } params = {
492                 .self = NULL,
493                 .target = NULL,
494         };
495
496         static const char * const _keywords[] = {"self", "target", NULL};
497         static _PyArg_Parser _parser = {"Os:target_get_range", _keywords, 0};
498         if (!_PyArg_ParseTupleAndKeywordsFast(
499                 args, kw, &_parser,
500                 &params.self,
501                 &params.target))
502         {
503                 goto fail;
504         }
505
506         wmGizmo *gz = ((BPy_StructRNA *)params.self)->ptr.data;
507
508         wmGizmoProperty *gz_prop =
509                 WM_gizmo_target_property_find(gz, params.target);
510         if (gz_prop == NULL) {
511                 PyErr_Format(PyExc_ValueError,
512                              "Gizmo target property '%s.%s' not found",
513                              gz->type->idname, params.target);
514                 goto fail;
515         }
516
517         switch (gz_prop->type->data_type) {
518                 case PROP_FLOAT:
519                 {
520                         float range[2];
521                         WM_gizmo_target_property_range_get(gz, gz_prop, range);
522                         return PyC_Tuple_PackArray_F32(range, 2);
523                 }
524                 default:
525                 {
526                         PyErr_SetString(PyExc_RuntimeError, "Not yet supported type");
527                         goto fail;
528                 }
529         }
530
531 fail:
532         return NULL;
533 }
534
535 /** \} */
536
537 int BPY_rna_gizmo_module(PyObject *mod_par)
538 {
539         static PyMethodDef method_def_array[] = {
540                 /* Gizmo Target Property Define API */
541                 {"target_set_handler", (PyCFunction)bpy_gizmo_target_set_handler,
542                  METH_VARARGS | METH_KEYWORDS, bpy_gizmo_target_set_handler_doc},
543                 /* Gizmo Target Property Access API */
544                 {"target_get_value", (PyCFunction)bpy_gizmo_target_get_value,
545                  METH_VARARGS | METH_KEYWORDS, bpy_gizmo_target_get_value_doc},
546                 {"target_set_value", (PyCFunction)bpy_gizmo_target_set_value,
547                  METH_VARARGS | METH_KEYWORDS, bpy_gizmo_target_set_value_doc},
548                 {"target_get_range", (PyCFunction)bpy_gizmo_target_get_range,
549                  METH_VARARGS | METH_KEYWORDS, bpy_gizmo_target_get_range_doc},
550                 /* no sentinel needed. */
551         };
552
553         for (int i = 0; i < ARRAY_SIZE(method_def_array); i++) {
554                 PyMethodDef *m = &method_def_array[i];
555                 PyObject *func = PyCFunction_New(m, NULL);
556                 PyObject *func_inst = PyInstanceMethod_New(func);
557                 char name_prefix[128];
558                 PyOS_snprintf(name_prefix, sizeof(name_prefix), "_rna_gizmo_%s", m->ml_name);
559                 /* TODO, return a type that binds nearly to a method. */
560                 PyModule_AddObject(mod_par, name_prefix, func_inst);
561         }
562
563         return 0;
564 }