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:
authorCampbell Barton <ideasman42@gmail.com>2017-06-26 08:57:14 +0300
committerCampbell Barton <ideasman42@gmail.com>2017-06-26 09:38:04 +0300
commit28b2f1c30504ce0e437b21f7964282fcd6894421 (patch)
tree8653eeeb0edb90cf41a93bc2b196ac55739f2a32 /source/blender/python/intern
parentc9e33b36de25d272a9896ad0a85dcc69ee0692ba (diff)
Manipulator: Python API
Initial support for Python/Manipulator integration from 'custom-manipulators' branch. Supports: - Registering custom manipulators & manipulator-groups. - Modifying RNA properties, custom values via get/set callbacks, or invoking an operator. - Drawing shape presets for Python defined manipulators (arrow, circle, face-maps) Limitations: - Only float properties supported. - Drawing only supported via shape presets. (we'll likely want a way to define custom geometry or draw directly). - When to refresh, recalculate manipulators will likely need integration with notifier system. Development will be continued in the 2.8 branch
Diffstat (limited to 'source/blender/python/intern')
-rw-r--r--source/blender/python/intern/CMakeLists.txt4
-rw-r--r--source/blender/python/intern/bpy.c3
-rw-r--r--source/blender/python/intern/bpy_intern_string.c4
-rw-r--r--source/blender/python/intern/bpy_intern_string.h1
-rw-r--r--source/blender/python/intern/bpy_manipulator_wrap.c231
-rw-r--r--source/blender/python/intern/bpy_manipulator_wrap.h36
-rw-r--r--source/blender/python/intern/bpy_rna.c34
-rw-r--r--source/blender/python/intern/bpy_rna_manipulator.c341
-rw-r--r--source/blender/python/intern/bpy_rna_manipulator.h32
9 files changed, 673 insertions, 13 deletions
diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt
index be4db6477fe..967e90d22cb 100644
--- a/source/blender/python/intern/CMakeLists.txt
+++ b/source/blender/python/intern/CMakeLists.txt
@@ -65,6 +65,7 @@ set(SRC
bpy_intern_string.c
bpy_library_load.c
bpy_library_write.c
+ bpy_manipulator_wrap.c
bpy_operator.c
bpy_operator_wrap.c
bpy_path.c
@@ -75,6 +76,7 @@ set(SRC
bpy_rna_callback.c
bpy_rna_driver.c
bpy_rna_id_collection.c
+ bpy_rna_manipulator.c
bpy_traceback.c
bpy_util.c
bpy_utils_previews.c
@@ -97,6 +99,7 @@ set(SRC
bpy_driver.h
bpy_intern_string.h
bpy_library.h
+ bpy_manipulator_wrap.h
bpy_operator.h
bpy_operator_wrap.h
bpy_path.h
@@ -106,6 +109,7 @@ set(SRC
bpy_rna_callback.h
bpy_rna_driver.h
bpy_rna_id_collection.h
+ bpy_rna_manipulator.h
bpy_traceback.h
bpy_util.h
bpy_utils_previews.h
diff --git a/source/blender/python/intern/bpy.c b/source/blender/python/intern/bpy.c
index 5bbfb4912e6..4a29d4f8da1 100644
--- a/source/blender/python/intern/bpy.c
+++ b/source/blender/python/intern/bpy.c
@@ -46,6 +46,7 @@
#include "bpy_rna.h"
#include "bpy_app.h"
#include "bpy_rna_id_collection.h"
+#include "bpy_rna_manipulator.h"
#include "bpy_props.h"
#include "bpy_library.h"
#include "bpy_operator.h"
@@ -327,6 +328,8 @@ void BPy_init_modules(void)
BPY_rna_id_collection_module(mod);
+ BPY_rna_manipulator_module(mod);
+
bpy_import_test("bpy_types");
PyModule_AddObject(mod, "data", BPY_rna_module()); /* imports bpy_types by running this */
bpy_import_test("bpy_types");
diff --git a/source/blender/python/intern/bpy_intern_string.c b/source/blender/python/intern/bpy_intern_string.c
index e9d84b2099b..6911e985e93 100644
--- a/source/blender/python/intern/bpy_intern_string.c
+++ b/source/blender/python/intern/bpy_intern_string.c
@@ -34,7 +34,7 @@
#include "BLI_utildefines.h"
-static PyObject *bpy_intern_str_arr[15];
+static PyObject *bpy_intern_str_arr[16];
PyObject *bpy_intern_str___doc__;
PyObject *bpy_intern_str___main__;
@@ -44,6 +44,7 @@ PyObject *bpy_intern_str___slots__;
PyObject *bpy_intern_str_attr;
PyObject *bpy_intern_str_bl_property;
PyObject *bpy_intern_str_bl_rna;
+PyObject *bpy_intern_str_bl_target_properties;
PyObject *bpy_intern_str_bpy_types;
PyObject *bpy_intern_str_frame;
PyObject *bpy_intern_str_order;
@@ -67,6 +68,7 @@ void bpy_intern_string_init(void)
BPY_INTERN_STR(bpy_intern_str_attr, "attr");
BPY_INTERN_STR(bpy_intern_str_bl_property, "bl_property");
BPY_INTERN_STR(bpy_intern_str_bl_rna, "bl_rna");
+ BPY_INTERN_STR(bpy_intern_str_bl_target_properties, "bl_target_properties");
BPY_INTERN_STR(bpy_intern_str_bpy_types, "bpy.types");
BPY_INTERN_STR(bpy_intern_str_frame, "frame");
BPY_INTERN_STR(bpy_intern_str_order, "order");
diff --git a/source/blender/python/intern/bpy_intern_string.h b/source/blender/python/intern/bpy_intern_string.h
index 66c469f70bc..998c312c321 100644
--- a/source/blender/python/intern/bpy_intern_string.h
+++ b/source/blender/python/intern/bpy_intern_string.h
@@ -38,6 +38,7 @@ extern PyObject *bpy_intern_str___slots__;
extern PyObject *bpy_intern_str_attr;
extern PyObject *bpy_intern_str_bl_property;
extern PyObject *bpy_intern_str_bl_rna;
+extern PyObject *bpy_intern_str_bl_target_properties;
extern PyObject *bpy_intern_str_bpy_types;
extern PyObject *bpy_intern_str_frame;
extern PyObject *bpy_intern_str_order;
diff --git a/source/blender/python/intern/bpy_manipulator_wrap.c b/source/blender/python/intern/bpy_manipulator_wrap.c
new file mode 100644
index 00000000000..53b6285e880
--- /dev/null
+++ b/source/blender/python/intern/bpy_manipulator_wrap.c
@@ -0,0 +1,231 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * 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.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/python/intern/bpy_manipulator_wrap.c
+ * \ingroup pythonintern
+ *
+ * This file is so Python can define widget-group's that C can call into.
+ * The generic callback functions for Python widget-group are defines in
+ * 'rna_wm.c', some calling into functions here to do python specific
+ * functionality.
+ *
+ * \note This follows 'bpy_operator_wrap.c' very closely.
+ * Keep in sync unless there is good reason not to!
+ */
+
+#include <Python.h>
+
+#include "BLI_utildefines.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "bpy_rna.h"
+#include "bpy_intern_string.h"
+#include "bpy_manipulator_wrap.h" /* own include */
+
+/* we may want to add, but not now */
+
+/* -------------------------------------------------------------------- */
+
+/** \name Manipulator
+ * \{ */
+
+
+static bool bpy_manipulatortype_target_property_def(
+ wmManipulatorType *wt, PyObject *item)
+{
+ /* Note: names based on 'rna_rna.c' */
+ PyObject *empty_tuple = PyTuple_New(0);
+ static const char * const _keywords[] = {"id", "type", "array_length", NULL};
+ static _PyArg_Parser _parser = {"|$ssi:register_class", _keywords, 0};
+
+ struct {
+ char *id;
+ char *type_id; int type;
+ int array_length;
+ } params = {
+ .id = NULL, /* not optional */
+ .type = PROP_FLOAT,
+ .type_id = NULL,
+ .array_length = 1,
+ };
+
+ if (!_PyArg_ParseTupleAndKeywordsFast(
+ empty_tuple, item,
+ &_parser,
+ &params.id,
+ &params.type_id,
+ &params.array_length))
+ {
+ goto fail;
+ }
+
+ if (params.id == NULL) {
+ PyErr_SetString(PyExc_ValueError, "'id' argument not given");
+ goto fail;
+ }
+
+ if ((params.type_id != NULL) &&
+ pyrna_enum_value_from_id(
+ rna_enum_property_type_items, params.type_id, &params.type, "'type' enum value") == -1)
+ {
+ goto fail;
+ }
+ else {
+ params.type = rna_enum_property_type_items[params.type].value;
+ }
+
+ if ((params.array_length < 1 || params.array_length > RNA_MAX_ARRAY_LENGTH)) {
+ PyErr_SetString(PyExc_ValueError, "'array_length' out of range");
+ goto fail;
+ }
+
+ WM_manipulatortype_target_property_def(wt, params.id, params.type, params.array_length);
+ Py_DECREF(empty_tuple);
+ return true;
+
+fail:
+ Py_DECREF(empty_tuple);
+ return false;
+}
+
+static void manipulator_properties_init(wmManipulatorType *wt)
+{
+ PyTypeObject *py_class = wt->ext.data;
+ RNA_struct_blender_type_set(wt->ext.srna, wt);
+
+ /* only call this so pyrna_deferred_register_class gives a useful error
+ * WM_operatortype_append_ptr will call RNA_def_struct_identifier
+ * later */
+ RNA_def_struct_identifier(wt->srna, wt->idname);
+
+ if (pyrna_deferred_register_class(wt->srna, py_class) != 0) {
+ PyErr_Print(); /* failed to register operator props */
+ PyErr_Clear();
+ }
+
+ /* Extract target property definitions from 'bl_target_properties' */
+ {
+ /* picky developers will notice that 'bl_targets' won't work with inheritance
+ * get direct from the dict to avoid raising a load of attribute errors (yes this isnt ideal) - campbell */
+ PyObject *py_class_dict = py_class->tp_dict;
+ PyObject *bl_target_properties = PyDict_GetItem(py_class_dict, bpy_intern_str_bl_target_properties);
+ PyObject *bl_target_properties_fast;
+
+ if (!(bl_target_properties_fast = PySequence_Fast(bl_target_properties, "bl_target_properties sequence"))) {
+ /* PySequence_Fast sets the error */
+ PyErr_Print();
+ PyErr_Clear();
+ return;
+ }
+
+ const uint items_len = PySequence_Fast_GET_SIZE(bl_target_properties_fast);
+ PyObject **items = PySequence_Fast_ITEMS(bl_target_properties_fast);
+
+ for (uint i = 0; i < items_len; i++) {
+ if (!bpy_manipulatortype_target_property_def(wt, items[i])) {
+ PyErr_Print();
+ PyErr_Clear();
+ break;
+ }
+ }
+
+ Py_DECREF(bl_target_properties_fast);
+ }
+}
+
+void BPY_RNA_manipulator_wrapper(wmManipulatorType *wt, void *userdata)
+{
+ /* take care not to overwrite anything set in
+ * WM_manipulatormaptype_group_link_ptr before opfunc() is called */
+ StructRNA *srna = wt->srna;
+ *wt = *((wmManipulatorType *)userdata);
+ wt->srna = srna; /* restore */
+
+ /* don't do translations here yet */
+#if 0
+ /* Use i18n context from ext.srna if possible (py manipulatorgroups). */
+ if (wt->ext.srna) {
+ RNA_def_struct_translation_context(wt->srna, RNA_struct_translation_context(wt->ext.srna));
+ }
+#endif
+
+ wt->struct_size = sizeof(wmManipulator);
+
+ manipulator_properties_init(wt);
+}
+
+/** \} */
+
+
+/* -------------------------------------------------------------------- */
+
+/** \name Manipulator Group
+ * \{ */
+
+static void manipulatorgroup_properties_init(wmManipulatorGroupType *wgt)
+{
+#ifdef USE_SRNA
+ PyTypeObject *py_class = wgt->ext.data;
+#endif
+ RNA_struct_blender_type_set(wgt->ext.srna, wgt);
+
+#ifdef USE_SRNA
+ /* only call this so pyrna_deferred_register_class gives a useful error
+ * WM_operatortype_append_ptr will call RNA_def_struct_identifier
+ * later */
+ RNA_def_struct_identifier(wgt->srna, wgt->idname);
+
+ if (pyrna_deferred_register_class(wgt->srna, py_class) != 0) {
+ PyErr_Print(); /* failed to register operator props */
+ PyErr_Clear();
+ }
+#endif
+}
+
+void BPY_RNA_manipulatorgroup_wrapper(wmManipulatorGroupType *wgt, void *userdata)
+{
+ /* take care not to overwrite anything set in
+ * WM_manipulatormaptype_group_link_ptr before opfunc() is called */
+#ifdef USE_SRNA
+ StructRNA *srna = wgt->srna;
+#endif
+ *wgt = *((wmManipulatorGroupType *)userdata);
+#ifdef USE_SRNA
+ wgt->srna = srna; /* restore */
+#endif
+
+#ifdef USE_SRNA
+ /* Use i18n context from ext.srna if possible (py manipulatorgroups). */
+ if (wgt->ext.srna) {
+ RNA_def_struct_translation_context(wgt->srna, RNA_struct_translation_context(wgt->ext.srna));
+ }
+#endif
+
+ manipulatorgroup_properties_init(wgt);
+}
+
+/** \} */
+
diff --git a/source/blender/python/intern/bpy_manipulator_wrap.h b/source/blender/python/intern/bpy_manipulator_wrap.h
new file mode 100644
index 00000000000..3f739e26059
--- /dev/null
+++ b/source/blender/python/intern/bpy_manipulator_wrap.h
@@ -0,0 +1,36 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * 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.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/python/intern/bpy_manipulator_wrap.h
+ * \ingroup pythonintern
+ */
+
+#ifndef __BPY_MANIPULATOR_WRAP_H__
+#define __BPY_MANIPULATOR_WRAP_H__
+
+struct wmManipulatorType;
+struct wmManipulatorGroupType;
+
+/* exposed to rna/wm api */
+void BPY_RNA_manipulator_wrapper(struct wmManipulatorType *wt, void *userdata);
+void BPY_RNA_manipulatorgroup_wrapper(struct wmManipulatorGroupType *wgt, void *userdata);
+
+#endif /* __BPY_MANIPULATOR_WRAP_H__ */
+
diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c
index 6f3d0145d87..51e179fb317 100644
--- a/source/blender/python/intern/bpy_rna.c
+++ b/source/blender/python/intern/bpy_rna.c
@@ -1839,19 +1839,28 @@ static int pyrna_py_to_prop(
* class mixing if this causes problems in the future it should be removed.
*/
if ((ptr_type == &RNA_AnyType) &&
- (BPy_StructRNA_Check(value)) &&
- (RNA_struct_is_a(((BPy_StructRNA *)value)->ptr.type, &RNA_Operator)))
+ (BPy_StructRNA_Check(value)))
{
- value = PyObject_GetAttr(value, bpy_intern_str_properties);
- value_new = value;
+ const StructRNA *base_type =
+ RNA_struct_base_child_of(((const BPy_StructRNA *)value)->ptr.type, NULL);
+ if (ELEM(base_type, &RNA_Operator, &RNA_Manipulator)) {
+ value = PyObject_GetAttr(value, bpy_intern_str_properties);
+ value_new = value;
+ }
}
-
- /* if property is an OperatorProperties pointer and value is a map,
+ /* if property is an OperatorProperties/ManipulatorProperties pointer and value is a map,
* forward back to pyrna_pydict_to_props */
- if (RNA_struct_is_a(ptr_type, &RNA_OperatorProperties) && PyDict_Check(value)) {
- PointerRNA opptr = RNA_property_pointer_get(ptr, prop);
- return pyrna_pydict_to_props(&opptr, value, false, error_prefix);
+ if (PyDict_Check(value)) {
+ const StructRNA *base_type = RNA_struct_base_child_of(ptr_type, NULL);
+ if (base_type == &RNA_OperatorProperties) {
+ PointerRNA opptr = RNA_property_pointer_get(ptr, prop);
+ return pyrna_pydict_to_props(&opptr, value, false, error_prefix);
+ }
+ else if (base_type == &RNA_ManipulatorProperties) {
+ PointerRNA opptr = RNA_property_pointer_get(ptr, prop);
+ return pyrna_pydict_to_props(&opptr, value, false, error_prefix);
+ }
}
/* another exception, allow to pass a collection as an RNA property */
@@ -6926,7 +6935,7 @@ static PyObject *pyrna_basetype_dir(BPy_BaseTypeRNA *self)
StructRNA *srna = itemptr.data;
StructRNA *srna_base = RNA_struct_base(itemptr.data);
/* skip own operators, these double up [#29666] */
- if (srna_base == &RNA_Operator) {
+ if (ELEM(srna_base, &RNA_Operator, &RNA_Manipulator)) {
/* do nothing */
}
else {
@@ -7456,7 +7465,8 @@ static int bpy_class_call(bContext *C, PointerRNA *ptr, FunctionRNA *func, Param
PyGILState_STATE gilstate;
#ifdef USE_PEDANTIC_WRITE
- const bool is_operator = RNA_struct_is_a(ptr->type, &RNA_Operator);
+ const bool is_readonly_init = !(RNA_struct_is_a(ptr->type, &RNA_Operator) ||
+ RNA_struct_is_a(ptr->type, &RNA_Manipulator));
// const char *func_id = RNA_function_identifier(func); /* UNUSED */
/* testing, for correctness, not operator and not draw function */
const bool is_readonly = !(RNA_function_flag(func) & FUNC_ALLOW_WRITE);
@@ -7521,7 +7531,7 @@ static int bpy_class_call(bContext *C, PointerRNA *ptr, FunctionRNA *func, Param
if (py_class->tp_init) {
#ifdef USE_PEDANTIC_WRITE
const int prev_write = rna_disallow_writes;
- rna_disallow_writes = is_operator ? false : true; /* only operators can write on __init__ */
+ rna_disallow_writes = is_readonly_init ? false : true; /* only operators can write on __init__ */
#endif
/* true in most cases even when the class its self doesn't define an __init__ function. */
diff --git a/source/blender/python/intern/bpy_rna_manipulator.c b/source/blender/python/intern/bpy_rna_manipulator.c
new file mode 100644
index 00000000000..245735f679e
--- /dev/null
+++ b/source/blender/python/intern/bpy_rna_manipulator.c
@@ -0,0 +1,341 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * 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.
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/python/intern/bpy_rna_manipulator.c
+ * \ingroup pythonintern
+ *
+ * .
+ */
+
+#include <Python.h>
+#include <stddef.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+
+#include "BKE_main.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "bpy_util.h"
+#include "bpy_rna_manipulator.h"
+
+#include "../generic/py_capi_utils.h"
+#include "../generic/python_utildefines.h"
+
+#include "RNA_access.h"
+#include "RNA_types.h"
+#include "RNA_enum_types.h"
+
+#include "bpy_rna.h"
+
+enum {
+ BPY_MANIPULATOR_FN_SLOT_GET = 0,
+ BPY_MANIPULATOR_FN_SLOT_SET,
+ BPY_MANIPULATOR_FN_SLOT_RANGE_GET,
+};
+#define BPY_MANIPULATOR_FN_SLOT_LEN (BPY_MANIPULATOR_FN_SLOT_RANGE_GET + 1)
+
+struct BPyManipulatorHandlerUserData {
+
+ PyObject *fn_slots[BPY_MANIPULATOR_FN_SLOT_LEN];
+};
+
+static void py_rna_manipulator_handler_get_cb(
+ const wmManipulator *UNUSED(mpr), wmManipulatorProperty *mpr_prop,
+ void *value_p)
+{
+ PyGILState_STATE gilstate = PyGILState_Ensure();
+
+ struct BPyManipulatorHandlerUserData *data = mpr_prop->custom_func.user_data;
+ PyObject *ret = PyObject_CallObject(data->fn_slots[BPY_MANIPULATOR_FN_SLOT_GET], NULL);
+ if (ret == NULL) {
+ goto fail;
+ }
+
+ if (mpr_prop->type->data_type == PROP_FLOAT) {
+ float *value = value_p;
+ if (mpr_prop->type->array_length == 1) {
+ if (((*value = PyFloat_AsDouble(ret)) == -1.0f && PyErr_Occurred()) == 0) {
+ goto fail;
+ }
+ }
+ else {
+ if (PyC_AsArray(value, ret, mpr_prop->type->array_length, &PyFloat_Type, false,
+ "Manipulator get callback: ") == -1)
+ {
+ goto fail;
+ }
+ }
+ }
+ else {
+ PyErr_SetString(PyExc_AttributeError, "internal error, unsupported type");
+ goto fail;
+ }
+
+ Py_DECREF(ret);
+
+ PyGILState_Release(gilstate);
+ return;
+
+fail:
+ PyErr_Print();
+ PyErr_Clear();
+
+ PyGILState_Release(gilstate);
+}
+
+static void py_rna_manipulator_handler_set_cb(
+ const wmManipulator *UNUSED(mpr), wmManipulatorProperty *mpr_prop,
+ const void *value_p)
+{
+ PyGILState_STATE gilstate = PyGILState_Ensure();
+
+ struct BPyManipulatorHandlerUserData *data = mpr_prop->custom_func.user_data;
+
+ PyObject *args = PyTuple_New(1);
+
+ if (mpr_prop->type->data_type == PROP_FLOAT) {
+ const float *value = value_p;
+ PyObject *py_value;
+ if (mpr_prop->type->array_length == 1) {
+ py_value = PyFloat_FromDouble(*value);
+ }
+ else {
+ py_value = PyC_FromArray((void *)value, mpr_prop->type->array_length, &PyFloat_Type, false,
+ "Manipulator set callback: ");
+ }
+ if (py_value == NULL) {
+ goto fail;
+ }
+ PyTuple_SET_ITEM(args, 0, py_value);
+ }
+ else {
+ PyErr_SetString(PyExc_AttributeError, "internal error, unsupported type");
+ goto fail;
+ }
+
+ PyObject *ret = PyObject_CallObject(data->fn_slots[BPY_MANIPULATOR_FN_SLOT_SET], args);
+ if (ret == NULL) {
+ goto fail;
+ }
+ Py_DECREF(ret);
+
+ PyGILState_Release(gilstate);
+ return;
+
+fail:
+ PyErr_Print();
+ PyErr_Clear();
+
+ Py_DECREF(args);
+
+ PyGILState_Release(gilstate);
+}
+
+static void py_rna_manipulator_handler_range_get_cb(
+ const wmManipulator *UNUSED(mpr), wmManipulatorProperty *mpr_prop,
+ void *value_p)
+{
+ struct BPyManipulatorHandlerUserData *data = mpr_prop->custom_func.user_data;
+
+ PyGILState_STATE gilstate = PyGILState_Ensure();
+
+ PyObject *ret = PyObject_CallObject(data->fn_slots[BPY_MANIPULATOR_FN_SLOT_RANGE_GET], NULL);
+ if (ret == NULL) {
+ goto fail;
+ }
+
+ if (!PyTuple_Check(ret)) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected a tuple, not %.200s",
+ Py_TYPE(ret)->tp_name);
+ goto fail;
+ }
+
+ if (PyTuple_GET_SIZE(ret) != 2) {
+ PyErr_Format(PyExc_TypeError,
+ "Expected a tuple of size 2, not %d",
+ PyTuple_GET_SIZE(ret));
+ goto fail;
+ }
+
+ if (mpr_prop->type->data_type == PROP_FLOAT) {
+ float range[2];
+ for (int i = 0; i < 2; i++) {
+ if (((range[i] = PyFloat_AsDouble(PyTuple_GET_ITEM(ret, i))) == -1.0f && PyErr_Occurred()) == 0) {
+ /* pass */
+ }
+ else {
+ goto fail;
+ }
+ }
+ memcpy(value_p, range, sizeof(range));
+ }
+ else {
+ PyErr_SetString(PyExc_AttributeError, "internal error, unsupported type");
+ goto fail;
+ }
+
+ Py_DECREF(ret);
+ PyGILState_Release(gilstate);
+ return;
+
+fail:
+ Py_XDECREF(ret);
+
+ PyErr_Print();
+ PyErr_Clear();
+
+ PyGILState_Release(gilstate);
+}
+
+static void py_rna_manipulator_handler_free_cb(
+ const wmManipulator *UNUSED(mpr), wmManipulatorProperty *mpr_prop)
+{
+ struct BPyManipulatorHandlerUserData *data = mpr_prop->custom_func.user_data;
+
+ PyGILState_STATE gilstate = PyGILState_Ensure();
+ for (int i = 0; i < BPY_MANIPULATOR_FN_SLOT_LEN; i++) {
+ Py_XDECREF(data->fn_slots[i]);
+ }
+ PyGILState_Release(gilstate);
+
+ MEM_freeN(data);
+
+}
+
+PyDoc_STRVAR(bpy_manipulator_target_set_handler_doc,
+".. method:: target_set_handler(target, get, set, range=None):\n"
+"\n"
+" Assigns callbacks to a manipulators property.\n"
+"\n"
+" :arg get: Function that returns the value for this property (single value or sequence).\n"
+" :type get: callable\n"
+" :arg set: Function that takes a single value argument and applies it.\n"
+" :type set: callable\n"
+" :arg range: Function that returns a (min, max) tuple for manipulators that use a range.\n"
+" :type range: callable\n"
+);
+static PyObject *bpy_manipulator_target_set_handler(PyObject *UNUSED(self), PyObject *args, PyObject *kwds)
+{
+ /* Note: this is a counter-part to functions:
+ * 'Manipulator.target_set_prop & target_set_operator'
+ * (see: rna_wm_manipulator_api.c). conventions should match. */
+ static const char * const _keywords[] = {"self", "target", "get", "set", "range", NULL};
+ static _PyArg_Parser _parser = {"Os|$OOO:target_set_handler", _keywords, 0};
+
+ PyGILState_STATE gilstate = PyGILState_Ensure();
+
+ struct {
+ PyObject *self;
+ char *target;
+ PyObject *py_fn_slots[BPY_MANIPULATOR_FN_SLOT_LEN];
+ } params = {
+ .self = NULL,
+ .target = NULL,
+ .py_fn_slots = {NULL},
+ };
+
+ if (!_PyArg_ParseTupleAndKeywordsFast(
+ args, kwds,
+ &_parser,
+ &params.self,
+ &params.target,
+ &params.py_fn_slots[BPY_MANIPULATOR_FN_SLOT_GET],
+ &params.py_fn_slots[BPY_MANIPULATOR_FN_SLOT_SET],
+ &params.py_fn_slots[BPY_MANIPULATOR_FN_SLOT_RANGE_GET]))
+ {
+ goto fail;
+ }
+
+ wmManipulator *mpr = ((BPy_StructRNA *)params.self)->ptr.data;
+
+ const wmManipulatorPropertyType *mpr_prop_type =
+ WM_manipulatortype_target_property_find(mpr->type, params.target);
+ if (mpr_prop_type == NULL) {
+ PyErr_Format(PyExc_ValueError,
+ "Manipulator target property '%s.%s' not found",
+ mpr->type->idname, params.target);
+ goto fail;
+ }
+
+ {
+ const int slots_required = 2;
+ const int slots_start = 2;
+ for (int i = 0; i < BPY_MANIPULATOR_FN_SLOT_LEN; i++) {
+ if (params.py_fn_slots[i] == NULL) {
+ if (i < slots_required) {
+ PyErr_Format(PyExc_ValueError, "Argument '%s' not given", _keywords[slots_start + i]);
+ goto fail;
+ }
+ }
+ else if (!PyCallable_Check(params.py_fn_slots[i])) {
+ PyErr_Format(PyExc_ValueError, "Argument '%s' not callable", _keywords[slots_start + i]);
+ goto fail;
+ }
+ }
+ }
+
+ struct BPyManipulatorHandlerUserData *data = MEM_callocN(sizeof(*data), __func__);
+
+ for (int i = 0; i < BPY_MANIPULATOR_FN_SLOT_LEN; i++) {
+ data->fn_slots[i] = params.py_fn_slots[i];
+ Py_XINCREF(params.py_fn_slots[i]);
+ }
+
+ WM_manipulator_target_property_def_func_ptr(
+ mpr, mpr_prop_type,
+ &(const struct wmManipulatorPropertyFnParams) {
+ .value_get_fn = py_rna_manipulator_handler_get_cb,
+ .value_set_fn = py_rna_manipulator_handler_set_cb,
+ .range_get_fn = py_rna_manipulator_handler_range_get_cb,
+ .free_fn = py_rna_manipulator_handler_free_cb,
+ .user_data = data,
+ });
+
+ PyGILState_Release(gilstate);
+
+ Py_RETURN_NONE;
+
+fail:
+ PyGILState_Release(gilstate);
+ return NULL;
+}
+
+int BPY_rna_manipulator_module(PyObject *mod_par)
+{
+ static PyMethodDef method_def = {
+ "target_set_handler", (PyCFunction)bpy_manipulator_target_set_handler, METH_VARARGS | METH_KEYWORDS,
+ bpy_manipulator_target_set_handler_doc};
+
+ PyObject *func = PyCFunction_New(&method_def, NULL);
+ PyObject *func_inst = PyInstanceMethod_New(func);
+
+
+ /* TODO, return a type that binds nearly to a method. */
+ PyModule_AddObject(mod_par, "_rna_manipulator_target_set_handler", func_inst);
+
+ return 0;
+}
+
+
diff --git a/source/blender/python/intern/bpy_rna_manipulator.h b/source/blender/python/intern/bpy_rna_manipulator.h
new file mode 100644
index 00000000000..b6f3a2e651d
--- /dev/null
+++ b/source/blender/python/intern/bpy_rna_manipulator.h
@@ -0,0 +1,32 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * 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.
+ *
+ * Contributor(s): Bastien Montagne
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/python/intern/bpy_rna_manipulator.h
+ * \ingroup pythonintern
+ */
+
+#ifndef __BPY_RNA_MANIPULATOR_H__
+#define __BPY_RNA_MANIPULATOR_H__
+
+int BPY_rna_manipulator_module(PyObject *);
+
+#endif /* __BPY_RNA_MANIPULATOR_H__ */