2274f58ef2
Remove empty space in Custom Properties panel, and align buttons in a row. Pull Request: https://projects.blender.org/blender/blender/pulls/115913
270 lines
8.1 KiB
Python
270 lines
8.1 KiB
Python
# SPDX-FileCopyrightText: 2009-2023 Blender Authors
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import bpy
|
|
|
|
from mathutils import Vector
|
|
from bpy.types import bpy_prop_array
|
|
from idprop.types import IDPropertyArray, IDPropertyGroup
|
|
|
|
ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector, bpy_prop_array)
|
|
|
|
# Maximum length of an array property for which a multi-line
|
|
# edit field will be displayed in the Custom Properties panel.
|
|
MAX_DISPLAY_ROWS = 8
|
|
|
|
|
|
def rna_idprop_quote_path(prop):
|
|
return "[\"%s\"]" % bpy.utils.escape_identifier(prop)
|
|
|
|
|
|
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_clear(item, prop):
|
|
ui_data = item.id_properties_ui(prop)
|
|
ui_data.clear()
|
|
|
|
|
|
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 = context.path_resolve(context_member)
|
|
|
|
return rna_item, context_member
|
|
|
|
|
|
def rna_idprop_has_properties(rna_item):
|
|
keys = rna_item.keys()
|
|
return bool(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):
|
|
ui_data = item.id_properties_ui(prop)
|
|
ui_data.update(default=value)
|
|
|
|
|
|
def rna_idprop_ui_create(
|
|
item, prop, *, default,
|
|
min=0, max=1,
|
|
soft_min=None, soft_max=None,
|
|
description=None,
|
|
overridable=False,
|
|
subtype=None,
|
|
step=None,
|
|
precision=None,
|
|
id_type='OBJECT',
|
|
items=None,
|
|
):
|
|
"""Create and initialize a custom property with limits, defaults and other settings."""
|
|
|
|
# Assign the value
|
|
item[prop] = default
|
|
|
|
rna_idprop_ui_prop_update(item, prop)
|
|
ui_data = item.id_properties_ui(prop)
|
|
proptype, _ = rna_idprop_value_item_type(default)
|
|
|
|
if soft_min is None:
|
|
soft_min = min
|
|
if soft_max is None:
|
|
soft_max = max
|
|
|
|
if (proptype is bool) or (proptype is str):
|
|
ui_data.update(
|
|
description=description,
|
|
default=default,
|
|
)
|
|
elif proptype is type(None) or issubclass(proptype, bpy.types.ID):
|
|
ui_data.update(
|
|
description=description,
|
|
id_type=id_type,
|
|
)
|
|
elif proptype is float:
|
|
if step is None:
|
|
step = 0.1
|
|
if precision is None:
|
|
precision = 3
|
|
|
|
ui_data.update(
|
|
subtype=subtype,
|
|
min=min,
|
|
max=max,
|
|
soft_min=soft_min,
|
|
soft_max=soft_max,
|
|
step=step,
|
|
precision=precision,
|
|
description=description,
|
|
default=default,
|
|
)
|
|
elif proptype is int:
|
|
if step is None:
|
|
step = 1
|
|
|
|
if items is None:
|
|
ui_data.update(
|
|
subtype=subtype,
|
|
min=min,
|
|
max=max,
|
|
soft_min=soft_min,
|
|
soft_max=soft_max,
|
|
step=step,
|
|
description=description,
|
|
default=default,
|
|
)
|
|
else:
|
|
ui_data.update(
|
|
subtype=subtype,
|
|
description=description,
|
|
default=default,
|
|
items=items,
|
|
)
|
|
else:
|
|
raise TypeError("Unexpected value type")
|
|
|
|
prop_path = rna_idprop_quote_path(prop)
|
|
|
|
item.property_overridable_library_set(prop_path, overridable)
|
|
|
|
|
|
def draw(layout, context, context_member, property_type, *, use_edit=True):
|
|
rna_item, context_member = rna_idprop_context_value(context, context_member, property_type)
|
|
# poll should really get this...
|
|
if not rna_item:
|
|
return
|
|
|
|
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 = list(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="New", icon='ADD')
|
|
props.data_path = context_member
|
|
del row
|
|
layout.separator()
|
|
|
|
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_decorate = False
|
|
|
|
for key, value in items:
|
|
is_rna = (key in rna_properties)
|
|
|
|
# Only show API defined properties to developers.
|
|
if is_rna and not show_developer_ui:
|
|
continue
|
|
|
|
to_dict = getattr(value, "to_dict", None)
|
|
to_list = getattr(value, "to_list", None)
|
|
is_datablock = value is None or isinstance(value, bpy.types.ID)
|
|
|
|
if to_dict:
|
|
value = to_dict()
|
|
elif to_list:
|
|
value = to_list()
|
|
|
|
split = layout.split(factor=0.4, align=True)
|
|
label_row = split.row()
|
|
label_row.alignment = 'RIGHT'
|
|
label_row.label(text=key, translate=False)
|
|
|
|
value_row = split.row(align=True)
|
|
value_column = value_row.column(align=True)
|
|
|
|
is_long_array = to_list and len(value) >= MAX_DISPLAY_ROWS
|
|
|
|
if is_rna:
|
|
value_column.prop(rna_item, key, text="")
|
|
elif to_dict or is_long_array:
|
|
props = value_column.operator("wm.properties_edit_value", text="Edit Value")
|
|
props.data_path = context_member
|
|
props.property_name = key
|
|
elif is_datablock:
|
|
value_column.template_ID(rna_item, rna_idprop_quote_path(key), text="")
|
|
else:
|
|
value_column.prop(rna_item, rna_idprop_quote_path(key), text="")
|
|
|
|
operator_row = value_row.row(align=True)
|
|
operator_row.alignment = 'RIGHT'
|
|
|
|
# Do not allow editing of overridden properties (we cannot use a poll function
|
|
# of the operators here since they have no access to the specific property).
|
|
operator_row.enabled = not (is_lib_override and key in rna_item.id_data.override_library.reference)
|
|
|
|
if use_edit:
|
|
if is_rna:
|
|
operator_row.label(text="API Defined")
|
|
elif is_lib_override:
|
|
operator_row.active = False
|
|
operator_row.label(text="", icon='DECORATE_LIBRARY_OVERRIDE')
|
|
else:
|
|
props = operator_row.operator("wm.properties_edit", text="", icon='PREFERENCES', emboss=False)
|
|
props.data_path = context_member
|
|
props.property_name = key
|
|
props = operator_row.operator("wm.properties_remove", text="", icon='X', emboss=False)
|
|
props.data_path = context_member
|
|
props.property_name = key
|
|
|
|
|
|
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)
|