blender/release/scripts/modules/rna_prop_ui.py
2020-06-30 15:06:33 +02:00

347 lines
9.7 KiB
Python

# ##### 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import bpy
from mathutils import Vector
from idprop.types import IDPropertyArray, IDPropertyGroup
ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector)
# Maximum length of an array property for which a multi-line
# edit field will be displayed in the Custom Properties panel.
MAX_DISPLAY_ROWS = 4
def rna_idprop_ui_get(item, create=True):
try:
return item['_RNA_UI']
except:
if create:
item['_RNA_UI'] = {}
return item['_RNA_UI']
else:
return None
def rna_idprop_ui_del(item):
try:
del item['_RNA_UI']
except KeyError:
pass
def rna_idprop_quote_path(prop):
return "[\"%s\"]" % prop.replace("\"", "\\\"")
def rna_idprop_ui_prop_update(item, prop):
prop_path = rna_idprop_quote_path(prop)
prop_rna = item.path_resolve(prop_path, False)
if isinstance(prop_rna, bpy.types.bpy_prop):
prop_rna.update()
def rna_idprop_ui_prop_get(item, prop, create=True):
rna_ui = rna_idprop_ui_get(item, create)
if rna_ui is None:
return None
try:
return rna_ui[prop]
except:
rna_ui[prop] = {}
return rna_ui[prop]
def rna_idprop_ui_prop_clear(item, prop, remove=True):
rna_ui = rna_idprop_ui_get(item, False)
if rna_ui is None:
return
try:
del rna_ui[prop]
except KeyError:
pass
if remove and len(item.keys()) == 1:
rna_idprop_ui_del(item)
def rna_idprop_context_value(context, context_member, property_type):
space = context.space_data
if space is None or isinstance(space, bpy.types.SpaceProperties):
pin_id = space.pin_id
else:
pin_id = None
if pin_id and isinstance(pin_id, property_type):
rna_item = pin_id
context_member = "space_data.pin_id"
else:
rna_item = eval("context." + context_member)
return rna_item, context_member
def rna_idprop_has_properties(rna_item):
keys = rna_item.keys()
nbr_props = len(keys)
return (nbr_props > 1) or (nbr_props and '_RNA_UI' not in keys)
def rna_idprop_value_to_python(value):
if isinstance(value, IDPropertyArray):
return value.to_list()
elif isinstance(value, IDPropertyGroup):
return value.to_dict()
else:
return value
def rna_idprop_value_item_type(value):
is_array = isinstance(value, ARRAY_TYPES) and len(value) > 0
item_value = value[0] if is_array else value
return type(item_value), is_array
def rna_idprop_ui_prop_default_set(item, prop, value):
defvalue = None
try:
prop_type, is_array = rna_idprop_value_item_type(item[prop])
if prop_type in {int, float, str}:
if is_array and isinstance(value, ARRAY_TYPES):
value = [prop_type(item) for item in value]
if any(value):
defvalue = value
else:
defvalue = prop_type(value)
except KeyError:
pass
except ValueError:
pass
if defvalue:
rna_ui = rna_idprop_ui_prop_get(item, prop, True)
rna_ui["default"] = defvalue
else:
rna_ui = rna_idprop_ui_prop_get(item, prop)
if rna_ui:
rna_ui.pop("default", None)
return defvalue
def rna_idprop_ui_create(
item, prop, *, default,
min=0.0, max=1.0,
soft_min=None, soft_max=None,
description=None,
overridable=False,
subtype=None,
):
"""Create and initialize a custom property with limits, defaults and other settings."""
proptype, is_array = rna_idprop_value_item_type(default)
# Sanitize limits
if proptype is bool:
min = soft_min = False
max = soft_max = True
if soft_min is None:
soft_min = min
if soft_max is None:
soft_max = max
# Assign the value
item[prop] = default
rna_idprop_ui_prop_update(item, prop)
# Clear the UI settings
rna_ui_group = rna_idprop_ui_get(item, True)
rna_ui_group[prop] = {}
rna_ui = rna_ui_group[prop]
# Assign limits and default
if proptype in {int, float, bool}:
# The type must be exactly the same
rna_ui["min"] = proptype(min)
rna_ui["soft_min"] = proptype(soft_min)
rna_ui["max"] = proptype(max)
rna_ui["soft_max"] = proptype(soft_max)
if default and (not is_array or any(default)):
rna_ui["default"] = default
if is_array and subtype and subtype != 'NONE':
rna_ui["subtype"] = subtype
# Assign other settings
if description is not None:
rna_ui["description"] = description
prop_path = rna_idprop_quote_path(prop)
item.property_overridable_library_set(prop_path, overridable)
return rna_ui
def draw(layout, context, context_member, property_type, use_edit=True):
def assign_props(prop, val, key):
prop.data_path = context_member
prop.property = key
try:
prop.value = str(val)
except:
pass
rna_item, context_member = rna_idprop_context_value(context, context_member, property_type)
# poll should really get this...
if not rna_item:
return
from bpy.utils import escape_identifier
if rna_item.id_data.library is not None:
use_edit = False
is_lib_override = rna_item.id_data.override_library and rna_item.id_data.override_library.reference
assert(isinstance(rna_item, property_type))
items = rna_item.items()
items.sort()
# TODO: Allow/support adding new custom props to overrides.
if use_edit and not is_lib_override:
row = layout.row()
props = row.operator("wm.properties_add", text="Add")
props.data_path = context_member
del row
show_developer_ui = context.preferences.view.show_developer_ui
rna_properties = {prop.identifier for prop in rna_item.bl_rna.properties if prop.is_runtime} if items else None
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
flow = layout.grid_flow(row_major=False, columns=0, even_columns=True, even_rows=False, align=True)
for key, val in items:
if key == '_RNA_UI':
continue
is_rna = (key in rna_properties)
# only show API defined for developers
if is_rna and not show_developer_ui:
continue
to_dict = getattr(val, "to_dict", None)
to_list = getattr(val, "to_list", None)
# val_orig = val # UNUSED
if to_dict:
val = to_dict()
val_draw = str(val)
elif to_list:
val = to_list()
val_draw = str(val)
else:
val_draw = val
row = layout.row(align=True)
box = row.box()
if use_edit:
split = box.split(factor=0.75)
row = split.row(align=True)
else:
split = box.split(factor=1.00)
row = split.row(align=True)
row.alignment = 'RIGHT'
row.label(text=key, translate=False)
# explicit exception for arrays.
show_array_ui = to_list and not is_rna and 0 < len(val) <= MAX_DISPLAY_ROWS
if show_array_ui and isinstance(val[0], (int, float)):
row.prop(rna_item, '["%s"]' % escape_identifier(key), text="")
elif to_dict or to_list:
row.label(text=val_draw, translate=False)
else:
if is_rna:
row.prop(rna_item, key, text="")
else:
row.prop(rna_item, '["%s"]' % escape_identifier(key), text="")
if use_edit:
row = split.row(align=True)
# Do not allow editing of overridden properties (we cannot use a poll function of the operators here
# since they's have no access to the specific property...).
row.enabled = not(is_lib_override and key in rna_item.id_data.override_library.reference)
if not is_rna:
props = row.operator("wm.properties_edit", text="Edit")
assign_props(props, val_draw, key)
props = row.operator("wm.properties_remove", text="", icon='REMOVE')
assign_props(props, val_draw, key)
else:
row.label(text="API Defined")
del flow
class PropertyPanel:
"""
The subclass should have its own poll function
and the variable '_context_path' MUST be set.
"""
bl_label = "Custom Properties"
bl_options = {'DEFAULT_CLOSED'}
bl_order = 1000 # Order panel after all others
@classmethod
def poll(cls, context):
rna_item, _context_member = rna_idprop_context_value(context, cls._context_path, cls._property_type)
return bool(rna_item)
"""
def draw_header(self, context):
rna_item, context_member = rna_idprop_context_value(context, self._context_path, self._property_type)
tot = len(rna_item.keys())
if tot:
self.layout().label(text="%d:" % tot)
"""
def draw(self, context):
draw(self.layout, context, self._context_path, self._property_type)