2.5 PyAPI
authorCampbell Barton <ideasman42@gmail.com>
Mon, 16 Mar 2009 15:54:43 +0000 (15:54 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Mon, 16 Mar 2009 15:54:43 +0000 (15:54 +0000)
Support for subclassing blenders operator, to be registered as a new operator.

Still need to...
* add constants like Operator.FINISHED
* wrap context (with rna?)
* poll() cant work right now because there is no way to access the operatorType that holds the python class.
* ´╗┐Only float, int and bool properties can be added so far.

working example operator.
http://wiki.blender.org/index.php/BlenderDev/Blender2.5/WinterCamp/TechnicalDesign#Operator_Example_Code

source/blender/python/intern/bpy_interface.c
source/blender/python/intern/bpy_operator.c
source/blender/python/intern/bpy_opwrapper.c
source/blender/python/intern/bpy_rna.c
source/blender/python/intern/bpy_rna.h
source/blender/windowmanager/intern/wm_event_system.c

index fc43d7e553771c947102563e1a39c1bba754340e..54f7bb55cc04376e9283d872036139e6ce12fda5 100644 (file)
@@ -57,6 +57,22 @@ static PyObject *CreateGlobalDictionary( bContext *C )
        PyDict_SetItemString( dict, "__bpy_context__", item );
        Py_DECREF(item);
        
+       
+       // XXX - put somewhere more logical
+       {
+               PyMethodDef *ml;
+               static PyMethodDef bpy_prop_meths[] = {
+                       {"FloatProperty", BPy_FloatProperty, METH_VARARGS|METH_KEYWORDS, ""},
+                       {"IntProperty", BPy_IntProperty, METH_VARARGS|METH_KEYWORDS, ""},
+                       {"BoolProperty", BPy_BoolProperty, METH_VARARGS|METH_KEYWORDS, ""},
+                       {NULL, NULL, 0, NULL}
+               };
+               
+               for(ml = &bpy_prop_meths; ml->ml_name; ml++) {
+                       PyDict_SetItemString( dict, ml->ml_name, PyCFunction_New(ml, NULL));
+               }
+       }
+       
        return dict;
 }
 
index 074a6ca499be5c984891535e6ed2c664052e5662..d2dbd6c91bb8a0090a90dcd679b431adeb37b768 100644 (file)
@@ -111,7 +111,7 @@ static PyObject *pyop_base_dir(PyObject *self);
 static struct PyMethodDef pyop_base_methods[] = {
        {"__dir__", (PyCFunction)pyop_base_dir, METH_NOARGS, ""},
        {"add", (PyCFunction)PYOP_wrap_add, METH_VARARGS, ""},
-       {"remove", (PyCFunction)PYOP_wrap_remove, METH_VARARGS, ""},
+       {"remove", (PyCFunction)PYOP_wrap_remove, METH_O, ""},
        {NULL, NULL, 0, NULL}
 };
 
index ca6104d087f5a755b2cbf4263ddd3ca45ad691ef..727dee015316a27d037c33e1b74bab1b5b42156b 100644 (file)
@@ -45,39 +45,9 @@ typedef struct PyOperatorType {
        char idname[OP_MAX_TYPENAME];
        char name[OP_MAX_TYPENAME];
        char description[OP_MAX_TYPENAME]; // XXX should be longer?
-       PyObject *py_invoke;
-       PyObject *py_exec;
+       PyObject *py_class;
 } PyOperatorType;
 
