Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/python')
-rw-r--r--source/blender/python/gpu/gpu_py_buffer.c176
-rw-r--r--source/blender/python/gpu/gpu_py_framebuffer.c158
-rw-r--r--source/blender/python/gpu/gpu_py_framebuffer.h7
-rw-r--r--source/blender/python/gpu/gpu_py_offscreen.c18
-rw-r--r--source/blender/python/gpu/gpu_py_state.c16
-rw-r--r--source/blender/python/gpu/gpu_py_texture.c32
-rw-r--r--source/blender/python/gpu/gpu_py_texture.h3
-rw-r--r--source/blender/python/intern/CMakeLists.txt2
-rw-r--r--source/blender/python/intern/bpy_interface.c11
-rw-r--r--source/blender/python/intern/bpy_operator.c8
-rw-r--r--source/blender/python/intern/bpy_rna_operator.c150
-rw-r--r--source/blender/python/intern/bpy_rna_operator.h31
-rw-r--r--source/blender/python/intern/bpy_rna_types_capi.c18
13 files changed, 540 insertions, 90 deletions
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..457ddcfc931 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 weak_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, &params);
+
+ 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);