bpy/rna api class feature
authorCampbell Barton <ideasman42@gmail.com>
Sun, 8 Nov 2009 01:13:19 +0000 (01:13 +0000)
committerCampbell Barton <ideasman42@gmail.com>
Sun, 8 Nov 2009 01:13:19 +0000 (01:13 +0000)
- python defined classes will be used when available (otherwise automaically generated metaclasses are made as before)
- use properties rather then functions for python defined rna class's
- call the classes getattr AFTER doing an RNA lookup, avoids setting and clearing exceptions for most attribute lookups, tested UI scripts are ~25% faster.
- extending rna py classes this way is a nicer alternative to modifying the generated metaclasses in place.

Example class

--- snip
class Object(bpy.types.ID):

    def _get_children(self):
        return [child for child in bpy.data.objects if child.parent == self]

    children = property(_get_children)
--- snip

The C initialization function looks in bpy_types.py for classes matching RNA structure names, using them when available.
This means all objects in python will be instances of these classes.
Python properties/funcs defined in ID py class will also be available for subclasses for eg. (Group Mesh etc)

release/scripts/modules/bpy_ext/Mesh.py
release/scripts/modules/bpy_ext/Object.py
release/scripts/modules/bpy_ext/__init__.py
release/scripts/modules/bpy_types.py [new file with mode: 0644]
release/scripts/op/mesh.py
release/scripts/op/mesh_skin.py
source/blender/python/intern/bpy_interface.c
source/blender/python/intern/bpy_rna.c
source/blender/python/intern/bpy_rna.h

