UIList: update examples and templates.
authorBastien Montagne <montagne29@wanadoo.fr>
Thu, 29 Aug 2013 13:34:36 +0000 (13:34 +0000)
committerBastien Montagne <montagne29@wanadoo.fr>
Thu, 29 Aug 2013 13:34:36 +0000 (13:34 +0000)
doc/python_api/examples/bpy.types.UIList.1.py [moved from doc/python_api/examples/bpy.types.UIList.py with 94% similarity]
doc/python_api/examples/bpy.types.UIList.2.py [new file with mode: 0644]
release/scripts/templates_py/ui_list.py
release/scripts/templates_py/ui_list_simple.py [new file with mode: 0644]

similarity index 94%
rename from doc/python_api/examples/bpy.types.UIList.py
rename to doc/python_api/examples/bpy.types.UIList.1.py
index 0f4ae0703ccb9f3dc31dd9d0839ef3d580e5116d..97c9bb40480c33fc135fa17e7aba2999a17c3577 100644 (file)
@@ -22,7 +22,10 @@ class MATERIAL_UL_matslots_example(bpy.types.UIList):
     #   active item of the collection).
     #   active_propname is the name of the active property (use 'getattr(active_data, active_propname)').
     #   index is index of the current item in the collection.
-    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
+    #   flt_flag is the result of the filtering process for this item.
+    #   Note: as index and flt_flag are optional arguments, you do not have to use/declare them here if you don't
+    #         need them.
+    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
         ob = data
         slot = item
         ma = slot.material
