diff options
Diffstat (limited to 'source/blender/python')
-rw-r--r-- | source/blender/python/bmesh/bmesh_py_types.c | 14 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_buffer.c | 176 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_framebuffer.c | 158 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_framebuffer.h | 7 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_offscreen.c | 18 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_state.c | 16 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_texture.c | 32 | ||||
-rw-r--r-- | source/blender/python/gpu/gpu_py_texture.h | 3 | ||||
-rw-r--r-- | source/blender/python/intern/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_interface.c | 11 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_operator.c | 8 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_operator.c | 150 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_operator.h | 31 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_types_capi.c | 18 |
14 files changed, 542 insertions, 102 deletions
diff --git a/source/blender/python/bmesh/bmesh_py_types.c b/source/blender/python/bmesh/bmesh_py_types.c index 563a76ac824..8654ec3583e 100644 --- a/source/blender/python/bmesh/bmesh_py_types.c +++ b/source/blender/python/bmesh/bmesh_py_types.c @@ -1087,7 +1087,7 @@ PyDoc_STRVAR( "3.0.\n"); static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject *kw) { - static const char *kwlist[] = {"object", "depsgraph", "deform", "cage", "face_normals", NULL}; + static const char *kwlist[] = {"object", "depsgraph", "cage", "face_normals", NULL}; PyObject *py_object; PyObject *py_depsgraph; Object *ob, *ob_eval; @@ -1095,7 +1095,6 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject struct Scene *scene_eval; Mesh *me_eval; BMesh *bm; - bool use_deform = true; bool use_cage = false; bool use_fnorm = true; const CustomData_MeshMasks data_masks = CD_MASK_BMESH; @@ -1104,13 +1103,11 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject if (!PyArg_ParseTupleAndKeywords(args, kw, - "OO|O&O&O&:from_object", + "OO|O&O&:from_object", (char **)kwlist, &py_object, &py_depsgraph, PyC_ParseBool, - &use_deform, - PyC_ParseBool, &use_cage, PyC_ParseBool, &use_fnorm) || @@ -1125,13 +1122,6 @@ static PyObject *bpy_bmesh_from_object(BPy_BMesh *self, PyObject *args, PyObject return NULL; } - if (use_deform == false) { - PyErr_WarnEx(PyExc_FutureWarning, - "from_object(...): the deform parameter is deprecated, assumed to be True, and " - "will be removed in version 3.0", - 1); - } - const bool use_render = DEG_get_mode(depsgraph) == DAG_EVAL_RENDER; scene_eval = DEG_get_evaluated_scene(depsgraph); ob_eval = DEG_get_evaluated_object(depsgraph, ob); diff --git a/source/blender/python/gpu/gpu_py_buffer.c b/source/blender/python/gpu/gpu_py_buffer.c index d0965e83e33..e36e3b42617 100644 --- a/source/blender/python/gpu/gpu_py_buffer.c +++ b/source/blender/python/gpu/gpu_py_buffer.c @@ -37,17 +37,90 @@ #include "gpu_py_buffer.h" -// #define PYGPU_BUFFER_PROTOCOL +//#define PYGPU_BUFFER_PROTOCOL +#define MAX_DIMENSIONS 64 /* -------------------------------------------------------------------- */ /** \name Utility Functions * \{ */ -static bool pygpu_buffer_dimensions_compare(int ndim, - const Py_ssize_t *shape_a, - const Py_ssize_t *shape_b) +static Py_ssize_t pygpu_buffer_dimensions_tot_elem(const Py_ssize_t *shape, Py_ssize_t shape_len) +{ + Py_ssize_t tot = shape[0]; + for (int i = 1; i < shape_len; i++) { + tot *= shape[i]; + } + + return tot; +} + +static bool pygpu_buffer_dimensions_tot_len_compare(const Py_ssize_t *shape_a, + const Py_ssize_t shape_a_len, + const Py_ssize_t *shape_b, + const Py_ssize_t shape_b_len) +{ + if (pygpu_buffer_dimensions_tot_elem(shape_a, shape_a_len) != + pygpu_buffer_dimensions_tot_elem(shape_b, shape_b_len)) { + PyErr_Format(PyExc_BufferError, "array size does not match"); + return false; + } + + return true; +} + +static bool pygpu_buffer_pyobj_as_shape(PyObject *shape_obj, + Py_ssize_t r_shape[MAX_DIMENSIONS], + Py_ssize_t *r_shape_len) { - return (bool)memcmp(shape_a, shape_b, ndim * sizeof(Py_ssize_t)); + Py_ssize_t shape_len = 0; + if (PyLong_Check(shape_obj)) { + shape_len = 1; + if (((r_shape[0] = PyLong_AsLong(shape_obj)) < 1)) { + PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1"); + return false; + } + } + else if (PySequence_Check(shape_obj)) { + shape_len = PySequence_Size(shape_obj); + if (shape_len > MAX_DIMENSIONS) { + PyErr_SetString(PyExc_AttributeError, + "too many dimensions, max is " STRINGIFY(MAX_DIMENSIONS)); + return false; + } + if (shape_len < 1) { + PyErr_SetString(PyExc_AttributeError, "sequence must have at least one dimension"); + return false; + } + + for (int i = 0; i < shape_len; i++) { + PyObject *ob = PySequence_GetItem(shape_obj, i); + if (!PyLong_Check(ob)) { + PyErr_Format(PyExc_TypeError, + "invalid dimension %i, expected an int, not a %.200s", + i, + Py_TYPE(ob)->tp_name); + Py_DECREF(ob); + return false; + } + + r_shape[i] = PyLong_AsLong(ob); + Py_DECREF(ob); + + if (r_shape[i] < 1) { + PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1"); + return false; + } + } + } + else { + PyErr_Format(PyExc_TypeError, + "invalid second argument argument expected a sequence " + "or an int, not a %.200s", + Py_TYPE(shape_obj)->tp_name); + } + + *r_shape_len = shape_len; + return true; } static const char *pygpu_buffer_formatstr(eGPUDataFormat data_format) @@ -174,7 +247,7 @@ static PyObject *pygpu_buffer_to_list_recursive(BPyGPUBuffer *self) return list; } -static PyObject *pygpu_buffer_dimensions(BPyGPUBuffer *self, void *UNUSED(arg)) +static PyObject *pygpu_buffer_dimensions_get(BPyGPUBuffer *self, void *UNUSED(arg)) { PyObject *list = PyList_New(self->shape_len); int i; @@ -186,6 +259,30 @@ static PyObject *pygpu_buffer_dimensions(BPyGPUBuffer *self, void *UNUSED(arg)) return list; } +static int pygpu_buffer_dimensions_set(BPyGPUBuffer *self, PyObject *value, void *UNUSED(type)) +{ + Py_ssize_t shape[MAX_DIMENSIONS]; + Py_ssize_t shape_len = 0; + + if (!pygpu_buffer_pyobj_as_shape(value, shape, &shape_len)) { + return -1; + } + + if (!pygpu_buffer_dimensions_tot_len_compare(shape, shape_len, self->shape, self->shape_len)) { + return -1; + } + + size_t size = shape_len * sizeof(*self->shape); + if (shape_len != self->shape_len) { + MEM_freeN(self->shape); + self->shape = MEM_mallocN(size, __func__); + } + + self->shape_len = shape_len; + memcpy(self->shape, shape, size); + return 0; +} + static int pygpu_buffer__tp_traverse(BPyGPUBuffer *self, visitproc visit, void *arg) { Py_VISIT(self->parent); @@ -280,14 +377,13 @@ static int pygpu_buffer_ass_slice(BPyGPUBuffer *self, return err; } -#define MAX_DIMENSIONS 64 static PyObject *pygpu_buffer__tp_new(PyTypeObject *UNUSED(type), PyObject *args, PyObject *kwds) { PyObject *length_ob, *init = NULL; BPyGPUBuffer *buffer = NULL; Py_ssize_t shape[MAX_DIMENSIONS]; - Py_ssize_t i, shape_len = 0; + Py_ssize_t shape_len = 0; if (kwds && PyDict_Size(kwds)) { PyErr_SetString(PyExc_TypeError, "Buffer(): takes no keyword args"); @@ -300,49 +396,7 @@ static PyObject *pygpu_buffer__tp_new(PyTypeObject *UNUSED(type), PyObject *args return NULL; } - if (PyLong_Check(length_ob)) { - shape_len = 1; - if (((shape[0] = PyLong_AsLong(length_ob)) < 1)) { - PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1"); - return NULL; - } - } - else if (PySequence_Check(length_ob)) { - shape_len = PySequence_Size(length_ob); - if (shape_len > MAX_DIMENSIONS) { - PyErr_SetString(PyExc_AttributeError, - "too many dimensions, max is " STRINGIFY(MAX_DIMENSIONS)); - return NULL; - } - if (shape_len < 1) { - PyErr_SetString(PyExc_AttributeError, "sequence must have at least one dimension"); - return NULL; - } - - for (i = 0; i < shape_len; i++) { - PyObject *ob = PySequence_GetItem(length_ob, i); - if (!PyLong_Check(ob)) { - PyErr_Format(PyExc_TypeError, - "invalid dimension %i, expected an int, not a %.200s", - i, - Py_TYPE(ob)->tp_name); - Py_DECREF(ob); - return NULL; - } - shape[i] = PyLong_AsLong(ob); - Py_DECREF(ob); - - if (shape[i] < 1) { - PyErr_SetString(PyExc_AttributeError, "dimension must be greater than or equal to 1"); - return NULL; - } - } - } - else { - PyErr_Format(PyExc_TypeError, - "invalid second argument argument expected a sequence " - "or an int, not a %.200s", - Py_TYPE(length_ob)->tp_name); + if (!pygpu_buffer_pyobj_as_shape(length_ob, shape, &shape_len)) { return NULL; } @@ -354,11 +408,7 @@ static PyObject *pygpu_buffer__tp_new(PyTypeObject *UNUSED(type), PyObject *args return NULL; } - if (shape_len != pybuffer.ndim || - !pygpu_buffer_dimensions_compare(shape_len, shape, pybuffer.shape)) { - PyErr_Format(PyExc_TypeError, "array size does not match"); - } - else { + if (pygpu_buffer_dimensions_tot_len_compare(shape, shape_len, pybuffer.shape, pybuffer.ndim)) { buffer = pygpu_buffer_make_from_data( init, pygpu_dataformat.value_found, pybuffer.ndim, shape, pybuffer.buf); } @@ -518,7 +568,11 @@ static PyMethodDef pygpu_buffer__tp_methods[] = { }; static PyGetSetDef pygpu_buffer_getseters[] = { - {"dimensions", (getter)pygpu_buffer_dimensions, NULL, NULL, NULL}, + {"dimensions", + (getter)pygpu_buffer_dimensions_get, + (setter)pygpu_buffer_dimensions_set, + NULL, + NULL}, {NULL, NULL, NULL, NULL, NULL}, }; @@ -625,13 +679,7 @@ static size_t pygpu_buffer_calc_size(const int format, const int shape_len, const Py_ssize_t *shape) { - size_t r_size = GPU_texture_dataformat_size(format); - - for (int i = 0; i < shape_len; i++) { - r_size *= shape[i]; - } - - return r_size; + return pygpu_buffer_dimensions_tot_elem(shape, shape_len) * GPU_texture_dataformat_size(format); } size_t bpygpu_Buffer_size(BPyGPUBuffer *buffer) diff --git a/source/blender/python/gpu/gpu_py_framebuffer.c b/source/blender/python/gpu/gpu_py_framebuffer.c index bc393aaafa4..7f64cb90a97 100644 --- a/source/blender/python/gpu/gpu_py_framebuffer.c +++ b/source/blender/python/gpu/gpu_py_framebuffer.c @@ -37,6 +37,8 @@ #include "gpu_py.h" #include "gpu_py_texture.h" +#include "gpu_py.h" +#include "gpu_py_buffer.h" #include "gpu_py_framebuffer.h" /* own include */ /* -------------------------------------------------------------------- */ @@ -46,13 +48,7 @@ static int pygpu_framebuffer_valid_check(BPyGPUFrameBuffer *bpygpu_fb) { if (UNLIKELY(bpygpu_fb->fb == NULL)) { - PyErr_SetString(PyExc_ReferenceError, -#ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD - "GPU framebuffer was freed, no further access is valid" -#else - "GPU framebuffer: internal error" -#endif - ); + PyErr_SetString(PyExc_ReferenceError, "GPU framebuffer was freed, no further access is valid"); return -1; } return 0; @@ -68,10 +64,6 @@ static int pygpu_framebuffer_valid_check(BPyGPUFrameBuffer *bpygpu_fb) static void pygpu_framebuffer_free_if_possible(GPUFrameBuffer *fb) { - if (!fb) { - return; - } - if (GPU_is_init()) { GPU_framebuffer_free(fb); } @@ -80,6 +72,21 @@ static void pygpu_framebuffer_free_if_possible(GPUFrameBuffer *fb) } } +static void pygpu_framebuffer_free_safe(BPyGPUFrameBuffer *self) +{ + if (self->fb) { +#ifndef GPU_NO_USE_PY_REFERENCES + GPU_framebuffer_py_reference_set(self->fb, NULL); + if (!self->shared_reference) +#endif + { + pygpu_framebuffer_free_if_possible(self->fb); + } + + self->fb = NULL; + } +} + /* Keep less than or equal to #FRAMEBUFFER_STACK_DEPTH */ #define GPU_PY_FRAMEBUFFER_STACK_LEN 16 @@ -336,7 +343,7 @@ static PyObject *pygpu_framebuffer__tp_new(PyTypeObject *UNUSED(self), GPUFrameBuffer *fb_python = GPU_framebuffer_create("fb_python"); GPU_framebuffer_config_array(fb_python, config, color_attachements_len + 1); - return BPyGPUFrameBuffer_CreatePyObject(fb_python); + return BPyGPUFrameBuffer_CreatePyObject(fb_python, false); } PyDoc_STRVAR(pygpu_framebuffer_is_bound_doc, @@ -450,6 +457,100 @@ static PyObject *pygpu_framebuffer_viewport_get(BPyGPUFrameBuffer *self, void *U return ret; } +PyDoc_STRVAR( + pygpu_framebuffer_read_color_doc, + ".. function:: read_color(x, y, xsize, ysize, channels, slot, format, data=data)\n" + "\n" + " Read a block of pixels from the frame buffer.\n" + "\n" + " :param x, y: Lower left corner of a rectangular block of pixels.\n" + " :param xsize, ysize: Dimensions of the pixel rectangle.\n" + " :type x, y, xsize, ysize: int\n" + " :param channels: Number of components to read.\n" + " :type channels: int\n" + " :param slot: The framebuffer slot to read data from.\n" + " :type slot: int\n" + " :param format: The format that describes the content of a single channel.\n" + " Possible values are `FLOAT`, `INT`, `UINT`, `UBYTE`, `UINT_24_8` and `10_11_11_REV`.\n" + " :type type: str\n" + " :arg data: Optional Buffer object to fill with the pixels values.\n" + " :type data: :class:`gpu.types.Buffer`\n" + " :return: The Buffer with the read pixels.\n" + " :rtype: :class:`gpu.types.Buffer`\n"); +static PyObject *pygpu_framebuffer_read_color(BPyGPUFrameBuffer *self, + PyObject *args, + PyObject *kwds) +{ + PYGPU_FRAMEBUFFER_CHECK_OBJ(self); + int x, y, w, h, channels; + uint slot; + struct PyC_StringEnum pygpu_dataformat = {bpygpu_dataformat_items, GPU_RGBA8}; + BPyGPUBuffer *py_buffer = NULL; + + static const char *_keywords[] = { + "x", "y", "xsize", "ysize", "channels", "slot", "format", "data", NULL}; + static _PyArg_Parser _parser = {"iiiiiIO&|$O!:GPUTexture.__new__", _keywords, 0}; + if (!_PyArg_ParseTupleAndKeywordsFast(args, + kwds, + &_parser, + &x, + &y, + &w, + &h, + &channels, + &slot, + PyC_ParseStringEnum, + &pygpu_dataformat, + &BPyGPU_BufferType, + &py_buffer)) { + return NULL; + } + + if (!IN_RANGE_INCL(channels, 1, 4)) { + PyErr_SetString(PyExc_AttributeError, "Color channels must be 1, 2, 3 or 4"); + return NULL; + } + + if (slot >= BPYGPU_FB_MAX_COLOR_ATTACHMENT) { + PyErr_SetString(PyExc_ValueError, "slot overflow"); + return NULL; + } + + if (py_buffer) { + if (pygpu_dataformat.value_found != py_buffer->format) { + PyErr_SetString(PyExc_AttributeError, + "the format of the buffer is different from that specified"); + return NULL; + } + + size_t size_curr = bpygpu_Buffer_size(py_buffer); + size_t size_expected = w * h * channels * + GPU_texture_dataformat_size(pygpu_dataformat.value_found); + if (size_curr < size_expected) { + PyErr_SetString(PyExc_BufferError, "the buffer size is smaller than expected"); + return NULL; + } + } + else { + py_buffer = BPyGPU_Buffer_CreatePyObject( + pygpu_dataformat.value_found, (Py_ssize_t[3]){h, w, channels}, 3, NULL); + BLI_assert(bpygpu_Buffer_size(py_buffer) == + w * h * channels * GPU_texture_dataformat_size(pygpu_dataformat.value_found)); + } + + GPU_framebuffer_read_color(self->fb, + x, + y, + w, + h, + channels, + (int)slot, + pygpu_dataformat.value_found, + py_buffer->buf.as_void); + + return (PyObject *)py_buffer; +} + #ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD PyDoc_STRVAR(pygpu_framebuffer_free_doc, ".. method:: free()\n" @@ -459,15 +560,14 @@ PyDoc_STRVAR(pygpu_framebuffer_free_doc, static PyObject *pygpu_framebuffer_free(BPyGPUFrameBuffer *self) { PYGPU_FRAMEBUFFER_CHECK_OBJ(self); - pygpu_framebuffer_free_if_possible(self->fb); - self->fb = NULL; + pygpu_framebuffer_free_safe(self); Py_RETURN_NONE; } #endif static void BPyGPUFrameBuffer__tp_dealloc(BPyGPUFrameBuffer *self) { - pygpu_framebuffer_free_if_possible(self->fb); + pygpu_framebuffer_free_safe(self); Py_TYPE(self)->tp_free((PyObject *)self); } @@ -494,6 +594,10 @@ static struct PyMethodDef pygpu_framebuffer__tp_methods[] = { (PyCFunction)pygpu_framebuffer_viewport_get, METH_NOARGS, pygpu_framebuffer_viewport_get_doc}, + {"read_color", + (PyCFunction)pygpu_framebuffer_read_color, + METH_VARARGS | METH_KEYWORDS, + pygpu_framebuffer_read_color_doc}, #ifdef BPYGPU_USE_GPUOBJ_FREE_METHOD {"free", (PyCFunction)pygpu_framebuffer_free, METH_NOARGS, pygpu_framebuffer_free_doc}, #endif @@ -531,13 +635,35 @@ PyTypeObject BPyGPUFrameBuffer_Type = { /** \name Public API * \{ */ -PyObject *BPyGPUFrameBuffer_CreatePyObject(GPUFrameBuffer *fb) +PyObject *BPyGPUFrameBuffer_CreatePyObject(GPUFrameBuffer *fb, bool shared_reference) { BPyGPUFrameBuffer *self; +#ifndef GPU_NO_USE_PY_REFERENCES + if (shared_reference) { + void **ref = GPU_framebuffer_py_reference_get(fb); + if (ref) { + /* Retrieve BPyGPUFrameBuffer reference. */ + self = (BPyGPUFrameBuffer *)POINTER_OFFSET(ref, -offsetof(BPyGPUFrameBuffer, fb)); + BLI_assert(self->fb == fb); + Py_INCREF(self); + return (PyObject *)self; + } + } +#else + UNUSED_VARS(shared_reference); +#endif + self = PyObject_New(BPyGPUFrameBuffer, &BPyGPUFrameBuffer_Type); self->fb = fb; +#ifndef GPU_NO_USE_PY_REFERENCES + self->shared_reference = shared_reference; + + BLI_assert(GPU_framebuffer_py_reference_get(fb) == NULL); + GPU_framebuffer_py_reference_set(fb, (void **)&self->fb); +#endif + return (PyObject *)self; } diff --git a/source/blender/python/gpu/gpu_py_framebuffer.h b/source/blender/python/gpu/gpu_py_framebuffer.h index 7113e7c35aa..6727328518c 100644 --- a/source/blender/python/gpu/gpu_py_framebuffer.h +++ b/source/blender/python/gpu/gpu_py_framebuffer.h @@ -28,6 +28,11 @@ extern PyTypeObject BPyGPUFrameBuffer_Type; typedef struct BPyGPUFrameBuffer { PyObject_HEAD struct GPUFrameBuffer *fb; + +#ifndef GPU_NO_USE_PY_REFERENCES + bool shared_reference; +#endif } BPyGPUFrameBuffer; -PyObject *BPyGPUFrameBuffer_CreatePyObject(struct GPUFrameBuffer *fb) ATTR_NONNULL(1); +PyObject *BPyGPUFrameBuffer_CreatePyObject(struct GPUFrameBuffer *fb, bool shared_reference) + ATTR_NONNULL(1); diff --git a/source/blender/python/gpu/gpu_py_offscreen.c b/source/blender/python/gpu/gpu_py_offscreen.c index 9e9a0b9066a..28bd24a6877 100644 --- a/source/blender/python/gpu/gpu_py_offscreen.c +++ b/source/blender/python/gpu/gpu_py_offscreen.c @@ -53,6 +53,8 @@ #include "../generic/py_capi_utils.h" #include "gpu_py.h" +#include "gpu_py_texture.h" + #include "gpu_py_offscreen.h" /* own include */ /* Define the free method to avoid breakage. */ @@ -264,6 +266,17 @@ static PyObject *pygpu_offscreen_color_texture_get(BPyGPUOffScreen *self, void * return PyLong_FromLong(GPU_texture_opengl_bindcode(texture)); } +PyDoc_STRVAR(pygpu_offscreen_texture_color_doc, + "The color texture attached.\n" + "\n" + ":type: :class:`gpu.types.GPUTexture`"); +static PyObject *pygpu_offscreen_texture_color_get(BPyGPUOffScreen *self, void *UNUSED(type)) +{ + BPY_GPU_OFFSCREEN_CHECK_OBJ(self); + GPUTexture *texture = GPU_offscreen_color_texture(self->ofs); + return BPyGPUTexture_CreatePyObject(texture, true); +} + PyDoc_STRVAR( pygpu_offscreen_draw_view3d_doc, ".. method:: draw_view3d(scene, view_layer, view3d, region, view_matrix, projection_matrix)\n" @@ -385,6 +398,11 @@ static PyGetSetDef pygpu_offscreen__tp_getseters[] = { (setter)NULL, pygpu_offscreen_color_texture_doc, NULL}, + {"texture_color", + (getter)pygpu_offscreen_texture_color_get, + (setter)NULL, + pygpu_offscreen_texture_color_doc, + NULL}, {"width", (getter)pygpu_offscreen_width_get, (setter)NULL, pygpu_offscreen_width_doc, NULL}, {"height", (getter)pygpu_offscreen_height_get, (setter)NULL, pygpu_offscreen_height_doc, NULL}, {NULL, NULL, NULL, NULL, NULL} /* Sentinel */ diff --git a/source/blender/python/gpu/gpu_py_state.c b/source/blender/python/gpu/gpu_py_state.c index 6b0fade8d1c..173c5afba56 100644 --- a/source/blender/python/gpu/gpu_py_state.c +++ b/source/blender/python/gpu/gpu_py_state.c @@ -25,11 +25,13 @@ #include <Python.h> +#include "GPU_framebuffer.h" #include "GPU_state.h" #include "../generic/py_capi_utils.h" #include "../generic/python_utildefines.h" +#include "gpu_py_framebuffer.h" #include "gpu_py_state.h" /* own include */ /* -------------------------------------------------------------------- */ @@ -334,6 +336,16 @@ static PyObject *pygpu_state_program_point_size_set(PyObject *UNUSED(self), PyOb Py_RETURN_NONE; } +PyDoc_STRVAR(pygpu_state_framebuffer_active_get_doc, + ".. function:: framebuffer_active_get(enable)\n" + "\n" + " Return the active framefuffer in context.\n"); +static PyObject *pygpu_state_framebuffer_active_get(PyObject *UNUSED(self)) +{ + GPUFrameBuffer *fb = GPU_framebuffer_active_get(); + return BPyGPUFrameBuffer_CreatePyObject(fb, true); +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -396,6 +408,10 @@ static struct PyMethodDef pygpu_state__tp_methods[] = { (PyCFunction)pygpu_state_program_point_size_set, METH_O, pygpu_state_program_point_size_set_doc}, + {"active_framebuffer_get", + (PyCFunction)pygpu_state_framebuffer_active_get, + METH_NOARGS, + pygpu_state_framebuffer_active_get_doc}, {NULL, NULL, 0, NULL}, }; diff --git a/source/blender/python/gpu/gpu_py_texture.c b/source/blender/python/gpu/gpu_py_texture.c index 1ae65c1dd11..4df61d35d4c 100644 --- a/source/blender/python/gpu/gpu_py_texture.c +++ b/source/blender/python/gpu/gpu_py_texture.c @@ -247,7 +247,7 @@ static PyObject *pygpu_texture__tp_new(PyTypeObject *UNUSED(self), PyObject *arg return NULL; } - return BPyGPUTexture_CreatePyObject(tex); + return BPyGPUTexture_CreatePyObject(tex, false); } PyDoc_STRVAR(pygpu_texture_width_doc, "Width of the texture.\n\n:type: `int`"); @@ -412,6 +412,9 @@ static PyObject *pygpu_texture_free(BPyGPUTexture *self) static void BPyGPUTexture__tp_dealloc(BPyGPUTexture *self) { if (self->tex) { +#ifndef GPU_NO_USE_PY_REFERENCES + GPU_texture_py_reference_set(self->tex, NULL); +#endif GPU_texture_free(self->tex); } Py_TYPE(self)->tp_free((PyObject *)self); @@ -535,10 +538,7 @@ static PyObject *pygpu_texture_from_image(PyObject *UNUSED(self), PyObject *arg) BKE_imageuser_default(&iuser); GPUTexture *tex = BKE_image_get_gpu_texture(ima, &iuser, NULL); - /* Increase the texture reference count. */ - GPU_texture_ref(tex); - - return BPyGPUTexture_CreatePyObject(tex); + return BPyGPUTexture_CreatePyObject(tex, true); } static struct PyMethodDef pygpu_texture__m_methods[] = { @@ -595,13 +595,33 @@ PyObject *bpygpu_texture_init(void) /** \name Public API * \{ */ -PyObject *BPyGPUTexture_CreatePyObject(GPUTexture *tex) +PyObject *BPyGPUTexture_CreatePyObject(GPUTexture *tex, bool shared_reference) { BPyGPUTexture *self; + if (shared_reference) { +#ifndef GPU_NO_USE_PY_REFERENCES + void **ref = GPU_texture_py_reference_get(tex); + if (ref) { + /* Retrieve BPyGPUTexture reference. */ + self = (BPyGPUTexture *)POINTER_OFFSET(ref, -offsetof(BPyGPUTexture, tex)); + BLI_assert(self->tex == tex); + Py_INCREF(self); + return (PyObject *)self; + } +#endif + + GPU_texture_ref(tex); + } + self = PyObject_New(BPyGPUTexture, &BPyGPUTexture_Type); self->tex = tex; +#ifndef GPU_NO_USE_PY_REFERENCES + BLI_assert(GPU_texture_py_reference_get(tex) == NULL); + GPU_texture_py_reference_set(tex, (void **)&self->tex); +#endif + return (PyObject *)self; } diff --git a/source/blender/python/gpu/gpu_py_texture.h b/source/blender/python/gpu/gpu_py_texture.h index 5130273f971..3eaaa3411bd 100644 --- a/source/blender/python/gpu/gpu_py_texture.h +++ b/source/blender/python/gpu/gpu_py_texture.h @@ -33,4 +33,5 @@ typedef struct BPyGPUTexture { int bpygpu_ParseTexture(PyObject *o, void *p); PyObject *bpygpu_texture_init(void); -PyObject *BPyGPUTexture_CreatePyObject(struct GPUTexture *tex) ATTR_NONNULL(1); +PyObject *BPyGPUTexture_CreatePyObject(struct GPUTexture *tex, bool shared_reference) + ATTR_NONNULL(1); diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt index 9ac8d4d9f47..2be2105d327 100644 --- a/source/blender/python/intern/CMakeLists.txt +++ b/source/blender/python/intern/CMakeLists.txt @@ -79,6 +79,7 @@ set(SRC bpy_rna_driver.c bpy_rna_gizmo.c bpy_rna_id_collection.c + bpy_rna_operator.c bpy_rna_types_capi.c bpy_rna_ui.c bpy_traceback.c @@ -118,6 +119,7 @@ set(SRC bpy_rna_driver.h bpy_rna_gizmo.h bpy_rna_id_collection.h + bpy_rna_operator.h bpy_rna_types_capi.h bpy_rna_ui.h bpy_traceback.h diff --git a/source/blender/python/intern/bpy_interface.c b/source/blender/python/intern/bpy_interface.c index 5f31e0bb74d..4144063cf5c 100644 --- a/source/blender/python/intern/bpy_interface.c +++ b/source/blender/python/intern/bpy_interface.c @@ -167,6 +167,14 @@ void bpy_context_clear(bContext *UNUSED(C), const PyGILState_STATE *gilstate) } } +static void bpy_context_end(bContext *C) +{ + if (UNLIKELY(C == NULL)) { + return; + } + CTX_wm_operator_poll_msg_clear(C); +} + /** * Use for `CTX_*_set(..)` functions need to set values which are later read back as expected. * In this case we don't want the Python context to override the values as it causes problems @@ -524,6 +532,9 @@ void BPY_python_end(void) /* finalizing, no need to grab the state, except when we are a module */ gilstate = PyGILState_Ensure(); + /* Clear Python values in the context so freeing the context after Python exits doesn't crash. */ + bpy_context_end(BPY_context_get()); + /* Decrement user counts of all callback functions. */ BPY_rna_props_clear_all(); diff --git a/source/blender/python/intern/bpy_operator.c b/source/blender/python/intern/bpy_operator.c index 94ad6a8ef78..4a5e2552598 100644 --- a/source/blender/python/intern/bpy_operator.c +++ b/source/blender/python/intern/bpy_operator.c @@ -244,12 +244,16 @@ static PyObject *pyop_call(PyObject *UNUSED(self), PyObject *args) } if (WM_operator_poll_context((bContext *)C, ot, context) == false) { - const char *msg = CTX_wm_operator_poll_msg_get(C); + bool msg_free = false; + const char *msg = CTX_wm_operator_poll_msg_get(C, &msg_free); PyErr_Format(PyExc_RuntimeError, "Operator bpy.ops.%.200s.poll() %.200s", opname, msg ? msg : "failed, context is incorrect"); - CTX_wm_operator_poll_msg_set(C, NULL); /* better set to NULL else it could be used again */ + CTX_wm_operator_poll_msg_clear(C); + if (msg_free) { + MEM_freeN((void *)msg); + } error_val = -1; } else { diff --git a/source/blender/python/intern/bpy_rna_operator.c b/source/blender/python/intern/bpy_rna_operator.c new file mode 100644 index 00000000000..6e0db3eca49 --- /dev/null +++ b/source/blender/python/intern/bpy_rna_operator.c @@ -0,0 +1,150 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup pythonintern + * + * This file extends `bpy.types.Operator` with C/Python API methods and attributes. + */ + +#include <Python.h> + +#include "BLI_string.h" + +#include "BKE_context.h" + +#include "../generic/python_utildefines.h" + +#include "BPY_extern.h" +#include "bpy_capi_utils.h" + +/* -------------------------------------------------------------------- */ +/** \name Operator `poll_message_set` Method + * \{ */ + +static char *pyop_poll_message_get_fn(bContext *UNUSED(C), void *user_data) +{ + PyGILState_STATE gilstate = PyGILState_Ensure(); + + PyObject *py_args = user_data; + PyObject *py_func_or_msg = PyTuple_GET_ITEM(py_args, 0); + + if (PyUnicode_Check(py_func_or_msg)) { + return BLI_strdup(PyUnicode_AsUTF8(py_func_or_msg)); + } + + PyObject *py_args_after_first = PyTuple_GetSlice(py_args, 1, PY_SSIZE_T_MAX); + PyObject *py_msg = PyObject_CallObject(py_func_or_msg, py_args_after_first); + Py_DECREF(py_args_after_first); + + char *msg = NULL; + bool error = false; + + /* NULL for no string. */ + if (py_msg == NULL) { + error = true; + } + else { + if (py_msg == Py_None) { + /* pass */ + } + else if (PyUnicode_Check(py_msg)) { + msg = BLI_strdup(PyUnicode_AsUTF8(py_msg)); + } + else { + PyErr_Format(PyExc_TypeError, + "poll_message_set(function, ...): expected string or None, got %.200s", + Py_TYPE(py_msg)->tp_name); + error = true; + } + Py_DECREF(py_msg); + } + + if (error) { + PyErr_Print(); + PyErr_Clear(); + } + + PyGILState_Release(gilstate); + return msg; +} + +static void pyop_poll_message_free_fn(bContext *UNUSED(C), void *user_data) +{ + /* Handles the GIL. */ + BPY_DECREF(user_data); +} + +PyDoc_STRVAR(BPY_rna_operator_poll_message_set_doc, + ".. method:: poll_message_set(message, ...)\n" + "\n" + " Set the message to show in the tool-tip when poll fails.\n" + "\n" + " When message is callable, " + "additional user defined positional arguments are passed to the message function.\n" + "\n" + " :param message: The message or a function that returns the message.\n" + " :type message: string or a callable that returns a string or None.\n"); + +static PyObject *BPY_rna_operator_poll_message_set(PyObject *UNUSED(self), PyObject *args) +{ + const ssize_t args_len = PyTuple_GET_SIZE(args); + if (args_len == 0) { + PyErr_SetString(PyExc_ValueError, + "poll_message_set(message, ...): requires a message argument"); + return NULL; + } + + PyObject *py_func_or_msg = PyTuple_GET_ITEM(args, 0); + + if (PyUnicode_Check(py_func_or_msg)) { + if (args_len > 1) { + PyErr_SetString(PyExc_ValueError, + "poll_message_set(message): does not support additional arguments"); + return NULL; + } + } + else if (PyCallable_Check(py_func_or_msg)) { + /* pass */ + } + else { + PyErr_Format(PyExc_TypeError, + "poll_message_set(message, ...): " + "expected at least 1 string or callable argument, got %.200s", + Py_TYPE(py_func_or_msg)->tp_name); + return NULL; + } + + bContext *C = BPY_context_get(); + struct bContextPollMsgDyn_Params params = { + .get_fn = pyop_poll_message_get_fn, + .free_fn = pyop_poll_message_free_fn, + .user_data = Py_INCREF_RET(args), + }; + + CTX_wm_operator_poll_msg_set_dynamic(C, ¶ms); + + Py_RETURN_NONE; +} + +PyMethodDef BPY_rna_operator_poll_message_set_method_def = { + "poll_message_set", + (PyCFunction)BPY_rna_operator_poll_message_set, + METH_VARARGS | METH_STATIC, + BPY_rna_operator_poll_message_set_doc, +}; + +/** \} */ diff --git a/source/blender/python/intern/bpy_rna_operator.h b/source/blender/python/intern/bpy_rna_operator.h new file mode 100644 index 00000000000..8040d8a492a --- /dev/null +++ b/source/blender/python/intern/bpy_rna_operator.h @@ -0,0 +1,31 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** \file + * \ingroup pythonintern + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +extern PyMethodDef BPY_rna_operator_poll_message_set_method_def; + +#ifdef __cplusplus +} +#endif diff --git a/source/blender/python/intern/bpy_rna_types_capi.c b/source/blender/python/intern/bpy_rna_types_capi.c index 9b15e84663d..2f6e197d1e2 100644 --- a/source/blender/python/intern/bpy_rna_types_capi.c +++ b/source/blender/python/intern/bpy_rna_types_capi.c @@ -41,6 +41,8 @@ #include "bpy_rna_types_capi.h" #include "bpy_rna_ui.h" +#include "bpy_rna_operator.h" + #include "../generic/py_capi_utils.h" #include "RNA_access.h" @@ -87,6 +89,17 @@ static struct PyMethodDef pyrna_uilayout_methods[] = { /** \} */ /* -------------------------------------------------------------------- */ +/** \name Operator + * \{ */ + +static struct PyMethodDef pyrna_operator_methods[] = { + {NULL, NULL, 0, NULL}, /* #BPY_rna_operator_poll_message_set */ + {NULL, NULL, 0, NULL}, +}; + +/** \} */ + +/* -------------------------------------------------------------------- */ /** \name Window Manager Clipboard Property * * Avoid using the RNA API because this value may change between checking its length @@ -228,6 +241,11 @@ void BPY_rna_types_extend_capi(void) /* Space */ pyrna_struct_type_extend_capi(&RNA_Space, pyrna_space_methods, NULL); + /* wmOperator */ + ARRAY_SET_ITEMS(pyrna_operator_methods, BPY_rna_operator_poll_message_set_method_def); + BLI_assert(ARRAY_SIZE(pyrna_operator_methods) == 2); + pyrna_struct_type_extend_capi(&RNA_Operator, pyrna_operator_methods, NULL); + /* WindowManager */ pyrna_struct_type_extend_capi( &RNA_WindowManager, pyrna_windowmanager_methods, pyrna_windowmanager_getset); |