rna_prop_ui.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; either version 2
  6. # of the License, or (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software Foundation,
  15. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18. # <pep8 compliant>
  19. import bpy
  20. def rna_idprop_ui_get(item, create=True):
  21. try:
  22. return item['_RNA_UI']
  23. except:
  24. if create:
  25. item['_RNA_UI'] = {}
  26. return item['_RNA_UI']
  27. else:
  28. return None
  29. def rna_idprop_ui_del(item):
  30. try:
  31. del item['_RNA_UI']
  32. except KeyError:
  33. pass
  34. def rna_idprop_quote_path(prop):
  35. return "[\"%s\"]" % prop.replace("\"", "\\\"")
  36. def rna_idprop_ui_prop_update(item, prop):
  37. prop_path = rna_idprop_quote_path(prop)
  38. prop_rna = item.path_resolve(prop_path, False)
  39. if isinstance(prop_rna, bpy.types.bpy_prop):
  40. prop_rna.update()
  41. def rna_idprop_ui_prop_get(item, prop, create=True):
  42. rna_ui = rna_idprop_ui_get(item, create)
  43. if rna_ui is None:
  44. return None
  45. try:
  46. return rna_ui[prop]
  47. except:
  48. rna_ui[prop] = {}
  49. return rna_ui[prop]
  50. def rna_idprop_ui_prop_clear(item, prop, remove=True):
  51. rna_ui = rna_idprop_ui_get(item, False)
  52. if rna_ui is None:
  53. return
  54. try:
  55. del rna_ui[prop]
  56. except KeyError:
  57. pass
  58. if remove and len(item.keys()) == 1:
  59. rna_idprop_ui_del(item)
  60. def rna_idprop_context_value(context, context_member, property_type):
  61. space = context.space_data
  62. if space is None or isinstance(space, bpy.types.SpaceProperties):
  63. pin_id = space.pin_id
  64. else:
  65. pin_id = None
  66. if pin_id and isinstance(pin_id, property_type):
  67. rna_item = pin_id
  68. context_member = "space_data.pin_id"
  69. else:
  70. rna_item = eval("context." + context_member)
  71. return rna_item, context_member
  72. def rna_idprop_has_properties(rna_item):
  73. keys = rna_item.keys()
  74. nbr_props = len(keys)
  75. return (nbr_props > 1) or (nbr_props and '_RNA_UI' not in keys)
  76. def rna_idprop_ui_prop_default_set(item, prop, value):
  77. defvalue = None
  78. try:
  79. prop_type = type(item[prop])
  80. if prop_type in {int, float}:
  81. defvalue = prop_type(value)
  82. except KeyError:
  83. pass
  84. if defvalue:
  85. rna_ui = rna_idprop_ui_prop_get(item, prop, True)
  86. rna_ui["default"] = defvalue
  87. else:
  88. rna_ui = rna_idprop_ui_prop_get(item, prop)
  89. if rna_ui and "default" in rna_ui:
  90. del rna_ui["default"]
  91. def rna_idprop_ui_create(
  92. item, prop, *, default,
  93. min=0.0, max=1.0,
  94. soft_min=None, soft_max=None,
  95. description=None,
  96. overridable=False,
  97. ):
  98. """Create and initialize a custom property with limits, defaults and other settings."""
  99. proptype = type(default)
  100. # Sanitize limits
  101. if proptype is bool:
  102. min = soft_min = False
  103. max = soft_max = True
  104. if soft_min is None:
  105. soft_min = min
  106. if soft_max is None:
  107. soft_max = max
  108. # Assign the value
  109. item[prop] = default
  110. rna_idprop_ui_prop_update(item, prop)
  111. # Clear the UI settings
  112. rna_ui_group = rna_idprop_ui_get(item, True)
  113. rna_ui_group[prop] = {}
  114. rna_ui = rna_ui_group[prop]
  115. # Assign limits and default
  116. if proptype in {int, float, bool}:
  117. # The type must be exactly the same
  118. rna_ui["min"] = proptype(min)
  119. rna_ui["soft_min"] = proptype(soft_min)
  120. rna_ui["max"] = proptype(max)
  121. rna_ui["soft_max"] = proptype(soft_max)
  122. if default:
  123. rna_ui["default"] = default
  124. # Assign other settings
  125. if description is not None:
  126. rna_ui["description"] = description
  127. prop_path = rna_idprop_quote_path(prop)
  128. item.property_overridable_library_set(prop_path, overridable)
  129. return rna_ui
  130. def draw(layout, context, context_member, property_type, use_edit=True):
  131. def assign_props(prop, val, key):
  132. prop.data_path = context_member
  133. prop.property = key
  134. try:
  135. prop.value = str(val)
  136. except:
  137. pass
  138. rna_item, context_member = rna_idprop_context_value(context, context_member, property_type)
  139. # poll should really get this...
  140. if not rna_item:
  141. return
  142. from bpy.utils import escape_identifier
  143. if rna_item.id_data.library is not None:
  144. use_edit = False
  145. assert(isinstance(rna_item, property_type))
  146. items = rna_item.items()
  147. items.sort()
  148. if use_edit:
  149. row = layout.row()
  150. props = row.operator("wm.properties_add", text="Add")
  151. props.data_path = context_member
  152. del row
  153. show_developer_ui = context.preferences.view.show_developer_ui
  154. rna_properties = {prop.identifier for prop in rna_item.bl_rna.properties if prop.is_runtime} if items else None
  155. layout.use_property_split = True
  156. layout.use_property_decorate = False # No animation.
  157. flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)
  158. for key, val in items:
  159. if key == '_RNA_UI':
  160. continue
  161. is_rna = (key in rna_properties)
  162. # only show API defined for developers
  163. if is_rna and not show_developer_ui:
  164. continue
  165. to_dict = getattr(val, "to_dict", None)
  166. to_list = getattr(val, "to_list", None)
  167. # val_orig = val # UNUSED
  168. if to_dict:
  169. val = to_dict()
  170. val_draw = str(val)
  171. elif to_list:
  172. val = to_list()
  173. val_draw = str(val)
  174. else:
  175. val_draw = val
  176. row = flow.row(align=True)
  177. box = row.box()
  178. if use_edit:
  179. split = box.split(factor=0.75)
  180. row = split.row(align=True)
  181. else:
  182. row = box.row(align=True)
  183. row.alignment = 'RIGHT'
  184. row.label(text=key, translate=False)
  185. # explicit exception for arrays.
  186. if to_dict or to_list:
  187. row.label(text=val_draw, translate=False)
  188. else:
  189. if is_rna:
  190. row.prop(rna_item, key, text="")
  191. else:
  192. row.prop(rna_item, '["%s"]' % escape_identifier(key), text="")
  193. if use_edit:
  194. row = split.row(align=True)
  195. if not is_rna:
  196. props = row.operator("wm.properties_edit", text="Edit")
  197. assign_props(props, val_draw, key)
  198. props = row.operator("wm.properties_remove", text="", icon='REMOVE')
  199. assign_props(props, val_draw, key)
  200. else:
  201. row.label(text="API Defined")
  202. del flow
  203. class PropertyPanel:
  204. """
  205. The subclass should have its own poll function
  206. and the variable '_context_path' MUST be set.
  207. """
  208. bl_label = "Custom Properties"
  209. bl_options = {'DEFAULT_CLOSED'}
  210. bl_order = 1000 # Order panel after all others
  211. @classmethod
  212. def poll(cls, context):
  213. rna_item, _context_member = rna_idprop_context_value(context, cls._context_path, cls._property_type)
  214. return bool(rna_item)
  215. """
  216. def draw_header(self, context):
  217. rna_item, context_member = rna_idprop_context_value(context, self._context_path, self._property_type)
  218. tot = len(rna_item.keys())
  219. if tot:
  220. self.layout().label(text="%d:" % tot)
  221. """
  222. def draw(self, context):
  223. draw(self.layout, context, self._context_path, self._property_type)