index c8abb15ae69efcbfa1693c1478fc1e3f0765db4d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,72 +0,0 @@
-# ##### 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-def ord_ind(i1,i2):
-    if i1<i2: return i1,i2
-    return i2,i1
-
-def edge_key(ed):
-    v1, v2 = tuple(ed.verts)
-    return ord_ind(v1, v2)
-
-def face_edge_keys(face):
-    verts = tuple(face.verts)
-    if len(verts)==3:
-        return ord_ind(verts[0], verts[1]),  ord_ind(verts[1], verts[2]),  ord_ind(verts[2], verts[0])
-
-    return ord_ind(verts[0], verts[1]),  ord_ind(verts[1], verts[2]),  ord_ind(verts[2], verts[3]),  ord_ind(verts[3], verts[0])
-
-def mesh_edge_keys(mesh):
-    return [edge_key for face in mesh.faces for edge_key in face.edge_keys()]
-
-def mesh_edge_face_count_dict(mesh, face_edge_keys=None):
-
-    # Optional speedup
-    if face_edge_keys==None:
-        face_edge_keys = [face.edge_keys() for face in face_list]
-
-    face_edge_count = {}
-    for face_keys in face_edge_keys:
-        for key in face_keys:
-            try:
-                face_edge_count[key] += 1
-            except:
-                face_edge_count[key] = 1
-
-
-    return face_edge_count
-
-def mesh_edge_face_count(mesh, face_edge_keys=None):
-    edge_face_count_dict = mesh.edge_face_count_dict(face_edge_keys)
-    return [edge_face_count_dict.get(ed.key(), 0) for ed in mesh.edges]
-
-import bpy
-
-# * Edge *
-class_obj = bpy.types.MeshEdge
-class_obj.key = edge_key
-
-# * Face *
-class_obj = bpy.types.MeshFace
-class_obj.edge_keys = face_edge_keys
-
-# * Mesh *
-class_obj = bpy.types.Mesh
-class_obj.edge_keys = mesh_edge_keys
-class_obj.edge_face_count = mesh_edge_face_count
-class_obj.edge_face_count_dict = mesh_edge_face_count_dict
index 1febb50cee83a0ce6ee10b45fc0a1e325bcff197..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,22 +0,0 @@
-# ##### 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-import bpy
-class_obj = bpy.types.Object
-
-class_obj.getChildren = lambda self: [child for child in bpy.data.objects if child.parent == self]
index 949a282c545b6bdb48d0fcdf610009f3928d8827..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -1,20 +0,0 @@
-# ##### 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
-#
-# ##### END GPL LICENSE BLOCK #####
-
-import bpy_ext.Object
-import bpy_ext.Mesh
diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py
new file mode 100644 (file)
index 0000000..d2bed9f
--- /dev/null
@@ -0,0 +1,82 @@
+# ##### 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+
+StructRNA = bpy.types.Struct.__bases__[0]
+# StructRNA = bpy.types.Struct
+
+
+class Object(bpy.types.ID):
+
+    def _get_children(self):
+        return [child for child in bpy.data.objects if child.parent == self]
+
+    children = property(_get_children)
+
+
+def ord_ind(i1,i2):
+    if i1<i2: return i1,i2
+    return i2,i1
+
+class Mesh(bpy.types.ID):
+    
+    def _get_edge_keys(self):
+        return [edge_key for face in self.faces for edge_key in face.edge_keys]
+
+    edge_keys = property(_get_edge_keys)
+
+    def _get_edge_face_count_dict(self):
+        face_edge_keys = [face.edge_keys for face in self.faces]
+        face_edge_count = {}
+        for face_keys in face_edge_keys:
+            for key in face_keys:
+                try:
+                    face_edge_count[key] += 1
+                except:
+                    face_edge_count[key] = 1
+
+        return face_edge_count
+
+    edge_face_count_dict = property(_get_edge_face_count_dict)
+
+    def _get_edge_face_count(self):
+        edge_face_count_dict = self.edge_face_count_dict
+        return [edge_face_count_dict.get(ed.key, 0) for ed in mesh.edges]
+
+    edge_face_count = property(_get_edge_face_count)
+
+
+class MeshEdge(StructRNA):
+
+    def _get_key(self):
+        return ord_ind(*tuple(self.verts))
+
+    key = property(_get_key)
+
+
+class MeshFace(StructRNA):
+
+    def _get_edge_keys(self):
+        verts = tuple(self.verts)
+        if len(verts)==3:
+            return ord_ind(verts[0], verts[1]),  ord_ind(verts[1], verts[2]),  ord_ind(verts[2], verts[0])
+
+        return ord_ind(verts[0], verts[1]),  ord_ind(verts[1], verts[2]),  ord_ind(verts[2], verts[3]),  ord_ind(verts[3], verts[0])
+
+    edge_keys = property(_get_edge_keys)
index 8ea9ea030625ae52325a97ca581ee6f1240ad285..f78e33730f0907191f9c9c28ffb7f85f08f0ee3e 100644 (file)
@@ -28,9 +28,9 @@ def main(context):
     mesh = ob.data
     
     face_list = [face for face in mesh.faces]
-    face_edge_keys = [face.edge_keys() for face in face_list]
+    face_edge_keys = [face.edge_keys for face in face_list]
 
-    edge_face_count = mesh.edge_face_count_dict(face_edge_keys)
+    edge_face_count = mesh.edge_face_count_dict
     
     def test_interior(index):
         for key in face_edge_keys[index]:
index 85df6463eb0a78823480ea253ec86802a4857238..f9a92c8cd82225d26a04c531e8d1f918dc85b75a 100644 (file)
@@ -256,11 +256,11 @@ def getSelectedEdges(context, me, ob):
        if MESH_MODE == 'FACE':
                context.scene.tool_settings.mesh_selection_mode = 'EDGE'
                # value is [edge, face_sel_user_in]
-               edge_dict=  dict((ed.key(), [ed, 0]) for ed in me.edges)
+               edge_dict=  dict((ed.key, [ed, 0]) for ed in me.edges)
                
                for f in me.faces:
                        if f.selected:
-                               for edkey in f.edge_keys():
+                               for edkey in f.edge_keys:
                                        edge_dict[edkey][1] += 1
                
                context.scene.tool_settings.mesh_selection_mode = MESH_MODE