diff --git a/doc/python_api/examples/bpy.types.UIList.2.py b/doc/python_api/examples/bpy.types.UIList.2.py
new file mode 100644 (file)
index 0000000..3ffd099
--- /dev/null
@@ -0,0 +1,160 @@
+"""
+Advanced UIList Example - Filtering and Reordering
+++++++++++++++++++++++++++++++++++++++++++++++++++
+This script is an extended version of the UIList subclass used to show vertex groups. It is not used 'as is',
+because iterating over all vertices in a 'draw' function is a very bad idea for UI performances! However, it's a good
+example of how to create/use filtering/reordering callbacks.
+"""
+import bpy
+
+
+class MESH_UL_vgroups_slow(UIList):
+    # Constants (flags)
+    # Be careful not to shadow FILTER_ITEM!
+    VGROUP_EMPTY = 1 << 0
+
+    # Custom properties, saved with .blend file.
+    use_filter_empty = bpy.props.BoolProperty(name="Filter Empty", default=False, options=set(),
+                                              description="Whether to filter empty vertex groups")
+    use_filter_empty_reverse = bpy.props.BoolProperty(name="Reverse Empty", default=False, options=set(),
+                                                      description="Reverse empty filtering")
+    use_filter_name_reverse = bpy.props.BoolProperty(name="Reverse Name", default=False, options=set(),
+                                                     description="Reverse name filtering")
+
+    # This allows us to have mutually exclusive options, which are also all disable-able!
+    def _gen_order_update(name1, name2):
+        def _u(self, ctxt):
+            if (getattr(self, name1)):
+                setattr(self, name2, False)
+        return _u
+    use_order_name = bpy.props.BoolProperty(name="Name", default=False, options=set(),
+                                            description="Sort groups by their name (case-insensitive)",
+                                            update=_gen_order_update("use_order_name", "use_order_importance"))
+    use_order_importance = bpy.props.BoolProperty(name="Importance", default=False, options=set(),
+                                                  description="Sort groups by their average weight in the mesh",
+                                                  update=_gen_order_update("use_order_importance", "use_order_name"))
+
+    # Usual draw item function.
+    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
+        # Just in case, we do not use it here!
+        self.use_filter_invert = False
+
+        # assert(isinstance(item, bpy.types.VertexGroup)
+        vgroup = item
+        if self.layout_type in {'DEFAULT', 'COMPACT'}:
+            # Here we use one feature of new filtering feature: it can pass data to draw_item, through flt_flag
+            # parameter, which contains exactly what filter_items set in its filter list for this item!
+            # In this case, we show empty groups grayed out.
+            if flt_flag & self.VGROUP_EMPTY:
+                col = layout.column()
+                col.enabled = False
+                col.alignment = 'LEFT'
+                col.label(text=vgroup.name, translate=False, icon_value=icon)
+            else:
+                layout.label(text=vgroup.name, translate=False, icon_value=icon)
+            icon = 'LOCKED' if vgroup.lock_weight else 'UNLOCKED'
+            layout.prop(vgroup, "lock_weight", text="", icon=icon, emboss=False)
+        elif self.layout_type in {'GRID'}:
+            layout.alignment = 'CENTER'
+            if flt_flag & self.VGROUP_EMPTY:
+                layout.enabled = False
+            layout.label(text="", icon_value=icon)
+
+    def draw_filter(self, context, layout):
+        # Nothing much to say here, it's usual UI code...
+        row = layout.row()
+
+        subrow = row.row(align=True)
+        subrow.prop(self, "filter_name", text="")
+        icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN'
+        subrow.prop(self, "use_filter_name_reverse", text="", icon=icon)
+
+        subrow = row.row(align=True)
+        subrow.prop(self, "use_filter_empty", toggle=True)
+        icon = 'ZOOM_OUT' if self.use_filter_empty_reverse else 'ZOOM_IN'
+        subrow.prop(self, "use_filter_empty_reverse", text="", icon=icon)
+
+        row = layout.row(align=True)
+        row.label("Order by:")
+        row.prop(self, "use_order_name", toggle=True)
+        row.prop(self, "use_order_importance", toggle=True)
+        icon = 'TRIA_UP' if self.use_filter_orderby_invert else 'TRIA_DOWN'
+        row.prop(self, "use_filter_orderby_invert", text="", icon=icon)
+
+    def filter_items_empty_vgroups(self, context, vgroups):
+        # This helper function checks vgroups to find out whether they are empty, and what's their average weights.
+        # TODO: This should be RNA helper actually (a vgroup prop like "raw_data: ((vidx, vweight), etc.)").
+        #       Too slow for python!
+        obj_data = context.active_object.data
+        ret = {vg.index: [True, 0.0] for vg in vgroups}
+        if hasattr(obj_data, "vertices"):  # Mesh data
+            if obj_data.is_editmode:
+                import bmesh
+                bm = bmesh.from_edit_mesh(obj_data)
+                # only ever one deform weight layer
+                dvert_lay = bm.verts.layers.deform.active
+                fact = 1 / len(bm.verts)
+                if dvert_lay:
+                    for v in bm.verts:
+                        for vg_idx, vg_weight in v[dvert_lay].items():
+                            ret[vg_idx][0] = False
+                            ret[vg_idx][1] += vg_weight * fact
+            else:
+                fact = 1 / len(obj_data.vertices)
+                for v in obj_data.vertices:
+                    for vg in v.groups:
+                        ret[vg.group][0] = False
+                        ret[vg.group][1] += vg.weight * fact
+        elif hasattr(obj_data, "points"):  # Lattice data
+            # XXX no access to lattice editdata?
+            fact = 1 / len(obj_data.points)
+            for v in obj_data.points:
+                for vg in v.groups:
+                    ret[vg.group][0] = False
+                    ret[vg.group][1] += vg.weight * fact
+        return ret
+
+    def filter_items(self, context, data, propname):
+        # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
+        # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
+        #   matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the
+        #   first one to mark VGROUP_EMPTY.
+        # * The second one is for reordering, it must return a list containing the new indices of the items (which
+        #   gives us a mapping org_idx -> new_idx).
+        # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).
+        # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than
+        # returning full lists doing nothing!).
+        vgroups = getattr(data, propname)
+        helper_funcs = bpy.types.UI_UL_list
+
+        # Default return values.
+        flt_flags = []
+        flt_neworder = []
+
+        # Pre-compute of vgroups data, CPU-intensive. :/
+        vgroups_empty = self.filter_items_empty_vgroups(context, vgroups)
+
+        # Filtering by name
+        if self.filter_name:
+            flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, vgroups, "name",
+                                                          reverse=self.use_filter_name_reverse)
+        if not flt_flags:
+            flt_flags = [self.bitflag_filter_item] * len(vgroups)
+
+        # Filter by emptiness.
+        for idx, vg in enumerate(vgroups):
+            if vgroups_empty[vg.index][0]:
+                flt_flags[idx] |= self.VGROUP_EMPTY
+                if self.use_filter_empty and self.use_filter_empty_reverse:
+                    flt_flags[idx] &= ~self.bitflag_filter_item
+            elif self.use_filter_empty and not self.use_filter_empty_reverse:
+                flt_flags[idx] &= ~self.bitflag_filter_item
+
+        # Reorder by name or average weight.
+        if self.use_order_name:
+            flt_neworder = helper_funcs.sort_items_by_name(vgroups, "name")
+        elif self.use_order_importance:
+            _sort = [(idx, vgroups_empty[vg.index][1]) for idx, vg in enumerate(vgroups)]
+            flt_neworder = helper_funcs.sort_items_helper(_sort, lambda e: e[1], True)
+
+        return flt_flags, flt_neworder
index f71b342c85435eee4d51c9e3598c2ac51b34a16a..61ad0ae04353e5f9ee8fbbabb0dd3f0ce0047301 100644 (file)
@@ -1,79 +1,45 @@
 import bpy
 
 
