Add ctrl-click rename to most lists in Blender UI and templates/examples.
[blender.git] / doc / python_api / examples / bpy.types.UIList.2.py
1 """
2 Advanced UIList Example - Filtering and Reordering
3 ++++++++++++++++++++++++++++++++++++++++++++++++++
4 This script is an extended version of the UIList subclass used to show vertex groups. It is not used 'as is',
5 because iterating over all vertices in a 'draw' function is a very bad idea for UI performances! However, it's a good
6 example of how to create/use filtering/reordering callbacks.
7 """
8 import bpy
9
10
11 class MESH_UL_vgroups_slow(bpy.types.UIList):
12     # Constants (flags)
13     # Be careful not to shadow FILTER_ITEM!
14     VGROUP_EMPTY = 1 << 0
15
16     # Custom properties, saved with .blend file.
17     use_filter_empty = bpy.props.BoolProperty(name="Filter Empty", default=False, options=set(),
18                                               description="Whether to filter empty vertex groups")
19     use_filter_empty_reverse = bpy.props.BoolProperty(name="Reverse Empty", default=False, options=set(),
20                                                       description="Reverse empty filtering")
21     use_filter_name_reverse = bpy.props.BoolProperty(name="Reverse Name", default=False, options=set(),
22                                                      description="Reverse name filtering")
23
24     # This allows us to have mutually exclusive options, which are also all disable-able!
25     def _gen_order_update(name1, name2):
26         def _u(self, ctxt):
27             if (getattr(self, name1)):
28                 setattr(self, name2, False)
29         return _u
30     use_order_name = bpy.props.BoolProperty(name="Name", default=False, options=set(),
31                                             description="Sort groups by their name (case-insensitive)",
32                                             update=_gen_order_update("use_order_name", "use_order_importance"))
33     use_order_importance = bpy.props.BoolProperty(name="Importance", default=False, options=set(),
34                                                   description="Sort groups by their average weight in the mesh",
35                                                   update=_gen_order_update("use_order_importance", "use_order_name"))
36
37     # Usual draw item function.
38     def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag):
39         # Just in case, we do not use it here!
40         self.use_filter_invert = False
41
42         # assert(isinstance(item, bpy.types.VertexGroup)
43         vgroup = item
44         if self.layout_type in {'DEFAULT', 'COMPACT'}:
45             # Here we use one feature of new filtering feature: it can pass data to draw_item, through flt_flag
46             # parameter, which contains exactly what filter_items set in its filter list for this item!
47             # In this case, we show empty groups grayed out.
48             if flt_flag & self.VGROUP_EMPTY:
49                 col = layout.column()
50                 col.enabled = False
51                 col.alignment = 'LEFT'
52                 col.prop(vgroup, "name", text="", emboss=False, icon_value=icon)
53             else:
54                 layout.prop(vgroup, "name", text="", emboss=False, icon_value=icon)
55             icon = 'LOCKED' if vgroup.lock_weight else 'UNLOCKED'
56             layout.prop(vgroup, "lock_weight", text="", icon=icon, emboss=False)
57         elif self.layout_type in {'GRID'}:
58             layout.alignment = 'CENTER'
59             if flt_flag & self.VGROUP_EMPTY:
60                 layout.enabled = False
61             layout.label(text="", icon_value=icon)
62
63     def draw_filter(self, context, layout):
64         # Nothing much to say here, it's usual UI code...
65         row = layout.row()
66
67         subrow = row.row(align=True)
68         subrow.prop(self, "filter_name", text="")
69         icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN'
70         subrow.prop(self, "use_filter_name_reverse", text="", icon=icon)
71
72         subrow = row.row(align=True)
73         subrow.prop(self, "use_filter_empty", toggle=True)
74         icon = 'ZOOM_OUT' if self.use_filter_empty_reverse else 'ZOOM_IN'
75         subrow.prop(self, "use_filter_empty_reverse", text="", icon=icon)
76
77         row = layout.row(align=True)
78         row.label("Order by:")
79         row.prop(self, "use_order_name", toggle=True)
80         row.prop(self, "use_order_importance", toggle=True)
81         icon = 'TRIA_UP' if self.use_filter_orderby_invert else 'TRIA_DOWN'
82         row.prop(self, "use_filter_orderby_invert", text="", icon=icon)
83
84     def filter_items_empty_vgroups(self, context, vgroups):
85         # This helper function checks vgroups to find out whether they are empty, and what's their average weights.
86         # TODO: This should be RNA helper actually (a vgroup prop like "raw_data: ((vidx, vweight), etc.)").
87         #       Too slow for python!
88         obj_data = context.active_object.data
89         ret = {vg.index: [True, 0.0] for vg in vgroups}
90         if hasattr(obj_data, "vertices"):  # Mesh data
91             if obj_data.is_editmode:
92                 import bmesh
93                 bm = bmesh.from_edit_mesh(obj_data)
94                 # only ever one deform weight layer
95                 dvert_lay = bm.verts.layers.deform.active
96                 fact = 1 / len(bm.verts)
97                 if dvert_lay:
98                     for v in bm.verts:
99                         for vg_idx, vg_weight in v[dvert_lay].items():
100                             ret[vg_idx][0] = False
101                             ret[vg_idx][1] += vg_weight * fact
102             else:
103                 fact = 1 / len(obj_data.vertices)
104                 for v in obj_data.vertices:
105                     for vg in v.groups:
106                         ret[vg.group][0] = False
107                         ret[vg.group][1] += vg.weight * fact
108         elif hasattr(obj_data, "points"):  # Lattice data
109             # XXX no access to lattice editdata?
110             fact = 1 / len(obj_data.points)
111             for v in obj_data.points:
112                 for vg in v.groups:
113                     ret[vg.group][0] = False
114                     ret[vg.group][1] += vg.weight * fact
115         return ret
116
117     def filter_items(self, context, data, propname):
118         # This function gets the collection property (as the usual tuple (data, propname)), and must return two lists:
119         # * The first one is for filtering, it must contain 32bit integers were self.bitflag_filter_item marks the
120         #   matching item as filtered (i.e. to be shown), and 31 other bits are free for custom needs. Here we use the
121         #   first one to mark VGROUP_EMPTY.
122         # * The second one is for reordering, it must return a list containing the new indices of the items (which
123         #   gives us a mapping org_idx -> new_idx).
124         # Please note that the default UI_UL_list defines helper functions for common tasks (see its doc for more info).
125         # If you do not make filtering and/or ordering, return empty list(s) (this will be more efficient than
126         # returning full lists doing nothing!).
127         vgroups = getattr(data, propname)
128         helper_funcs = bpy.types.UI_UL_list
129
130         # Default return values.
131         flt_flags = []
132         flt_neworder = []
133
134         # Pre-compute of vgroups data, CPU-intensive. :/
135         vgroups_empty = self.filter_items_empty_vgroups(context, vgroups)
136
137         # Filtering by name
138         if self.filter_name:
139             flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, vgroups, "name",
140                                                           reverse=self.use_filter_name_reverse)
141         if not flt_flags:
142             flt_flags = [self.bitflag_filter_item] * len(vgroups)
143
144         # Filter by emptiness.
145         for idx, vg in enumerate(vgroups):
146             if vgroups_empty[vg.index][0]:
147                 flt_flags[idx] |= self.VGROUP_EMPTY
148                 if self.use_filter_empty and self.use_filter_empty_reverse:
149                     flt_flags[idx] &= ~self.bitflag_filter_item
150             elif self.use_filter_empty and not self.use_filter_empty_reverse:
151                 flt_flags[idx] &= ~self.bitflag_filter_item
152
153         # Reorder by name or average weight.
154         if self.use_order_name:
155             flt_neworder = helper_funcs.sort_items_by_name(vgroups, "name")
156         elif self.use_order_importance:
157             _sort = [(idx, vgroups_empty[vg.index][1]) for idx, vg in enumerate(vgroups)]
158             flt_neworder = helper_funcs.sort_items_helper(_sort, lambda e: e[1], True)
159
160         return flt_flags, flt_neworder