@@ -280,7 +280,7 @@ def getVertLoops(selEdges, me):
        vert_used = [False] * tot
        
        for ed in selEdges:
-               i1, i2 = ed.key()
+               i1, i2 = ed.key
                vert_siblings[i1].append(i2)
                vert_siblings[i2].append(i1)
        
index e67df5db096d4ca49eb8c6e29d52f01d56367f38..4a55c2fb6fba060c26cea2fff2d3fd638272679f 100644 (file)
@@ -176,35 +176,35 @@ static void bpy_init_modules( void )
 {
        PyObject *mod;
        
-       mod = PyModule_New("bpy");
+       /* Needs to be first since this dir is needed for future modules */
+       char *modpath= BLI_gethome_folder("scripts/modules", BLI_GETHOME_ALL);
+       if(modpath) {
+               PyObject *sys_path= PySys_GetObject("path"); /* borrow */
+               PyObject *py_modpath= PyUnicode_FromString(modpath);
+               PyList_Insert(sys_path, 0, py_modpath); /* add first */
+               Py_DECREF(py_modpath);
+       }
        
-       PyModule_AddObject( mod, "data", BPY_rna_module() );
+       mod = PyModule_New("bpy");
+
+       /* add the module so we can import it */
+       PyDict_SetItemString(PySys_GetObject("modules"), "bpy", mod);
+       Py_DECREF(mod);
+
+       /* run first, initializes rna types */
+       BPY_rna_init();
+
+       PyModule_AddObject( mod, "types", BPY_rna_types() ); /* needs to be first so bpy_types can run */
+       bpy_import_test("bpy_types");
+       PyModule_AddObject( mod, "data", BPY_rna_module() ); /* imports bpy_types by running this */
+       bpy_import_test("bpy_types");
        /* PyModule_AddObject( mod, "doc", BPY_rna_doc() ); */
-       PyModule_AddObject( mod, "types", BPY_rna_types() );
        PyModule_AddObject( mod, "props", BPY_rna_props() );
        PyModule_AddObject( mod, "__ops__", BPY_operator_module() ); /* ops is now a python module that does the conversion from SOME_OT_foo -> some.foo */
        PyModule_AddObject( mod, "ui", BPY_ui_module() ); // XXX very experimental, consider this a test, especially PyCObject is not meant to be permanent
-       
-       /* add the module so we can import it */
-       PyDict_SetItemString(PySys_GetObject("modules"), "bpy", mod);
-       Py_DECREF(mod);
 
-       /* add our own modules dir */
-       {
-               char *modpath= BLI_gethome_folder("scripts/modules", BLI_GETHOME_ALL);
-               
-               if(modpath) {
-                       PyObject *sys_path= PySys_GetObject("path"); /* borrow */
-                       PyObject *py_modpath= PyUnicode_FromString(modpath);
-                       PyList_Insert(sys_path, 0, py_modpath); /* add first */
-                       Py_DECREF(py_modpath);
-               }
-               
-               bpy_import_test("bpy_ops"); /* adds its self to bpy.ops */
-               bpy_import_test("bpy_utils"); /* adds its self to bpy.sys */
-               bpy_import_test("bpy_ext"); /* extensions to our existing types */
-       }
-       
+
+
        /* bpy context */
        {
                bpy_context_module= ( BPy_StructRNA * ) PyObject_NEW( BPy_StructRNA, &pyrna_struct_Type );
@@ -213,11 +213,16 @@ static void bpy_init_modules( void )
                PyModule_AddObject(mod, "context", (PyObject *)bpy_context_module);
        }
 
-
        /* stand alone utility modules not related to blender directly */
        Geometry_Init();
        Mathutils_Init();
        BGL_Init();
+
+       /* add our own modules dir */
+       {
+               bpy_import_test("bpy_ops"); /* adds its self to bpy.ops */
+               bpy_import_test("bpy_utils"); /* adds its self to bpy.sys */
+       }
 }
 
 void BPY_update_modules( void )