-class MATERIAL_UL_matslots_example(bpy.types.UIList):
-    # The draw_item function is called for each item of the collection that is visible in the list.
-    #   data is the RNA object containing the collection,
-    #   item is the current drawn item of the collection,
-    #   icon is the "computed" icon for the item (as an integer, because some objects like materials or textures
-    #   have custom icons ID, which are not available as enum items).
-    #   active_data is the RNA object containing the active property for the collection (i.e. integer pointing to the
-    #   active item of the collection).
-    #   active_propname is the name of the active property (use 'getattr(active_data, active_propname)').
-    #   index is index of the current item in the collection.
-    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
-        ob = data
-        slot = item
-        ma = slot.material
-        # draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code.
+class MESH_UL_mylist(UIList):
+    # Constants (flags)
+    # Be careful not to shadow FILTER_ITEM (i.e. UIList().bitflag_filter_item)!
+    # E.g. VGROUP_EMPTY = 1 << 0
+
+    # Custom properties, saved with .blend file. E.g.
+    # use_filter_empty = bpy.props.BoolProperty(name="Filter Empty", default=False, options=set(),
+    #                                           description="Whether to filter empty vertex groups")
+
+    # Called for each drawn item.
+    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
+        # 'DEFAULT' and 'COMPACT' layout types should usually use the same draw code.
         if self.layout_type in {'DEFAULT', 'COMPACT'}:
-            # You should always start your row layout by a label (icon + text), this will also make the row easily
-            # selectable in the list!
-            # We use icon_value of label, as our given icon is an integer value, not an enum ID.
-            # Note "data" names should never be translated!
-            layout.label(text=ma.name if ma else "", translate=False, icon_value=icon)
-            # And now we can add other UI stuff...
-            # Here, we add nodes info if this material uses (old!) shading nodes.
-            if ma and not context.scene.render.use_shading_nodes:
-                manode = ma.active_node_material
-                if manode:
-                    # The static method UILayout.icon returns the integer value of the icon ID "computed" for the given
-                    # RNA object.
-                    layout.label(text="Node %s" % manode.name, translate=False, icon_value=layout.icon(manode))
-                elif ma.use_nodes:
-                    layout.label(text="Node <none>", translate=False)
-                else:
-                    layout.label(text="")
+            pass
         # 'GRID' layout type should be as compact as possible (typically a single icon!).
         elif self.layout_type in {'GRID'}:
