blender-addons/system_property_chart.py
2023-07-05 09:41:03 +02:00

307 lines
8.6 KiB
Python

# SPDX-License-Identifier: GPL-2.0-or-later
bl_info = {
"name": "Property Chart",
"author": "Campbell Barton (ideasman42)",
"version": (0, 1, 1),
"blender": (2, 80, 0),
"location": "View3D > Sidebar > Item Tab",
"description": ("Edit arbitrary selected properties for "
"objects/sequence strips of the same type"),
"warning": "",
"doc_url": "{BLENDER_MANUAL_URL}/addons/system/property_chart.html",
"category": "System",
}
"""List properties of selected objects"""
import bpy
from bl_operators.presets import AddPresetBase
from bpy.types import (
Menu,
Operator,
Panel,
)
class AddPresetProperties(AddPresetBase, Operator):
"""Add an properties preset"""
bl_idname = "scene.properties_preset_add"
bl_label = "Add Properties Preset"
preset_menu = "SCENE_MT_properties_presets"
preset_defines = [
"scene = bpy.context.scene",
]
def pre_cb(self, context):
space_type = context.space_data.type
if space_type == 'VIEW_3D':
self.preset_subdir = "system_property_chart_view3d"
self.preset_values = ["scene.view3d_edit_props"]
else:
self.preset_subdir = "system_property_chart_sequencer"
self.preset_values = ["scene.sequencer_edit_props"]
class SCENE_MT_properties_presets(Menu):
bl_label = "Properties Presets"
preset_operator = "script.execute_preset"
def draw(self, context):
space_type = context.space_data.type
if space_type == 'VIEW_3D':
self.preset_subdir = "system_property_chart_view3d"
else:
self.preset_subdir = "system_property_chart_sequencer"
Menu.draw_preset(self, context)
def _property_chart_data_get(self, context):
# eg. context.active_object
obj = eval("context.%s" % self.context_data_path_active)
if obj is None:
return None, None
# eg. context.selected_objects[:]
selected_objects = eval("context.%s" % self.context_data_path_selected)[:]
if not selected_objects:
return None, None
return obj, selected_objects
def _property_chart_draw(self, context):
"""
This function can run for different types.
"""
obj, selected_objects = _property_chart_data_get(self, context)
if not obj:
return
# active first
try:
active_index = selected_objects.index(obj)
except ValueError:
active_index = -1
if active_index > 0: # not the first already
selected_objects[0], selected_objects[active_index] = selected_objects[active_index], selected_objects[0]
id_storage = context.scene
strings = getattr(id_storage, self._PROP_STORAGE_ID)
# Collected all props, now display them all
layout = self.layout
if strings:
def obj_prop_get(obj, attr_string):
"""return a pair (rna_base, "rna_property") to give to the rna UI property function"""
attrs = attr_string.split(".")
val_new = obj
for i, attr in enumerate(attrs):
val_old = val_new
val_new = getattr(val_old, attr, Ellipsis)
if val_new == Ellipsis:
return None, None
return val_old, attrs[-1]
strings = strings.split()
prop_all = []
for obj in selected_objects:
prop_pairs = []
prop_found = False
for attr_string in strings:
prop_pairs.append(obj_prop_get(obj, attr_string))
if prop_found is False and prop_pairs[-1] != (None, None):
prop_found = True
if prop_found:
prop_all.append((obj, prop_pairs))
row = layout.row(align=True)
col = row.column(align=True)
col.label(text="name")
for obj, prop_pairs in prop_all:
col.prop(obj, "name", text="")
for i in range(len(strings)):
col = row.column(align=True)
# name and copy button
rowsub = col.row(align=False)
rowsub.label(text=strings[i].rsplit(".", 1)[-1])
props = rowsub.operator("wm.chart_copy", text="", icon='PASTEDOWN', emboss=False)
props.data_path_active = self.context_data_path_active
props.data_path_selected = self.context_data_path_selected
props.data_path = strings[i]
for obj, prop_pairs in prop_all:
data, attr = prop_pairs[i]
# row is needed for vector buttons
if data:
col.row().prop(data, attr, text="") # , emboss=obj==active_object
else:
col.label(text="<missing>")
# Presets for properties
col = layout.column()
col.label(text="Properties")
row = col.row(align=True)
row.menu("SCENE_MT_properties_presets", text=bpy.types.SCENE_MT_properties_presets.bl_label)
row.operator("scene.properties_preset_add", text="", icon='ADD')
row.operator("scene.properties_preset_add", text="", icon='REMOVE').remove_active = True
# edit the display props
col.prop(id_storage, self._PROP_STORAGE_ID, text="")
class View3DEditProps(Panel):
bl_idname = "SYSPROP_CHART_PT_view3d"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Item"
bl_label = "Property Chart"
bl_context = "objectmode"
bl_options = {'DEFAULT_CLOSED'}
_PROP_STORAGE_ID = "view3d_edit_props"
_PROP_STORAGE_ID_DESCR = "Properties of objects in the context"
_PROP_STORAGE_DEFAULT = "data data.use_auto_smooth"
# _property_chart_draw needs these
context_data_path_active = "active_object"
context_data_path_selected = "selected_objects"
draw = _property_chart_draw
class SequencerEditProps(Panel):
bl_idname = "SYSPROP_CHART_PT_sequencer"
bl_space_type = 'SEQUENCE_EDITOR'
bl_region_type = 'UI'
bl_category = "Item"
bl_label = "Property Chart"
bl_options = {'DEFAULT_CLOSED'}
_PROP_STORAGE_ID = "sequencer_edit_props"
_PROP_STORAGE_ID_DESCR = "Properties of sequencer strips in the context"
_PROP_STORAGE_DEFAULT = "blend_type blend_alpha"
# _property_chart_draw needs these
context_data_path_active = "scene.sequence_editor.active_strip"
context_data_path_selected = "selected_sequences"
draw = _property_chart_draw
@classmethod
def poll(cls, context):
return context.scene.sequence_editor is not None
# Operator to copy properties
def _property_chart_copy(self, context):
obj, selected_objects = _property_chart_data_get(self, context)
if not obj:
return
data_path = self.data_path
data_path_with_dot = data_path
if not data_path_with_dot.startswith("["):
data_path_with_dot = "." + data_path_with_dot
code = compile(
"obj_iter%s = obj%s" % (data_path_with_dot, data_path_with_dot),
"<property_chart>",
'exec',
)
for obj_iter in selected_objects:
if obj != obj_iter:
try:
exec(code, {}, {"obj": obj, "obj_iter": obj_iter})
except:
# just in case we need to know what went wrong!
import traceback
traceback.print_exc()
from bpy.props import StringProperty
class CopyPropertyChart(Operator):
"Open a path in a file browser"
bl_idname = "wm.chart_copy"
bl_label = "Copy properties from active to selected"
data_path_active: StringProperty()
data_path_selected: StringProperty()
data_path: StringProperty()
def execute(self, context):
# so attributes are found for '_property_chart_data_get()'
self.context_data_path_active = self.data_path_active
self.context_data_path_selected = self.data_path_selected
_property_chart_copy(self, context)
return {'FINISHED'}
# List The Classes #
classes = (
AddPresetProperties,
SCENE_MT_properties_presets,
View3DEditProps,
SequencerEditProps,
CopyPropertyChart
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
Scene = bpy.types.Scene
for cls in View3DEditProps, SequencerEditProps:
setattr(
Scene,
cls._PROP_STORAGE_ID,
StringProperty(
name="Properties",
description=cls._PROP_STORAGE_ID_DESCR + " (separated by spaces)",
default=cls._PROP_STORAGE_DEFAULT, maxlen=1024,
),
)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
Scene = bpy.types.Scene
for cls in View3DEditProps, SequencerEditProps:
delattr(Scene,
cls._PROP_STORAGE_ID,
)
if __name__ == "__main__":
register()