index 3e31e24a8e0999618544bea5a0f552c755285e97..15c1f378cb1769b1dcad0678df7bc1e9f88fd7e0 100644 (file)
@@ -1364,16 +1364,6 @@ static PyObject *pyrna_struct_getattro( BPy_StructRNA * self, PyObject *pyname )
        PropertyRNA *prop;
        FunctionRNA *func;
        
-       /* 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
-        * 
-        * Also needed to return methods when its not a subtype
-        * */
-       ret = PyObject_GenericGetAttr((PyObject *)self, pyname);
-       if (ret)        return ret;
-       else            PyErr_Clear();
-       /* done with subtypes */
-       
        if ((prop = RNA_struct_find_property(&self->ptr, name))) {
                ret = pyrna_prop_to_py(&self->ptr, prop);
        }
@@ -1419,8 +1409,18 @@ static PyObject *pyrna_struct_getattro( BPy_StructRNA * self, PyObject *pyname )
                }
        }
        else {
+#if 0
                PyErr_Format( PyExc_AttributeError, "StructRNA - Attribute \"%.200s\" not found", name);
                ret = NULL;
+#endif
+               /* 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
+                *
+                * Also needed to return methods when its not a subtype
+                * */
+
+               /* The error raised here will be displayed */
+               ret = PyObject_GenericGetAttr((PyObject *)self, pyname);
        }
        
        return ret;
@@ -2551,6 +2551,7 @@ static PyObject* pyrna_srna_PyBase(StructRNA *srna) //, PyObject *bpy_types_dict
 
        /* get the base type */
        base= RNA_struct_base(srna);
+
        if(base && base != srna) {
                /*/printf("debug subtype %s %p\n", RNA_struct_identifier(srna), srna); */
                py_base= pyrna_srna_Subtype(base); //, bpy_types_dict);
@@ -2564,6 +2565,56 @@ static PyObject* pyrna_srna_PyBase(StructRNA *srna) //, PyObject *bpy_types_dict
        return py_base;
 }
 