-            layout.alignment = 'CENTER'
-            layout.label(text="", icon_value=icon)
-
-
-# And now we can use this list everywhere in Blender. Here is a small example panel.
-class UIListPanelExample(bpy.types.Panel):
-    """Creates a Panel in the Object properties window"""
-    bl_label = "UIList Panel"
-    bl_idname = "OBJECT_PT_ui_list_example"
-    bl_space_type = 'PROPERTIES'
-    bl_region_type = 'WINDOW'
-    bl_context = "object"
-
-    def draw(self, context):
-        layout = self.layout
-
-        obj = context.object
-
-        # template_list now takes two new args.
-        # The first one is the identifier of the registered UIList to use (if you want only the default list,
-        # with no custom draw code, use "UI_UL_list").
-        layout.template_list("MATERIAL_UL_matslots_example", "", obj, "material_slots", obj, "active_material_index")
-
-        # The second one can usually be left as an empty string. It's an additional ID used to distinguish lists in case you
-        # use the same list several times in a given area.
-        layout.template_list("MATERIAL_UL_matslots_example", "compact", obj, "material_slots",
-                             obj, "active_material_index", type='COMPACT')
-
-
-def register():
-    bpy.utils.register_class(MATERIAL_UL_matslots_example)
-    bpy.utils.register_class(UIListPanelExample)
-
-
-def unregister():
-    bpy.utils.unregister_class(MATERIAL_UL_matslots_example)
-    bpy.utils.unregister_class(UIListPanelExample)
-
-
-if __name__ == "__main__":
-    register()
\ No newline at end of file
+            pass
+
+    # Called once to draw filtering/reordering options.
+    def draw_filter(self, context, layout):
+        # Nothing much to say here, it's usual UI code...
+        pass
+
+    # Called once to filter/reorder items.
+    def filter_items(self, context, data, propname):
+        # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
+        # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
+        #   matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the
+        #   first one to mark VGROUP_EMPTY.
+        # * The second one is for reordering, it must return a list containing the new indices of the items (which
+        #   gives us a mapping org_idx -> new_idx).
+        # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).
+        # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than
+        # returning full lists doing nothing!).
+
+        # Default return values.
+        flt_flags = []
+        flt_neworder = []
+
+        # Do filtering/reordering here...
+
+        return flt_flags, flt_neworder
diff --git a/release/scripts/templates_py/ui_list_simple.py b/release/scripts/templates_py/ui_list_simple.py
new file mode 100644 (file)
index 0000000..815d62a
--- /dev/null
@@ -0,0 +1,82 @@
+import bpy
+
+
+class MATERIAL_UL_matslots_example(bpy.types.UIList):
+    # The draw_item function is called for each item of the collection that is visible in the list.
+    #   data is the RNA object containing the collection,
+    #   item is the current drawn item of the collection,
+    #   icon is the "computed" icon for the item (as an integer, because some objects like materials or textures
+    #   have custom icons ID, which are not available as enum items).
+    #   active_data is the RNA object containing the active property for the collection (i.e. integer pointing to the
+    #   active item of the collection).
+    #   active_propname is the name of the active property (use 'getattr(active_data, active_propname)').
+    #   index is index of the current item in the collection.
+    #   flt_flag is the result of the filtering process for this item.
+    #   Note: as index and flt_flag are optional arguments, you do not have to use/declare them here if you don't
+    #         need them.
+    def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
+        ob = data
+        slot = item
+        ma = slot.material
+        # draw_item must handle the three layout types... Usually 'DEFAULT' and 'COMPACT' can share the same code.
+        if self.layout_type in {'DEFAULT', 'COMPACT'}:
+            # You should always start your row layout by a label (icon + text), this will also make the row easily
+            # selectable in the list!
+            # We use icon_value of label, as our given icon is an integer value, not an enum ID.
+            # Note "data" names should never be translated!
+            layout.label(text=ma.name if ma else "", translate=False, icon_value=icon)
+            # And now we can add other UI stuff...
+            # Here, we add nodes info if this material uses (old!) shading nodes.
+            if ma and not context.scene.render.use_shading_nodes:
+                manode = ma.active_node_material
+                if manode:
+                    # The static method UILayout.icon returns the integer value of the icon ID "computed" for the given
+                    # RNA object.
+                    layout.label(text="Node %s" % manode.name, translate=False, icon_value=layout.icon(manode))
+                elif ma.use_nodes:
+                    layout.label(text="Node <none>", translate=False)
+                else:
+                    layout.label(text="")
+        # 'GRID' layout type should be as compact as possible (typically a single icon!).
+        elif self.layout_type in {'GRID'}:
+            layout.alignment = 'CENTER'
+            layout.label(text="", icon_value=icon)
+
+
+# And now we can use this list everywhere in Blender. Here is a small example panel.
+class UIListPanelExample(bpy.types.Panel):
+    """Creates a Panel in the Object properties window"""
+    bl_label = "UIList Panel"
+    bl_idname = "OBJECT_PT_ui_list_example"
+    bl_space_type = 'PROPERTIES'
+    bl_region_type = 'WINDOW'
+    bl_context = "object"
+
+    def draw(self, context):
+        layout = self.layout
+
+        obj = context.object
+
+        # template_list now takes two new args.
+        # The first one is the identifier of the registered UIList to use (if you want only the default list,
+        # with no custom draw code, use "UI_UL_list").
+        layout.template_list("MATERIAL_UL_matslots_example", "", obj, "material_slots", obj, "active_material_index")
+
+        # The second one can usually be left as an empty string. It's an additional ID used to distinguish lists in case you
+        # use the same list several times in a given area.
+        layout.template_list("MATERIAL_UL_matslots_example", "compact", obj, "material_slots",
+                             obj, "active_material_index", type='COMPACT')
+
+
+def register():
+    bpy.utils.register_class(MATERIAL_UL_matslots_example)
+    bpy.utils.register_class(UIListPanelExample)
+
+
+def unregister():
+    bpy.utils.unregister_class(MATERIAL_UL_matslots_example)
+    bpy.utils.unregister_class(UIListPanelExample)
+
+
+if __name__ == "__main__":
+    register()