Fix T67459: Dope Editor, muting channels with shortcut doesn't work
[blender.git] / source / blender / python / gpu / gpu_py_matrix.c
1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  */
16
17 /** \file
18  * \ingroup bpygpu
19  *
20  * This file defines the gpu.matrix stack API.
21  *
22  * \warning While these functions attempt to ensure correct stack usage.
23  * Mixing Python and C functions may still crash on invalid use.
24  *
25  * - Use ``bpygpu_`` for local API.
26  * - Use ``BPyGPU`` for public API.
27  */
28
29 #include <Python.h>
30
31 #include "BLI_utildefines.h"
32
33 #include "../mathutils/mathutils.h"
34
35 #include "../generic/py_capi_utils.h"
36
37 #define USE_GPU_PY_MATRIX_API
38 #include "GPU_matrix.h"
39 #undef USE_GPU_PY_MATRIX_API
40
41 #include "gpu_py_matrix.h" /* own include */
42
43 /* -------------------------------------------------------------------- */
44 /** \name Helper Functions
45  * \{ */
46
47 static bool bpygpu_stack_is_push_model_view_ok_or_error(void)
48 {
49   if (GPU_matrix_stack_level_get_model_view() >= GPU_PY_MATRIX_STACK_LEN) {
50     PyErr_SetString(
51         PyExc_RuntimeError,
52         "Maximum model-view stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
53     return false;
54   }
55   return true;
56 }
57
58 static bool bpygpu_stack_is_push_projection_ok_or_error(void)
59 {
60   if (GPU_matrix_stack_level_get_projection() >= GPU_PY_MATRIX_STACK_LEN) {
61     PyErr_SetString(
62         PyExc_RuntimeError,
63         "Maximum projection stack depth " STRINGIFY(GPU_PY_MATRIX_STACK_DEPTH) " reached");
64     return false;
65   }
66   return true;
67 }
68
69 static bool bpygpu_stack_is_pop_model_view_ok_or_error(void)
70 {
71   if (GPU_matrix_stack_level_get_model_view() == 0) {
72     PyErr_SetString(PyExc_RuntimeError, "Minimum model-view stack depth reached");
73     return false;
74   }
75   return true;
76 }
77
78 static bool bpygpu_stack_is_pop_projection_ok_or_error(void)
79 {
80   if (GPU_matrix_stack_level_get_projection() == 0) {
81     PyErr_SetString(PyExc_RuntimeError, "Minimum projection stack depth reached");
82     return false;
83   }
84   return true;
85 }
86
87 /** \} */
88
89 /* -------------------------------------------------------------------- */
90 /** \name Manage Stack
91  * \{ */
92
93 PyDoc_STRVAR(bpygpu_matrix_push_doc,
94              ".. function:: push()\n"
95              "\n"
96              "   Add to the model-view matrix stack.\n");
97 static PyObject *bpygpu_matrix_push(PyObject *UNUSED(self))
98 {
99   if (!bpygpu_stack_is_push_model_view_ok_or_error()) {
100     return NULL;
101   }
102   GPU_matrix_push();
103   Py_RETURN_NONE;
104 }
105
106 PyDoc_STRVAR(bpygpu_matrix_pop_doc,
107              ".. function:: pop()\n"
108              "\n"
109              "   Remove the last model-view matrix from the stack.\n");
110 static PyObject *bpygpu_matrix_pop(PyObject *UNUSED(self))
111 {
112   if (!bpygpu_stack_is_pop_model_view_ok_or_error()) {
113     return NULL;
114   }
115   GPU_matrix_pop();
116   Py_RETURN_NONE;
117 }
118
119 PyDoc_STRVAR(bpygpu_matrix_push_projection_doc,
120              ".. function:: push_projection()\n"
121              "\n"
122              "   Add to the projection matrix stack.\n");
123 static PyObject *bpygpu_matrix_push_projection(PyObject *UNUSED(self))
124 {
125   if (!bpygpu_stack_is_push_projection_ok_or_error()) {
126     return NULL;
127   }
128   GPU_matrix_push_projection();
129   Py_RETURN_NONE;
130 }
131
132 PyDoc_STRVAR(bpygpu_matrix_pop_projection_doc,
133              ".. function:: pop_projection()\n"
134              "\n"
135              "   Remove the last projection matrix from the stack.\n");
136 static PyObject *bpygpu_matrix_pop_projection(PyObject *UNUSED(self))
137 {
138   if (!bpygpu_stack_is_pop_projection_ok_or_error()) {
139     return NULL;
140   }
141   GPU_matrix_pop_projection();
142   Py_RETURN_NONE;
143 }
144
145 /** \} */
146
147 /* -------------------------------------------------------------------- */
148 /** \name Stack (Context Manager)
149  *
150  * Safer alternative to ensure balanced push/pop calls.
151  *
152  * \{ */
153
154 typedef struct {
155   PyObject_HEAD /* required python macro */
156       int type;
157   int level;
158 } BPyGPU_MatrixStackContext;
159
160 enum {
161   PYGPU_MATRIX_TYPE_MODEL_VIEW = 1,
162   PYGPU_MATRIX_TYPE_PROJECTION = 2,
163 };
164
165 static PyObject *bpygpu_matrix_stack_context_enter(BPyGPU_MatrixStackContext *self);
166 static PyObject *bpygpu_matrix_stack_context_exit(BPyGPU_MatrixStackContext *self, PyObject *args);
167
168 static PyMethodDef bpygpu_matrix_stack_context_methods[] = {
169     {"__enter__", (PyCFunction)bpygpu_matrix_stack_context_enter, METH_NOARGS},
170     {"__exit__", (PyCFunction)bpygpu_matrix_stack_context_exit, METH_VARARGS},
171     {NULL},
172 };
173
174 static PyTypeObject BPyGPU_matrix_stack_context_Type = {
175     PyVarObject_HEAD_INIT(NULL, 0).tp_name = "GPUMatrixStackContext",
176     .tp_basicsize = sizeof(BPyGPU_MatrixStackContext),
177     .tp_flags = Py_TPFLAGS_DEFAULT,
178     .tp_methods = bpygpu_matrix_stack_context_methods,
179 };
180
181 static PyObject *bpygpu_matrix_stack_context_enter(BPyGPU_MatrixStackContext *self)
182 {
183   /* sanity - should never happen */
184   if (self->level != -1) {
185     PyErr_SetString(PyExc_RuntimeError, "Already in use");
186     return NULL;
187   }
188
189   if (self->type == PYGPU_MATRIX_TYPE_MODEL_VIEW) {
190     if (!bpygpu_stack_is_push_model_view_ok_or_error()) {
191       return NULL;
192     }
193     GPU_matrix_push();
194     self->level = GPU_matrix_stack_level_get_model_view();
195   }
196   else if (self->type == PYGPU_MATRIX_TYPE_PROJECTION) {
197     if (!bpygpu_stack_is_push_projection_ok_or_error()) {
198       return NULL;
199     }
200     GPU_matrix_push_projection();
201     self->level = GPU_matrix_stack_level_get_projection();
202   }
203   else {
204     BLI_assert(0);
205   }
206   Py_RETURN_NONE;
207 }
208
209 static PyObject *bpygpu_matrix_stack_context_exit(BPyGPU_MatrixStackContext *self,
210                                                   PyObject *UNUSED(args))
211 {
212   /* sanity - should never happen */
213   if (self->level == -1) {
214     fprintf(stderr, "Not yet in use\n");
215     goto finally;
216   }
217
218   if (self->type == PYGPU_MATRIX_TYPE_MODEL_VIEW) {
219     const int level = GPU_matrix_stack_level_get_model_view();
220     if (level != self->level) {
221       fprintf(stderr, "Level push/pop mismatch, expected %d, got %d\n", self->level, level);
222     }
223     if (level != 0) {
224       GPU_matrix_pop();
225     }
226   }
227   else if (self->type == PYGPU_MATRIX_TYPE_PROJECTION) {
228     const int level = GPU_matrix_stack_level_get_projection();
229     if (level != self->level) {
230       fprintf(stderr, "Level push/pop mismatch, expected %d, got %d", self->level, level);
231     }
232     if (level != 0) {
233       GPU_matrix_pop_projection();
234     }
235   }
236   else {
237     BLI_assert(0);
238   }
239 finally:
240   Py_RETURN_NONE;
241 }
242
243 static PyObject *bpygpu_matrix_push_pop_impl(int type)
244 {
245   BPyGPU_MatrixStackContext *ret = PyObject_New(BPyGPU_MatrixStackContext,
246                                                 &BPyGPU_matrix_stack_context_Type);
247   ret->type = type;
248   ret->level = -1;
249   return (PyObject *)ret;
250 }
251
252 PyDoc_STRVAR(
253     bpygpu_matrix_push_pop_doc,
254     ".. function:: push_pop()\n"
255     "\n"
256     "   Context manager to ensure balanced push/pop calls, even in the case of an error.\n");
257 static PyObject *bpygpu_matrix_push_pop(PyObject *UNUSED(self))
258 {
259   return bpygpu_matrix_push_pop_impl(PYGPU_MATRIX_TYPE_MODEL_VIEW);
260 }
261
262 PyDoc_STRVAR(
263     bpygpu_matrix_push_pop_projection_doc,
264     ".. function:: push_pop_projection()\n"
265     "\n"
266     "   Context manager to ensure balanced push/pop calls, even in the case of an error.\n");
267 static PyObject *bpygpu_matrix_push_pop_projection(PyObject *UNUSED(self))
268 {
269   return bpygpu_matrix_push_pop_impl(PYGPU_MATRIX_TYPE_PROJECTION);
270 }
271
272 /** \} */
273
274 /* -------------------------------------------------------------------- */
275 /** \name Manipulate State
276  * \{ */
277
278 PyDoc_STRVAR(bpygpu_matrix_multiply_matrix_doc,
279              ".. function:: multiply_matrix(matrix)\n"
280              "\n"
281              "   Multiply the current stack matrix.\n"
282              "\n"
283              "   :param matrix: A 4x4 matrix.\n"
284              "   :type matrix: :class:`mathutils.Matrix`\n");
285 static PyObject *bpygpu_matrix_multiply_matrix(PyObject *UNUSED(self), PyObject *value)
286 {
287   MatrixObject *pymat;
288   if (!Matrix_Parse4x4(value, &pymat)) {
289     return NULL;
290   }
291   GPU_matrix_mul(pymat->matrix);
292   Py_RETURN_NONE;
293 }
294
295 PyDoc_STRVAR(bpygpu_matrix_scale_doc,
296              ".. function:: scale(scale)\n"
297              "\n"
298              "   Scale the current stack matrix.\n"
299              "\n"
300              "   :param scale: Scale the current stack matrix.\n"
301              "   :type scale: sequence of 2 or 3 floats\n");
302 static PyObject *bpygpu_matrix_scale(PyObject *UNUSED(self), PyObject *value)
303 {
304   float scale[3];
305   int len;
306   if ((len = mathutils_array_parse(
307            scale, 2, 3, value, "gpu.matrix.scale(): invalid vector arg")) == -1) {
308     return NULL;
309   }
310   if (len == 2) {
311     GPU_matrix_scale_2fv(scale);
312   }
313   else {
314     GPU_matrix_scale_3fv(scale);
315   }
316   Py_RETURN_NONE;
317 }
318
319 PyDoc_STRVAR(bpygpu_matrix_scale_uniform_doc,
320              ".. function:: scale_uniform(scale)\n"
321              "\n"
322              "   :param scale: Scale the current stack matrix.\n"
323              "   :type scale: float\n");
324 static PyObject *bpygpu_matrix_scale_uniform(PyObject *UNUSED(self), PyObject *value)
325 {
326   float scalar;
327   if ((scalar = PyFloat_AsDouble(value)) == -1.0f && PyErr_Occurred()) {
328     PyErr_Format(PyExc_TypeError, "expected a number, not %.200s", Py_TYPE(value)->tp_name);
329     return NULL;
330   }
331   GPU_matrix_scale_1f(scalar);
332   Py_RETURN_NONE;
333 }
334
335 PyDoc_STRVAR(bpygpu_matrix_translate_doc,
336              ".. function:: translate(offset)\n"
337              "\n"
338              "   Scale the current stack matrix.\n"
339              "\n"
340              "   :param offset: Translate the current stack matrix.\n"
341              "   :type offset: sequence of 2 or 3 floats\n");
342 static PyObject *bpygpu_matrix_translate(PyObject *UNUSED(self), PyObject *value)
343 {
344   float offset[3];
345   int len;
346   if ((len = mathutils_array_parse(
347            offset, 2, 3, value, "gpu.matrix.translate(): invalid vector arg")) == -1) {
348     return NULL;
349   }
350   if (len == 2) {
351     GPU_matrix_translate_2fv(offset);
352   }
353   else {
354     GPU_matrix_translate_3fv(offset);
355   }
356   Py_RETURN_NONE;
357 }
358
359 /** \} */
360
361 /* -------------------------------------------------------------------- */
362 /** \name Write State
363  * \{ */
364
365 PyDoc_STRVAR(bpygpu_matrix_reset_doc,
366              ".. function:: reset()\n"
367              "\n"
368              "   Empty stack and set to identity.\n");
369 static PyObject *bpygpu_matrix_reset(PyObject *UNUSED(self))
370 {
371   GPU_matrix_reset();
372   Py_RETURN_NONE;
373 }
374
375 PyDoc_STRVAR(bpygpu_matrix_load_identity_doc,
376              ".. function:: load_identity()\n"
377              "\n"
378              "   Empty stack and set to identity.\n");
379 static PyObject *bpygpu_matrix_load_identity(PyObject *UNUSED(self))
380 {
381   GPU_matrix_identity_set();
382   Py_RETURN_NONE;
383 }
384
385 PyDoc_STRVAR(bpygpu_matrix_load_matrix_doc,
386              ".. function:: load_matrix(matrix)\n"
387              "\n"
388              "   Load a matrix into the stack.\n"
389              "\n"
390              "   :param matrix: A 4x4 matrix.\n"
391              "   :type matrix: :class:`mathutils.Matrix`\n");
392 static PyObject *bpygpu_matrix_load_matrix(PyObject *UNUSED(self), PyObject *value)
393 {
394   MatrixObject *pymat;
395   if (!Matrix_Parse4x4(value, &pymat)) {
396     return NULL;
397   }
398   GPU_matrix_set(pymat->matrix);
399   Py_RETURN_NONE;
400 }
401
402 PyDoc_STRVAR(bpygpu_matrix_load_projection_matrix_doc,
403              ".. function:: load_projection_matrix(matrix)\n"
404              "\n"
405              "   Load a projection matrix into the stack.\n"
406              "\n"
407              "   :param matrix: A 4x4 matrix.\n"
408              "   :type matrix: :class:`mathutils.Matrix`\n");
409 static PyObject *bpygpu_matrix_load_projection_matrix(PyObject *UNUSED(self), PyObject *value)
410 {
411   MatrixObject *pymat;
412   if (!Matrix_Parse4x4(value, &pymat)) {
413     return NULL;
414   }
415   GPU_matrix_projection_set(pymat->matrix);
416   Py_RETURN_NONE;
417 }
418
419 /** \} */
420
421 /* -------------------------------------------------------------------- */
422 /** \name Read State
423  * \{ */
424
425 PyDoc_STRVAR(bpygpu_matrix_get_projection_matrix_doc,
426              ".. function:: get_projection_matrix()\n"
427              "\n"
428              "   Return a copy of the projection matrix.\n"
429              "\n"
430              "   :return: A 4x4 projection matrix.\n"
431              "   :rtype: :class:`mathutils.Matrix`\n");
432 static PyObject *bpygpu_matrix_get_projection_matrix(PyObject *UNUSED(self))
433 {
434   float matrix[4][4];
435   GPU_matrix_projection_get(matrix);
436   return Matrix_CreatePyObject(&matrix[0][0], 4, 4, NULL);
437 }
438
439 PyDoc_STRVAR(bpygpu_matrix_get_model_view_matrix_doc,
440              ".. function:: get_model_view_matrix()\n"
441              "\n"
442              "   Return a copy of the model-view matrix.\n"
443              "\n"
444              "   :return: A 4x4 view matrix.\n"
445              "   :rtype: :class:`mathutils.Matrix`\n");
446 static PyObject *bpygpu_matrix_get_model_view_matrix(PyObject *UNUSED(self))
447 {
448   float matrix[4][4];
449   GPU_matrix_model_view_get(matrix);
450   return Matrix_CreatePyObject(&matrix[0][0], 4, 4, NULL);
451 }
452
453 PyDoc_STRVAR(bpygpu_matrix_get_normal_matrix_doc,
454              ".. function:: get_normal_matrix()\n"
455              "\n"
456              "   Return a copy of the normal matrix.\n"
457              "\n"
458              "   :return: A 3x3 normal matrix.\n"
459              "   :rtype: :class:`mathutils.Matrix`\n");
460 static PyObject *bpygpu_matrix_get_normal_matrix(PyObject *UNUSED(self))
461 {
462   float matrix[3][3];
463   GPU_matrix_normal_get(matrix);
464   return Matrix_CreatePyObject(&matrix[0][0], 3, 3, NULL);
465 }
466
467 /** \} */
468
469 /* -------------------------------------------------------------------- */
470 /** \name Module
471  * \{ */
472
473 static struct PyMethodDef bpygpu_matrix_methods[] = {
474     /* Manage Stack */
475     {"push", (PyCFunction)bpygpu_matrix_push, METH_NOARGS, bpygpu_matrix_push_doc},
476     {"pop", (PyCFunction)bpygpu_matrix_pop, METH_NOARGS, bpygpu_matrix_pop_doc},
477
478     {"push_projection",
479      (PyCFunction)bpygpu_matrix_push_projection,
480      METH_NOARGS,
481      bpygpu_matrix_push_projection_doc},
482     {"pop_projection",
483      (PyCFunction)bpygpu_matrix_pop_projection,
484      METH_NOARGS,
485      bpygpu_matrix_pop_projection_doc},
486
487     /* Stack (Context Manager) */
488     {"push_pop", (PyCFunction)bpygpu_matrix_push_pop, METH_NOARGS, bpygpu_matrix_push_pop_doc},
489     {"push_pop_projection",
490      (PyCFunction)bpygpu_matrix_push_pop_projection,
491      METH_NOARGS,
492      bpygpu_matrix_push_pop_projection_doc},
493
494     /* Manipulate State */
495     {"multiply_matrix",
496      (PyCFunction)bpygpu_matrix_multiply_matrix,
497      METH_O,
498      bpygpu_matrix_multiply_matrix_doc},
499     {"scale", (PyCFunction)bpygpu_matrix_scale, METH_O, bpygpu_matrix_scale_doc},
500     {"scale_uniform",
501      (PyCFunction)bpygpu_matrix_scale_uniform,
502      METH_O,
503      bpygpu_matrix_scale_uniform_doc},
504     {"translate", (PyCFunction)bpygpu_matrix_translate, METH_O, bpygpu_matrix_translate_doc},
505
506 /* TODO */
507 #if 0
508     {"rotate", (PyCFunction)bpygpu_matrix_rotate, METH_O, bpygpu_matrix_rotate_doc},
509     {"rotate_axis", (PyCFunction)bpygpu_matrix_rotate_axis, METH_O, bpygpu_matrix_rotate_axis_doc},
510     {"look_at", (PyCFunction)bpygpu_matrix_look_at, METH_O, bpygpu_matrix_look_at_doc},
511 #endif
512
513     /* Write State */
514     {"reset", (PyCFunction)bpygpu_matrix_reset, METH_NOARGS, bpygpu_matrix_reset_doc},
515     {"load_identity",
516      (PyCFunction)bpygpu_matrix_load_identity,
517      METH_NOARGS,
518      bpygpu_matrix_load_identity_doc},
519     {"load_matrix", (PyCFunction)bpygpu_matrix_load_matrix, METH_O, bpygpu_matrix_load_matrix_doc},
520     {"load_projection_matrix",
521      (PyCFunction)bpygpu_matrix_load_projection_matrix,
522      METH_O,
523      bpygpu_matrix_load_projection_matrix_doc},
524
525     /* Read State */
526     {"get_projection_matrix",
527      (PyCFunction)bpygpu_matrix_get_projection_matrix,
528      METH_NOARGS,
529      bpygpu_matrix_get_projection_matrix_doc},
530     {"get_model_view_matrix",
531      (PyCFunction)bpygpu_matrix_get_model_view_matrix,
532      METH_NOARGS,
533      bpygpu_matrix_get_model_view_matrix_doc},
534     {"get_normal_matrix",
535      (PyCFunction)bpygpu_matrix_get_normal_matrix,
536      METH_NOARGS,
537      bpygpu_matrix_get_normal_matrix_doc},
538
539     {NULL, NULL, 0, NULL},
540 };
541
542 PyDoc_STRVAR(bpygpu_matrix_doc, "This module provides access to the matrix stack.");
543 static PyModuleDef BPyGPU_matrix_module_def = {
544     PyModuleDef_HEAD_INIT,
545     .m_name = "gpu.matrix",
546     .m_doc = bpygpu_matrix_doc,
547     .m_methods = bpygpu_matrix_methods,
548 };
549
550 PyObject *BPyInit_gpu_matrix(void)
551 {
552   PyObject *submodule;
553
554   submodule = PyModule_Create(&BPyGPU_matrix_module_def);
555
556   if (PyType_Ready(&BPyGPU_matrix_stack_context_Type) < 0) {
557     return NULL;
558   }
559
560   return submodule;
561 }
562
563 /** \} */