+/* check if we have a native python subclass, use it when it exists
+ * return a borrowed reference */
+static PyObject* pyrna_srna_ExternalType(StructRNA *srna)
+{
+       PyObject *bpy_types_dict= NULL;
+       const char *idname= RNA_struct_identifier(srna);
+       PyObject *newclass;
+
+       if(bpy_types_dict==NULL) {
+               PyObject *bpy_types= PyImport_ImportModuleLevel("bpy_types", NULL, NULL, NULL, 0);
+
+               if(bpy_types==NULL) {
+                       PyErr_Print();
+                       PyErr_Clear();
+                       fprintf(stderr, "pyrna_srna_ExternalType: failed to find 'bpy_types' module\n");
+                       return NULL;
+               }
+
+               bpy_types_dict = PyModule_GetDict(bpy_types); // borrow
+               Py_DECREF(bpy_types); // fairly safe to assume the dict is kept
+       }
+
+       newclass= PyDict_GetItemString(bpy_types_dict, idname);
+
+       /* sanity check, could skip this unless in debug mode */
+       if(newclass) {
+               PyObject *base_compare= pyrna_srna_PyBase(srna);
+               PyObject *bases= PyObject_GetAttrString(newclass, "__bases__");
+
+               if(PyTuple_GET_SIZE(bases)) {
+                       PyObject *base= PyTuple_GET_ITEM(bases, 0);
+
+                       if(base_compare != base) {
+                               PyLineSpit();
+                               fprintf(stderr, "pyrna_srna_ExternalType: incorrect subclassing of SRNA '%s'\n", idname);
+                               PyObSpit("Expected! ", base_compare);
+                               newclass= NULL;
+                       }
+                       else {
+                               if(G.f & G_DEBUG)
+                                       fprintf(stderr, "SRNA Subclassed: '%s'\n", idname);
+                       }
+               }
+
+               Py_DECREF(bases);
+       }
+
+       return newclass;
+}
+
 static PyObject* pyrna_srna_Subtype(StructRNA *srna)
 {
        PyObject *newclass = NULL;
@@ -2572,6 +2623,9 @@ static PyObject* pyrna_srna_Subtype(StructRNA *srna)
                newclass= NULL; /* Nothing to do */
        } else if ((newclass= RNA_struct_py_type_get(srna))) {
                Py_INCREF(newclass);
+       } else if ((newclass= pyrna_srna_ExternalType(srna))) {
+               pyrna_subtype_set_rna(newclass, srna);
+               Py_INCREF(newclass);
        } else {
                /* subclass equivelents
                - class myClass(myBase):
@@ -2681,23 +2735,26 @@ PyObject *pyrna_prop_CreatePyObject( PointerRNA *ptr, PropertyRNA *prop )
        return ( PyObject * ) pyrna;
 }
 
-/* bpy.data from python */
-static PointerRNA *rna_module_ptr= NULL;
-PyObject *BPY_rna_module( void )
+void BPY_rna_init(void)
 {
-       BPy_StructRNA *pyrna;
-       PointerRNA ptr;
-       
 #ifdef USE_MATHUTILS // register mathutils callbacks, ok to run more then once.
        mathutils_rna_array_cb_index= Mathutils_RegisterCallback(&mathutils_rna_array_cb);
        mathutils_rna_matrix_cb_index= Mathutils_RegisterCallback(&mathutils_rna_matrix_cb);
 #endif
-       
+
        if( PyType_Ready( &pyrna_struct_Type ) < 0 )
-               return NULL;
-       
+               return;
+
        if( PyType_Ready( &pyrna_prop_Type ) < 0 )
-               return NULL;
+               return;
+}
+
+/* bpy.data from python */
+static PointerRNA *rna_module_ptr= NULL;
+PyObject *BPY_rna_module(void)
+{
+       BPy_StructRNA *pyrna;
+       PointerRNA ptr;
 
        /* for now, return the base RNA type rather then a real module */
        RNA_main_pointer_create(G.main, &ptr);
@@ -2735,21 +2792,22 @@ static PyObject *pyrna_basetype_getattro( BPy_BaseTypeRNA * self, PyObject *pyna
        PointerRNA newptr;
        PyObject *ret;
        
-       ret = PyObject_GenericGetAttr((PyObject *)self, pyname);
-       if (ret)        return ret;
-       else            PyErr_Clear();
-       
        if (RNA_property_collection_lookup_string(&self->ptr, self->prop, _PyUnicode_AsString(pyname), &newptr)) {
                ret= pyrna_struct_Subtype(&newptr);
                if (ret==NULL) {
                        PyErr_Format(PyExc_SystemError, "bpy.types.%.200s subtype could not be generated, this is a bug!", _PyUnicode_AsString(pyname));
                }
-               return ret;
        }
-       else { /* Override the error */
+       else {
+#if 0
                PyErr_Format(PyExc_AttributeError, "bpy.types.%.200s RNA_Struct does not exist", _PyUnicode_AsString(pyname));
                return NULL;
+#endif
+               /* The error raised here will be displayed */
+               ret= PyObject_GenericGetAttr((PyObject *)self, pyname);
        }
+
+       return ret;
 }
 
 static PyObject *pyrna_basetype_dir(BPy_BaseTypeRNA *self);
index d480fd3ec9bc50f02630b8ce8b95b356e84bb4fb..fe38ddb6bd990f00b9a42fcedfa32519e677dfeb 100644 (file)
@@ -64,6 +64,7 @@ typedef struct {
 /* cheap trick */
 #define BPy_BaseTypeRNA BPy_PropertyRNA
 
+void      BPY_rna_init( void );
 PyObject *BPY_rna_module( void );
 void     BPY_update_rna_module( void );
 /*PyObject *BPY_rna_doc( void );*/