-static PyObject *pyop_kwargs_from_operator(wmOperator *op)
-{
-       PyObject *dict = PyDict_New();
-       PyObject *item;
-       PropertyRNA *prop, *iterprop;
-       CollectionPropertyIterator iter;
-       const char *arg_name;
-
-       iterprop= RNA_struct_iterator_property(op->ptr);
-       RNA_property_collection_begin(op->ptr, iterprop, &iter);
-
-       for(; iter.valid; RNA_property_collection_next(&iter)) {
-               prop= iter.ptr.data;
-
-               arg_name= RNA_property_identifier(&iter.ptr, prop);
-
-               if (strcmp(arg_name, "rna_type")==0) continue;
-
-               item = pyrna_prop_to_py(op->ptr, prop);
-               PyDict_SetItemString(dict, arg_name, item);
-               Py_DECREF(item);
-       }
-
-       RNA_property_collection_end(&iter);
-
-       return dict;
-}
-
-
 static PyObject *pyop_dict_from_event(wmEvent *event)
 {
        PyObject *dict= PyDict_New();
@@ -191,33 +161,6 @@ static struct BPY_flag_def pyop_ret_flags[] = {
        {NULL, 0}
 };
 
-/* exec only - no user input */
-static int PYTHON_OT_exec(bContext *C, wmOperator *op)
-{
-       PyOperatorType *pyot = op->type->pyop_data;
-       PyObject *args= PyTuple_New(0);
-       PyObject *kw= pyop_kwargs_from_operator(op);
-       PyObject *ret;
-       int ret_flag;
-
-       ret = PyObject_Call(pyot->py_exec, args, kw);
-
-       if (ret == NULL) {
-               pyop_error_report(op->reports);
-       }
-       else {
-               if (BPY_flag_from_seq(pyop_ret_flags, ret, &ret_flag) == -1) {
-                        /* the returned value could not be converted into a flag */
-                       pyop_error_report(op->reports);
-               }
-       }
-
-       Py_DECREF(args);
-       Py_DECREF(kw);
-
-       return ret_flag;
-}
-
 /* This invoke function can take events and
  *
  * It is up to the pyot->py_invoke() python func to run pyot->py_exec()
@@ -233,26 +176,93 @@ static int PYTHON_OT_exec(bContext *C, wmOperator *op)
  *     op_exec(**prop_defs)
  *
  * when there is no invoke function, C calls exec and sets the props.
+ * python class instance is stored in op->customdata so exec() can access
  */
-static int PYTHON_OT_invoke(bContext *C, wmOperator *op, wmEvent *event)
-{
-       PyOperatorType *pyot = op->type->pyop_data;
-       PyObject *args= PyTuple_New(2);
-       PyObject *ret;
-       int ret_flag;
 
-       PyTuple_SET_ITEM(args, 0, pyop_dict_from_event(event));
-       PyTuple_SET_ITEM(args, 1, pyop_kwargs_from_operator(op));
 
-       ret = PyObject_Call(pyot->py_invoke, args, NULL);
+#define PYOP_EXEC 1
+#define PYOP_INVOKE 2
+#define PYOP_POLL 3
+       
+static int PYTHON_OT_generic(int mode, bContext *C, wmOperator *op, wmEvent *event)
+{
+       PyOperatorType *pyot = op->type->pyop_data;
+       PyObject *args;
+       PyObject *ret= NULL, *py_class_instance, *item;
+       int ret_flag= (mode==PYOP_POLL ? 0:OPERATOR_CANCELLED);
+       
+       args = PyTuple_New(1);
+       PyTuple_SET_ITEM(args, 0, PyObject_GetAttrString(pyot->py_class, "__rna__")); // need to use an rna instance as the first arg
+       py_class_instance = PyObject_Call(pyot->py_class, args, NULL);
+       Py_DECREF(args);
+       
+       if (py_class_instance) { /* Initializing the class worked, now run its invoke function */
+               
+               
+               /* Assign instance attributes from operator properties */
+               {
+                       PropertyRNA *prop, *iterprop;
+                       CollectionPropertyIterator iter;
+                       const char *arg_name;
+
+                       iterprop= RNA_struct_iterator_property(op->ptr);
+                       RNA_property_collection_begin(op->ptr, iterprop, &iter);
+
+                       for(; iter.valid; RNA_property_collection_next(&iter)) {
+                               prop= iter.ptr.data;
+                               arg_name= RNA_property_identifier(&iter.ptr, prop);
+
+                               if (strcmp(arg_name, "rna_type")==0) continue;
+
+                               item = pyrna_prop_to_py(op->ptr, prop);
+                               PyObject_SetAttrString(py_class_instance, arg_name, item);
+                               Py_DECREF(item);
+                       }
 
+                       RNA_property_collection_end(&iter);
+               }
+               
+               
+               if (mode==PYOP_INVOKE) {
+                       item= PyObject_GetAttrString(pyot->py_class, "invoke");
+                       args = PyTuple_New(2);
+                       PyTuple_SET_ITEM(args, 1, pyop_dict_from_event(event));
+               }
+               else if (mode==PYOP_EXEC) {
+                       item= PyObject_GetAttrString(pyot->py_class, "exec");
+                       args = PyTuple_New(1);
+               }
+               else if (mode==PYOP_POLL) {
+                       item= PyObject_GetAttrString(pyot->py_class, "poll");
+                       args = PyTuple_New(2);
+                       //XXX  Todo - wrap context in a useful way, None for now.
+                       PyTuple_SET_ITEM(args, 1, Py_None);
+               }
+               PyTuple_SET_ITEM(args, 0, py_class_instance);
+       
+               ret = PyObject_Call(item, args, NULL);
+               
+               Py_DECREF(args);
+               Py_DECREF(item);
+       }
+       
        if (ret == NULL) {
                pyop_error_report(op->reports);
        }
        else {
-               if (BPY_flag_from_seq(pyop_ret_flags, ret, &ret_flag) == -1) {
+               if (mode==PYOP_POLL) {
+                       if (PyBool_Check(ret) == 0) {
+                               PyErr_SetString(PyExc_ValueError, "Python poll function return value ");
+                               pyop_error_report(op->reports);
+                       }
+                       else {
+                               ret_flag= ret==Py_True ? 1:0;
+                       }
+                       
+               } else if (BPY_flag_from_seq(pyop_ret_flags, ret, &ret_flag) == -1) {
                         /* the returned value could not be converted into a flag */
                        pyop_error_report(op->reports);
+                       
                }
                /* there is no need to copy the py keyword dict modified by
                 * pyot->py_invoke(), back to the operator props since they are just
@@ -261,131 +271,234 @@ static int PYTHON_OT_invoke(bContext *C, wmOperator *op, wmEvent *event)
                 * If we ever want to do this and use the props again,
                 * it can be done with - PYOP_props_from_dict(op->ptr, kw)
                 */
+               
+               Py_DECREF(ret);
        }
 
-       
+       return ret_flag;
+}
+
+static int PYTHON_OT_invoke(bContext *C, wmOperator *op, wmEvent *event)
+{
+       return PYTHON_OT_generic(PYOP_INVOKE, C, op, event);    
+}
 
-       Py_DECREF(args); /* also decref's kw */
+static int PYTHON_OT_exec(bContext *C, wmOperator *op)
+{
+       return PYTHON_OT_generic(PYOP_EXEC, C, op, NULL);
+}
 
-       return ret_flag;
+static int PYTHON_OT_poll(bContext *C)
+{
+       // XXX TODO - no way to get the operator type (and therefor class) from the poll function.
+       //return PYTHON_OT_generic(PYOP_POLL, C, NULL, NULL);
+       return 1;
 }
 
 void PYTHON_OT_wrapper(wmOperatorType *ot, void *userdata)
 {
        PyOperatorType *pyot = (PyOperatorType *)userdata;
+       PyObject *py_class = pyot->py_class;
 
        /* identifiers */
        ot->name= pyot->name;
        ot->idname= pyot->idname;
        ot->description= pyot->description;
 
-       /* api callbacks */
-       if (pyot->py_invoke != Py_None)
+       /* api callbacks, detailed checks dont on adding */ 
+       if (PyObject_HasAttrString(py_class, "invoke"))
                ot->invoke= PYTHON_OT_invoke;
-       
-       ot->exec= PYTHON_OT_exec;
-
-       ot->poll= ED_operator_screenactive; /* how should this work?? */
-       /* ot->flag= OPTYPE_REGISTER; */
+       if (PyObject_HasAttrString(py_class, "exec"))
+               ot->exec= PYTHON_OT_exec;
+       if (PyObject_HasAttrString(py_class, "poll"))
+               ot->poll= PYTHON_OT_poll;
        
        ot->pyop_data= userdata;
        
-       /* inspect function keyword args to get properties */
-       {
-               PropertyRNA *prop;
-
-               PyObject *var_names= PyObject_GetAttrString(PyFunction_GET_CODE(pyot->py_exec), "co_varnames");
-               PyObject *var_vals = PyFunction_GET_DEFAULTS(pyot->py_exec);
-               PyObject *py_val, *py_name;
+       // TODO - set properties
+       PyObject *props, *item;
+       
+       
+       if ((props=PyObject_GetAttrString(py_class, "properties"))) {           
+               PyObject *dummy_args = PyTuple_New(0);
+               
                int i;
-               char *name;
-
-               if (PyTuple_Size(var_names) != PyTuple_Size(var_vals)) {
-                       printf("All args must be keywords");
-               }
-
-               for(i=0; i<PyTuple_Size(var_names); i++) {
-                       py_name = PyTuple_GetItem(var_names, i);
-                       name = _PyUnicode_AsString(py_name);
-                       py_val = PyTuple_GetItem(var_vals, i);
-
-                       if (PyBool_Check(py_val)) {
-                               prop = RNA_def_property(ot->srna, name, PROP_BOOLEAN, PROP_NONE);
-                               RNA_def_property_boolean_default(prop, PyObject_IsTrue(py_val));
-                       }
-                       else if (PyLong_Check(py_val)) {
-                               prop = RNA_def_property(ot->srna, name, PROP_INT, PROP_NONE);
-                               RNA_def_property_int_default(prop, (int)PyLong_AsSsize_t(py_val));
-                       }
-                       else if (PyFloat_Check(py_val)) {
-                               prop = RNA_def_property(ot->srna, name, PROP_FLOAT, PROP_NONE);
-                               RNA_def_property_float_default(prop, (float)PyFloat_AsDouble(py_val));
-                       }
-                       else if (PyUnicode_Check(py_val)) {
-                               /* WARNING - holding a reference to the string from py_val is
-                                * not ideal since we rely on python keeping it,
-                                * however we're also keeping a reference to this function
-                                * so it should be OK!. just be careful with changes */
-                               prop = RNA_def_property(ot->srna, name, PROP_STRING, PROP_NONE);
-                               RNA_def_property_string_default(prop, _PyUnicode_AsString(py_val));
-                       }
-                       else {
-                               printf("error, python function arg \"%s\" was not a bool, int, float or string type\n", name);
+               
+               for(i=0; i<PyList_Size(props); i++) {
+                       item = PyList_GET_ITEM(props, i);
+                       
+                       PyObject *py_func_ptr, *py_kw, *py_srna_cobject, *py_ret;
+                       
+                       if (PyArg_ParseTuple(item, "O!O!", &PyCObject_Type, &py_func_ptr, &PyDict_Type, &py_kw)) {
+                               
+                               PyObject *(*pyfunc)(PyObject *, PyObject *, PyObject *);
+                               pyfunc = PyCObject_AsVoidPtr(py_func_ptr);
+                               py_srna_cobject = PyCObject_FromVoidPtr(ot->srna, NULL);
+                               
+                               py_ret = pyfunc(py_srna_cobject, dummy_args, py_kw);
+                               if (py_ret) {
+                                       Py_DECREF(py_ret);
+                               } else {
+                                       PyErr_Print();
+                                       PyErr_Clear();
+                               }
+                               Py_DECREF(py_srna_cobject);
+                               
+                       } else {
+                               /* cant return NULL from here */ // XXX a bit ugly
+                               PyErr_Print();
+                               PyErr_Clear();
                        }
+                       
+                       // expect a tuple with a CObject and a dict
                }
+               Py_DECREF(dummy_args);
+       } else {
+               PyErr_Clear();
        }
-
 }
 
+
 /* pyOperators - Operators defined IN Python */
 PyObject *PYOP_wrap_add(PyObject *self, PyObject *args)
 {
+       // XXX ugly - store the Operator type elsewhere!, probably leaks memory
+       PyObject *optype = PyObject_GetAttrString(PyObject_GetAttrString(PyDict_GetItemString(PyEval_GetGlobals(), "bpy"), "types"), "Operator");
+       PyObject *value, *item;
+       
        PyOperatorType *pyot;
-
        char *idname= NULL;
        char *name= NULL;
        char *description= NULL;
-       PyObject *invoke= NULL;
-       PyObject *exec= NULL;
-
-       if (!PyArg_ParseTuple(args, "sssOO", &idname, &name, &description, &invoke, &exec)) {
-               PyErr_SetString( PyExc_AttributeError, "expected 2 strings and 2 function objects");
+       
+       static char *pyop_func_names[] = {"exec", "invoke", "poll", NULL};
+       static int *pyop_func_nargs[] = {1, 2, 2, 0};
+       
+       
+       if (!PyArg_ParseTuple(args, "O", &value) || !PyObject_IsSubclass(value, optype)) {
+               PyErr_SetString( PyExc_AttributeError, "expected Operator subclass of bpy.types.Operator");
                return NULL;
        }
-
+       
+       /* class name is used for operator ID - this can be changed later if we want */
+       item = PyObject_GetAttrString(value, "__name__");
+       idname =  _PyUnicode_AsString(item);
+       Py_DECREF(item);
+       
        if (WM_operatortype_find(idname)) {
-               PyErr_Format( PyExc_AttributeError, "First argument \"%s\" operator alredy exists with this name", idname);
+               PyErr_Format( PyExc_AttributeError, "Operator alredy exists with this name", idname);
                return NULL;
        }
-
-       if (((PyFunction_Check(invoke) || invoke==Py_None) && PyFunction_Check(exec)) == 0) {
-               PyErr_SetString( PyExc_AttributeError, "the 2nd arg must be a function or None, the secons must be a function");
-               return NULL;
+       
+       /* Operator user readible name */
+       item = PyObject_GetAttrString(value, "name");
+       if (item) {
+               name = _PyUnicode_AsString(item);
+               Py_DECREF(item);
        }
-
+       if (name == NULL) {
+               name = idname;
+               PyErr_Clear();
+       }
+       
+       /* use py docstring for description, should always be None or a string */
+       item = PyObject_GetAttrString(value, "__doc__");
+       if (PyUnicode_Check(item)) {
+               description = _PyUnicode_AsString(item);
+       }
+       else {
+               description = "";
+       }
+       Py_DECREF(item);
+       
+       /* Check known functions and argument lengths */
+       int i;
+       int argcount;
+       for (i=0; pyop_func_names[i]; i++) {
+               if (item=PyObject_GetAttrString(value, pyop_func_names[i])) {
+                       /* check its callable */
+                       if (!PyFunction_Check(item)) {
+                               PyErr_Format(PyExc_ValueError, "Cant register operator class -  %s.%s() is not a function", idname, pyop_func_names[i]);
+                               Py_DECREF(item);
+                               return NULL;
+                       }
+                       /* check the number of args is correct */
+                       // MyClass.exec.func_code.co_argcount
+                       
+                       PyObject *pyargcount = PyObject_GetAttrString(PyFunction_GetCode(item), "co_argcount");
+                       argcount = PyLong_AsSsize_t(pyargcount);
+                       Py_DECREF(pyargcount);
+                       
+                       if (argcount != pyop_func_nargs[i]) {
+                               PyErr_Format(PyExc_ValueError, "Cant register operator class - %s.%s() takes %d args, should be %d", idname, pyop_func_names[i], argcount, pyop_func_nargs[i]);
+                               Py_DECREF(item);
+                               return NULL;
+                       }
+                       
+               } else {
+                       PyErr_Clear();
+               }
+               Py_XDECREF(item);
+       }
+       
+       /* If we have properties set, check its a list of dicts */
+       item = PyObject_GetAttrString(value, "properties");
+       if (item) {
+               if (!PyList_Check(item)) {
+                       PyErr_Format(PyExc_ValueError, "Cant register operator class - %s.properties must be a list", idname);
+                       Py_DECREF(item);
+                       return NULL;
+               }
+               
+               int i;
+               for(i=0; i<PyList_Size(item); i++) {
+                       PyObject *py_args = PyList_GET_ITEM(item, i);
+                       PyObject *py_func_ptr, *py_kw; /* place holders */
+                       
+                       if (!PyArg_ParseTuple(py_args, "O!O!", &PyCObject_Type, &py_func_ptr, &PyDict_Type, &py_kw)) {
+                               PyErr_Format(PyExc_ValueError, "Cant register operator class - %s.properties must contain values from FloatProperty", idname);
+                               Py_DECREF(item);
+                               return NULL;                            
+                       }
+               }
+               
+               Py_DECREF(item);
+       }
+       else {
+               PyErr_Clear();
+       }
+       
        pyot= MEM_callocN(sizeof(PyOperatorType), "PyOperatorType");
 
        strncpy(pyot->idname, idname, sizeof(pyot->idname));
        strncpy(pyot->name, name, sizeof(pyot->name));
        strncpy(pyot->description, description, sizeof(pyot->description));
-       pyot->py_invoke= invoke;
-       pyot->py_exec= exec;
-       Py_INCREF(invoke);
-       Py_INCREF(exec);
+       pyot->py_class= value;
+       Py_INCREF(value);
 
        WM_operatortype_append_ptr(PYTHON_OT_wrapper, pyot);
 
        Py_RETURN_NONE;
 }
 
-PyObject *PYOP_wrap_remove(PyObject *self, PyObject *args)
+PyObject *PYOP_wrap_remove(PyObject *self, PyObject *value)
 {
        char *idname= NULL;
        wmOperatorType *ot;
        PyOperatorType *pyot;
 
-       if (!PyArg_ParseTuple(args, "s", &idname))
+       if (PyUnicode_Check(value))
+               idname = _PyUnicode_AsString(value);
+       else if (PyCFunction_Check(value)) {
+               PyObject *cfunc_self = PyCFunction_GetSelf(value);
+               if (cfunc_self)
+                       idname = _PyUnicode_AsString(cfunc_self);
+       }
+       
+       if (idname==NULL) {
+               PyErr_SetString( PyExc_ValueError, "Expected the operator name as a string or the operator function");
                return NULL;
+       }
 
        if (!(ot= WM_operatortype_find(idname))) {
                PyErr_Format( PyExc_AttributeError, "Operator \"%s\" does not exists, cant remove", idname);
@@ -397,8 +510,7 @@ PyObject *PYOP_wrap_remove(PyObject *self, PyObject *args)
                return NULL;
        }
        
-       Py_XDECREF(pyot->py_invoke);
-       Py_XDECREF(pyot->py_exec);
+       Py_XDECREF(pyot->py_class);
        MEM_freeN(pyot);
 
        WM_operatortype_remove(idname);
@@ -406,3 +518,5 @@ PyObject *PYOP_wrap_remove(PyObject *self, PyObject *args)
        Py_RETURN_NONE;
 }
 
+
+
index c9cd44141273a0dd1934ef64a4b9e229f8e90fd3..193f2a8d3d28eaf3d37aa3920ea6d6a8fb367edb 100644 (file)
@@ -26,6 +26,9 @@
 #include "bpy_compat.h"
 //#include "blendef.h"
 #include "BLI_dynstr.h"
+#include "float.h" /* FLT_MIN/MAX */
+
+#include "RNA_define.h" /* for defining our own rna */
 
 #include "MEM_guardedalloc.h"
 #include "BKE_global.h" /* evil G.* */
@@ -65,12 +68,16 @@ static PyObject *pyrna_prop_richcmp(BPy_PropertyRNA * a, BPy_PropertyRNA * b, in
 /*----------------------repr--------------------------------------------*/
 static PyObject *pyrna_struct_repr( BPy_StructRNA * self )
 {
-       return PyUnicode_FromFormat( "[BPy_StructRNA \"%s\"]", RNA_struct_identifier(&self->ptr));
+       char str[512];
+       RNA_string_get(&self->ptr, "identifier", str);
+       return PyUnicode_FromFormat( "[BPy_StructRNA \"%s\" -> \"%s\"]", RNA_struct_identifier(&self->ptr), str);
 }
 
 static PyObject *pyrna_prop_repr( BPy_PropertyRNA * self )
 {
-       return PyUnicode_FromFormat( "[BPy_PropertyRNA \"%s\" -> \"%s\" ]", RNA_struct_identifier(&self->ptr), RNA_property_identifier(&self->ptr, self->prop) );
+       char str[512];
+       RNA_string_get(&self->ptr, "identifier", str);
+       return PyUnicode_FromFormat( "[BPy_PropertyRNA \"%s\" -> \"%s\" -> \"%s\" ]", RNA_struct_identifier(&self->ptr), str, RNA_property_identifier(&self->ptr, self->prop) );
 }
 
 static long pyrna_struct_hash( BPy_StructRNA * self )
@@ -88,7 +95,7 @@ static void pyrna_struct_dealloc( BPy_StructRNA * self )
                self->ptr.data= NULL;
        }
 
-       ((PyObject *)self)->ob_type->tp_free(self);
+       Py_TYPE(self)->tp_free(self);
        return;
 }
 
@@ -516,7 +523,7 @@ static int pyrna_prop_assign_subscript( BPy_PropertyRNA * self, PyObject *key, P
        char *keyname = NULL;
        
        if (!RNA_property_editable(&self->ptr, self->prop)) {
-               PyErr_Format( PyExc_AttributeError, "Attribute \"%s\" from \"%s\" is read-only", RNA_property_identifier(&self->ptr, self->prop), RNA_struct_identifier(&self->ptr) );
+               PyErr_Format( PyExc_AttributeError, "PropertyRNA - attribute \"%s\" from \"%s\" is read-only", RNA_property_identifier(&self->ptr, self->prop), RNA_struct_identifier(&self->ptr) );
                return -1;
        }
        
@@ -525,26 +532,26 @@ static int pyrna_prop_assign_subscript( BPy_PropertyRNA * self, PyObject *key, P
        } else if (PyLong_Check(key)) {
                keynum = PyLong_AsSsize_t(key);
        } else {
-               PyErr_SetString(PyExc_AttributeError, "invalid key, key must be a string or an int");
+               PyErr_SetString(PyExc_AttributeError, "PropertyRNA - invalid key, key must be a string or an int");
                return -1;
        }
        
        if (RNA_property_type(&self->ptr, self->prop) == PROP_COLLECTION) {
-               PyErr_SetString(PyExc_AttributeError, "assignment is not supported for collections (yet)");
+               PyErr_SetString(PyExc_AttributeError, "PropertyRNA - assignment is not supported for collections (yet)");
                ret = -1;
        } else if (keyname) {
-               PyErr_SetString(PyExc_AttributeError, "string keys are only supported for collections");
+               PyErr_SetString(PyExc_AttributeError, "PropertyRNA - string keys are only supported for collections");
                ret = -1;
        } else {
                int len = RNA_property_array_length(&self->ptr, self->prop);
                
                if (len==0) { /* not an array*/
-                       PyErr_Format(PyExc_AttributeError, "not an array or collection %d", keynum);
+                       PyErr_Format(PyExc_AttributeError, "PropertyRNA - not an array or collection %d", keynum);
                        ret = -1;
                }
                
                if (keynum >= len){
-                       PyErr_SetString(PyExc_AttributeError, "index out of range");
+                       PyErr_SetString(PyExc_AttributeError, "PropertyRNA - index out of range");
                        ret = -1;
                } else {
                        ret = pyrna_py_to_prop_index(&self->ptr, self->prop, keynum, value);
@@ -621,9 +628,11 @@ static PyObject *pyrna_struct_getattro( BPy_StructRNA * self, PyObject *pyname )
        /* Include this incase this instance is a subtype of a python class
         * In these instances we may want to return a function or variable provided by the subtype
         * */
-       ret = PyObject_GenericGetAttr((PyObject *)self, pyname);
-       if (ret)        return ret;
-       else            PyErr_Clear();
+       if (BPy_StructRNA_CheckExact(self) == 0) {
+               ret = PyObject_GenericGetAttr((PyObject *)self, pyname);
+               if (ret)        return ret;
+               else            PyErr_Clear();
+       }
        /* done with subtypes */
 
        prop = RNA_struct_find_property(&self->ptr, name);
@@ -632,7 +641,7 @@ static PyObject *pyrna_struct_getattro( BPy_StructRNA * self, PyObject *pyname )
                ret = pyrna_prop_to_py(&self->ptr, prop);
        }
        else {
-               PyErr_Format( PyExc_AttributeError, "Attribute \"%s\" not found", name);
+               PyErr_Format( PyExc_AttributeError, "StructRNA - Attribute \"%s\" not found", name);
                ret = NULL;
        }
        
@@ -646,12 +655,17 @@ static int pyrna_struct_setattro( BPy_StructRNA * self, PyObject *pyname, PyObje
        PropertyRNA *prop = RNA_struct_find_property(&self->ptr, name);
        
        if (prop==NULL) {
-               PyErr_Format( PyExc_AttributeError, "Attribute \"%s\" not found", name);
-               return -1;
+               if (!BPy_StructRNA_CheckExact(self) && PyObject_GenericSetAttr((PyObject *)self, pyname, value) >= 0) {
+                       return 0;
+               }
+               else {
+                       PyErr_Format( PyExc_AttributeError, "StructRNA - Attribute \"%s\" not found", name);
+                       return -1;
+               }
        }               
        
        if (!RNA_property_editable(&self->ptr, prop)) {
-               PyErr_Format( PyExc_AttributeError, "Attribute \"%s\" from \"%s\" is read-only", RNA_property_identifier(&self->ptr, prop), RNA_struct_identifier(&self->ptr) );
+               PyErr_Format( PyExc_AttributeError, "StructRNA - Attribute \"%s\" from \"%s\" is read-only", RNA_property_identifier(&self->ptr, prop), RNA_struct_identifier(&self->ptr) );
                return -1;
        }
                
@@ -1200,3 +1214,89 @@ PyObject *BPY_rna_doc( void )
        
        return mod;
 }
+
+
+/* Orphan functions, not sure where they should go */
+
+/* Function that sets RNA, NOTE - self is NULL when called from python, but being abused from C so we can pass the srna allong
+ * This isnt incorrect since its a python object - but be careful */
+PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw)
+{
+       static char *kwlist[] = {"attribute", "name", "description", "min", "max", "soft_min", "soft_max", "default", NULL};
+       char *id, *name="", *description="";
+       float min=FLT_MIN, max=FLT_MAX, soft_min=FLT_MIN, soft_max=FLT_MAX, def=0.0f;
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kw, "s|ssfffff:FloatProperty", kwlist, &id, &name, &description, &min, &max, &soft_min, &soft_max, &def))
+               return NULL;
+       
+       if (PyTuple_Size(args) > 0) {
+               PyErr_SetString(PyExc_ValueError, "all args must be keywors"); // TODO - py3 can enforce this.
+               return NULL;
+       }
+       
+       if (self) {
+               StructRNA *srna = PyCObject_AsVoidPtr(self);
+               RNA_def_float(srna, id, def, min, max, name, description, soft_min, soft_max);
+               Py_RETURN_NONE;
+       } else {
+               PyObject *ret = PyTuple_New(2);
+               PyTuple_SET_ITEM(ret, 0, PyCObject_FromVoidPtr((void *)BPy_FloatProperty, NULL));
+               PyTuple_SET_ITEM(ret, 1, kw);
+               Py_INCREF(kw);
+               return ret;
+       }
+}
+
+PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw)
+{
+       static char *kwlist[] = {"attribute", "name", "description", "min", "max", "soft_min", "soft_max", "default", NULL};
+       char *id, *name="", *description="";
+       int min=INT_MIN, max=INT_MAX, soft_min=INT_MIN, soft_max=INT_MAX, def=0;
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kw, "s|ssiiiii:IntProperty", kwlist, &id, &name, &description, &min, &max, &soft_min, &soft_max, &def))
+               return NULL;
+       
+       if (PyTuple_Size(args) > 0) {
+               PyErr_SetString(PyExc_ValueError, "all args must be keywors"); // TODO - py3 can enforce this.
+               return NULL;
+       }
+       
+       if (self) {
+               StructRNA *srna = PyCObject_AsVoidPtr(self);
+               RNA_def_int(srna, id, def, min, max, name, description, soft_min, soft_max);
+               Py_RETURN_NONE;
+       } else {
+               PyObject *ret = PyTuple_New(2);
+               PyTuple_SET_ITEM(ret, 0, PyCObject_FromVoidPtr((void *)BPy_IntProperty, NULL));
+               PyTuple_SET_ITEM(ret, 1, kw);
+               Py_INCREF(kw);
+               return ret;
+       }
+}
+
+PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw)
+{
+       static char *kwlist[] = {"attribute", "name", "description", "default", NULL};
+       char *id, *name="", *description="";
+       int def=0;
+       
+       if (!PyArg_ParseTupleAndKeywords(args, kw, "s|ssi:IntProperty", kwlist, &id, &name, &description, &def))
+               return NULL;
+       
+       if (PyTuple_Size(args) > 0) {
+               PyErr_SetString(PyExc_ValueError, "all args must be keywors"); // TODO - py3 can enforce this.
+               return NULL;
+       }
+       
+       if (self) {
+               StructRNA *srna = PyCObject_AsVoidPtr(self);
+               RNA_def_boolean(srna, id, def, name, description);
+               Py_RETURN_NONE;
+       } else {
+               PyObject *ret = PyTuple_New(2);
+               PyTuple_SET_ITEM(ret, 0, PyCObject_FromVoidPtr((void *)BPy_IntProperty, NULL));
+               PyTuple_SET_ITEM(ret, 1, kw);
+               Py_INCREF(kw);
+               return ret;
+       }
+}
index 1d49d4d5485d1f88822508087489cd20cf643367..904529b58de05c45756957952cd04418a9d8ab06 100644 (file)
 extern PyTypeObject pyrna_struct_Type;
 extern PyTypeObject pyrna_prop_Type;
 
-#define BPy_StructRNA_Check(v)         (PyObject_TypeCheck(v, &pyrna_struct_Type))
-#define BPy_PropertyRNA_Check(v)       (PyObject_TypeCheck(v, &pyrna_prop_Type))
+#define BPy_StructRNA_Check(v)                 (PyObject_TypeCheck(v, &pyrna_struct_Type))
+#define BPy_StructRNA_CheckExact(v)            (Py_TYPE(v) == &pyrna_struct_Type)
+#define BPy_PropertyRNA_Check(v)               (PyObject_TypeCheck(v, &pyrna_prop_Type))
+#define BPy_PropertyRNA_CheckExact(v)  (Py_TYPE(v) == &pyrna_prop_Type)
 
  //XXX add propper accessor function, we know this is just after next/prev pointers
  
@@ -69,4 +71,11 @@ PyObject *pyrna_prop_CreatePyObject( PointerRNA *ptr, PropertyRNA *prop );
 /* operators also need this to set args */
 int pyrna_py_to_prop(PointerRNA *ptr, PropertyRNA *prop, PyObject *value);
 PyObject * pyrna_prop_to_py(PointerRNA *ptr, PropertyRNA *prop);
+
+/* functions for setting up new props - experemental */
+PyObject *BPy_FloatProperty(PyObject *self, PyObject *args, PyObject *kw);
+PyObject *BPy_IntProperty(PyObject *self, PyObject *args, PyObject *kw);
+PyObject *BPy_BoolProperty(PyObject *self, PyObject *args, PyObject *kw);
+
+
 #endif
index 2b08f5a894376e170a2e75a33653a84b78cba79a..772b308fb6274b08076247b0c8036f8056e5a946 100644 (file)
@@ -469,7 +469,12 @@ int WM_operator_call_py(bContext *C, wmOperatorType *ot, PointerRNA *properties,
 {
        wmWindowManager *wm=    CTX_wm_manager(C);
        wmOperator *op=                 wm_operator_create(wm, ot, properties, reports);
-       int retval=                             op->type->exec(C, op);
+       int retval= OPERATOR_CANCELLED;
+       
+       if (op->type->exec)
+               retval= op->type->exec(C, op);
+       else
+               printf("error \"%s\" operator has no exec function, python cannot call it\n", op->type->name);
        
        if (reports)
                op->reports= NULL; /* dont let the operator free reports passed to this function */