diff options
author | Campbell Barton <ideasman42@gmail.com> | 2017-06-26 08:57:14 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2017-06-26 09:38:04 +0300 |
commit | 28b2f1c30504ce0e437b21f7964282fcd6894421 (patch) | |
tree | 8653eeeb0edb90cf41a93bc2b196ac55739f2a32 /source/blender/python | |
parent | c9e33b36de25d272a9896ad0a85dcc69ee0692ba (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')
-rw-r--r-- | source/blender/python/intern/CMakeLists.txt | 4 | ||||
-rw-r--r-- | source/blender/python/intern/bpy.c | 3 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_intern_string.c | 4 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_intern_string.h | 1 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_manipulator_wrap.c | 231 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_manipulator_wrap.h | 36 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna.c | 34 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_manipulator.c | 341 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna_manipulator.h | 32 |
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, + ¶ms.id, + ¶ms.type_id, + ¶ms.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, ¶ms.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, + ¶ms.self, + ¶ms.target, + ¶ms.py_fn_slots[BPY_MANIPULATOR_FN_SLOT_GET], + ¶ms.py_fn_slots[BPY_MANIPULATOR_FN_SLOT_SET], + ¶ms.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__ */ |