From b7e2cd5948a437cc5c599dec966f9dfc05212aeb Mon Sep 17 00:00:00 2001 From: Bastien Montagne Date: Thu, 29 Aug 2013 13:34:36 +0000 Subject: [PATCH] UIList: update examples and templates. --- ....types.UIList.py => bpy.types.UIList.1.py} | 5 +- doc/python_api/examples/bpy.types.UIList.2.py | 160 ++++++++++++++++++ release/scripts/templates_py/ui_list.py | 102 ++++------- .../scripts/templates_py/ui_list_simple.py | 82 +++++++++ 4 files changed, 280 insertions(+), 69 deletions(-) rename doc/python_api/examples/{bpy.types.UIList.py => bpy.types.UIList.1.py} (94%) create mode 100644 doc/python_api/examples/bpy.types.UIList.2.py create mode 100644 release/scripts/templates_py/ui_list_simple.py diff --git a/doc/python_api/examples/bpy.types.UIList.py b/doc/python_api/examples/bpy.types.UIList.1.py 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 0f4ae0703cc..97c9bb40480 100644 --- a/doc/python_api/examples/bpy.types.UIList.py +++ b/doc/python_api/examples/bpy.types.UIList.1.py @@ -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 index 00000000000..3ffd099859a --- /dev/null +++ b/doc/python_api/examples/bpy.types.UIList.2.py @@ -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 diff --git a/release/scripts/templates_py/ui_list.py b/release/scripts/templates_py/ui_list.py index f71b342c854..61ad0ae0435 100644 --- a/release/scripts/templates_py/ui_list.py +++ b/release/scripts/templates_py/ui_list.py @@ -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 ", 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) + 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 -# 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" + # 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!). - def draw(self, context): - layout = self.layout + # Default return values. + flt_flags = [] + flt_neworder = [] - obj = context.object + # Do filtering/reordering here... - # 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 + 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 index 00000000000..815d62ad734 --- /dev/null +++ b/release/scripts/templates_py/ui_list_simple.py @@ -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 ", 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()