diff --git a/release/scripts/modules/rna_prop_ui.py b/release/scripts/modules/rna_prop_ui.py index 2cc806be10d..fce59a26c38 100644 --- a/release/scripts/modules/rna_prop_ui.py +++ b/release/scripts/modules/rna_prop_ui.py @@ -28,7 +28,7 @@ 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 = 4 +MAX_DISPLAY_ROWS = 8 def rna_idprop_quote_path(prop): @@ -134,18 +134,7 @@ def rna_idprop_ui_create( def draw(layout, context, context_member, property_type, *, use_edit=True): - - def assign_props(prop, value, key): - prop.data_path = context_member - prop.property_name = key - - try: - prop.value = str(value) - 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 @@ -164,17 +153,15 @@ def draw(layout, context, context_member, property_type, *, use_edit=True): # 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 = 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_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) + layout.use_property_decorate = False for key, value in items: is_rna = (key in rna_properties) @@ -188,57 +175,50 @@ def draw(layout, context, context_member, property_type, *, use_edit=True): if to_dict: value = to_dict() - val_draw = str(value) elif to_list: value = to_list() - val_draw = str(value) - else: - val_draw = value - row = layout.row(align=True) - box = row.box() + 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 + else: + value_column.prop(rna_item, '["%s"]' % escape_identifier(key), text="") + + operator_row = value_row.row() + + # 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). + operator_row.enabled = not(is_lib_override and key in rna_item.id_data.override_library.reference) if use_edit: - split = box.split(factor=0.75) - row = split.row() - else: - split = box.split(factor=1.00) - row = split.row() - - 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(value) <= MAX_DISPLAY_ROWS - - if show_array_ui and isinstance(value[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 is_rna: - row.label(text="API Defined") + operator_row.label(text="API Defined") elif is_lib_override: - row.label(text="Library Override") + operator_row.active = False + operator_row.label(text="", icon='DECORATE_LIBRARY_OVERRIDE') else: - 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) - - del flow - + 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 + else: + # Add some spacing, so the right side of the buttons line up with layouts with decorators. + operator_row.label(text="", icon='BLANK1') class PropertyPanel: """ diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py index 170b9f3ae44..4dbdc6e633e 100644 --- a/release/scripts/startup/bl_operators/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -1442,7 +1442,7 @@ class WM_OT_properties_edit(Operator): # Convert an old property for a string, avoiding unhelpful string representations for custom list types. @staticmethod - def _convert_old_property_to_string(item, name): + def convert_custom_property_to_string(item, name): # The IDProperty group view API currently doesn't have a "lookup" method. for key, value in item.items(): if key == name: @@ -1461,7 +1461,8 @@ class WM_OT_properties_edit(Operator): # Retrieve the current type of the custom property on the RNA struct. Some properties like group properties # can be created in the UI, but editing their meta-data isn't supported. In that case, return 'PYTHON'. - def _get_property_type(self, item, property_name): + @staticmethod + def get_property_type(item, property_name): from rna_prop_ui import ( rna_idprop_value_item_type, ) @@ -1549,17 +1550,17 @@ class WM_OT_properties_edit(Operator): return self._convert_new_value_single(item[name_old], float) if prop_type_new == 'INT_ARRAY': - prop_type_old = self._get_property_type(item, name_old) + prop_type_old = self.get_property_type(item, name_old) if prop_type_old in {'INT', 'FLOAT', 'INT_ARRAY', 'FLOAT_ARRAY'}: return self._convert_new_value_array(item[name_old], int, self.array_length) if prop_type_new == 'FLOAT_ARRAY': - prop_type_old = self._get_property_type(item, name_old) + prop_type_old = self.get_property_type(item, name_old) if prop_type_old in {'INT', 'FLOAT', 'FLOAT_ARRAY', 'INT_ARRAY'}: return self._convert_new_value_array(item[name_old], float, self.array_length) if prop_type_new == 'STRING': - return self._convert_old_property_to_string(item, name_old) + return self.convert_custom_property_to_string(item, name_old) # If all else fails, create an empty string property. That should avoid errors later on anyway. return "" @@ -1672,7 +1673,7 @@ class WM_OT_properties_edit(Operator): self.report({'ERROR'}, "Cannot edit properties from override data") return {'CANCELLED'} - prop_type_old = self._get_property_type(item, name_old) + prop_type_old = self.get_property_type(item, name_old) prop_type_new = self.property_type self._old_prop_name[:] = [name] @@ -1716,14 +1717,14 @@ class WM_OT_properties_edit(Operator): return {'CANCELLED'} # Set operator's property type with the type of the existing property, to display the right settings. - old_type = self._get_property_type(item, name) + old_type = self.get_property_type(item, name) self.property_type = old_type self.last_property_type = old_type # So that the operator can do something for unsupported properties, change the property into # a string, just for editing in the dialog. When the operator executes, it will be converted back # into a python value. Always do this conversion, in case the Python property edit type is selected. - self.eval_string = self._convert_old_property_to_string(item, name) + self.eval_string = self.convert_custom_property_to_string(item, name) if old_type != 'PYTHON': self._fill_old_ui_data(item, name) @@ -1845,6 +1846,62 @@ class WM_OT_properties_edit(Operator): layout.prop(self, "description") +# Edit the value of a custom property with the given name on the RNA struct at the given data path. +# For supported types, this simply acts as a convenient way to create a popup for a specific property +# and draws the custom property value directly in the popup. For types like groups which can't be edited +# directly with buttons, instead convert the value to a string, evaluate the changed string when executing. +class WM_OT_properties_edit_value(Operator): + """Edit the value of a custom property""" + bl_idname = "wm.properties_edit_value" + bl_label = "Edit Property Value" + # register only because invoke_props_popup requires. + bl_options = {'REGISTER', 'INTERNAL'} + + data_path: rna_path + property_name: rna_custom_property_name + + # Store the value converted to a string as a fallback for otherwise unsupported types. + eval_string: StringProperty( + name="Value", + description="Value for custom property types that can only be edited as a Python expression" + ) + + def execute(self, context): + if self.eval_string: + rna_item = eval("context.%s" % self.data_path) + try: + new_value = eval(self.eval_string) + except Exception as ex: + self.report({'WARNING'}, "Python evaluation failed: " + str(ex)) + return {'CANCELLED'} + rna_item[self.property_name] = new_value + return {'FINISHED'} + + def invoke(self, context, _event): + rna_item = eval("context.%s" % self.data_path) + + if WM_OT_properties_edit.get_property_type(rna_item, self.property_name) == 'PYTHON': + self.eval_string = WM_OT_properties_edit.convert_custom_property_to_string(rna_item, + self.property_name) + else: + self.eval_string = "" + + wm = context.window_manager + return wm.invoke_props_dialog(self) + + def draw(self, context): + from bpy.utils import escape_identifier + + rna_item = eval("context.%s" % self.data_path) + + layout = self.layout + if WM_OT_properties_edit.get_property_type(rna_item, self.property_name) == 'PYTHON': + layout.prop(self, "eval_string") + else: + col = layout.column(align=True) + col.prop(rna_item, '["%s"]' % escape_identifier(self.property_name), text="") + + class WM_OT_properties_add(Operator): """Add your own property to the data-block""" bl_idname = "wm.properties_add" @@ -3056,6 +3113,7 @@ classes = ( WM_OT_properties_add, WM_OT_properties_context_change, WM_OT_properties_edit, + WM_OT_properties_edit_value, WM_OT_properties_remove, WM_OT_sysinfo, WM_OT_owner_disable,