Merge branch 'blender-v4.0-release'
[blender-addons.git] / system_property_chart.py
blob3d208f779d064bfb2c817ea57651a371dc090576
1 # SPDX-FileCopyrightText: 2010-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "Property Chart",
7 "author": "Campbell Barton (ideasman42)",
8 "version": (0, 1, 1),
9 "blender": (2, 80, 0),
10 "location": "View3D > Sidebar > Item Tab",
11 "description": ("Edit arbitrary selected properties for "
12 "objects/sequence strips of the same type"),
13 "warning": "",
14 "doc_url": "{BLENDER_MANUAL_URL}/addons/system/property_chart.html",
15 "category": "System",
18 """List properties of selected objects"""
20 import bpy
21 from bl_operators.presets import AddPresetBase
22 from bpy.types import (
23 Menu,
24 Operator,
25 Panel,
29 class AddPresetProperties(AddPresetBase, Operator):
30 """Add an properties preset"""
31 bl_idname = "scene.properties_preset_add"
32 bl_label = "Add Properties Preset"
33 preset_menu = "SCENE_MT_properties_presets"
35 preset_defines = [
36 "scene = bpy.context.scene",
39 def pre_cb(self, context):
40 space_type = context.space_data.type
41 if space_type == 'VIEW_3D':
42 self.preset_subdir = "system_property_chart_view3d"
43 self.preset_values = ["scene.view3d_edit_props"]
44 else:
45 self.preset_subdir = "system_property_chart_sequencer"
46 self.preset_values = ["scene.sequencer_edit_props"]
49 class SCENE_MT_properties_presets(Menu):
50 bl_label = "Properties Presets"
51 preset_operator = "script.execute_preset"
53 def draw(self, context):
54 space_type = context.space_data.type
56 if space_type == 'VIEW_3D':
57 self.preset_subdir = "system_property_chart_view3d"
58 else:
59 self.preset_subdir = "system_property_chart_sequencer"
61 Menu.draw_preset(self, context)
64 def _property_chart_data_get(self, context):
65 # eg. context.active_object
66 obj = eval("context.%s" % self.context_data_path_active)
68 if obj is None:
69 return None, None
71 # eg. context.selected_objects[:]
72 selected_objects = eval("context.%s" % self.context_data_path_selected)[:]
74 if not selected_objects:
75 return None, None
77 return obj, selected_objects
80 def _property_chart_draw(self, context):
81 """
82 This function can run for different types.
83 """
84 obj, selected_objects = _property_chart_data_get(self, context)
86 if not obj:
87 return
89 # active first
90 try:
91 active_index = selected_objects.index(obj)
92 except ValueError:
93 active_index = -1
95 if active_index > 0: # not the first already
96 selected_objects[0], selected_objects[active_index] = selected_objects[active_index], selected_objects[0]
98 id_storage = context.scene
100 strings = getattr(id_storage, self._PROP_STORAGE_ID)
102 # Collected all props, now display them all
103 layout = self.layout
105 if strings:
107 def obj_prop_get(obj, attr_string):
108 """return a pair (rna_base, "rna_property") to give to the rna UI property function"""
109 attrs = attr_string.split(".")
110 val_new = obj
111 for i, attr in enumerate(attrs):
112 val_old = val_new
113 val_new = getattr(val_old, attr, Ellipsis)
115 if val_new == Ellipsis:
116 return None, None
117 return val_old, attrs[-1]
119 strings = strings.split()
121 prop_all = []
123 for obj in selected_objects:
124 prop_pairs = []
125 prop_found = False
126 for attr_string in strings:
127 prop_pairs.append(obj_prop_get(obj, attr_string))
128 if prop_found is False and prop_pairs[-1] != (None, None):
129 prop_found = True
131 if prop_found:
132 prop_all.append((obj, prop_pairs))
134 row = layout.row(align=True)
136 col = row.column(align=True)
137 col.label(text="name")
138 for obj, prop_pairs in prop_all:
139 col.prop(obj, "name", text="")
141 for i in range(len(strings)):
142 col = row.column(align=True)
144 # name and copy button
145 rowsub = col.row(align=False)
146 rowsub.label(text=strings[i].rsplit(".", 1)[-1])
147 props = rowsub.operator("wm.chart_copy", text="", icon='PASTEDOWN', emboss=False)
148 props.data_path_active = self.context_data_path_active
149 props.data_path_selected = self.context_data_path_selected
150 props.data_path = strings[i]
152 for obj, prop_pairs in prop_all:
153 data, attr = prop_pairs[i]
154 # row is needed for vector buttons
155 if data:
156 col.row().prop(data, attr, text="") # , emboss=obj==active_object
157 else:
158 col.label(text="<missing>")
160 # Presets for properties
161 col = layout.column()
162 col.label(text="Properties")
163 row = col.row(align=True)
164 row.menu("SCENE_MT_properties_presets", text=bpy.types.SCENE_MT_properties_presets.bl_label)
165 row.operator("scene.properties_preset_add", text="", icon='ADD')
166 row.operator("scene.properties_preset_add", text="", icon='REMOVE').remove_active = True
167 # edit the display props
168 col.prop(id_storage, self._PROP_STORAGE_ID, text="")
171 class View3DEditProps(Panel):
172 bl_idname = "SYSPROP_CHART_PT_view3d"
173 bl_space_type = 'VIEW_3D'
174 bl_region_type = 'UI'
175 bl_category = "Item"
176 bl_label = "Property Chart"
177 bl_context = "objectmode"
178 bl_options = {'DEFAULT_CLOSED'}
180 _PROP_STORAGE_ID = "view3d_edit_props"
181 _PROP_STORAGE_ID_DESCR = "Properties of objects in the context"
182 _PROP_STORAGE_DEFAULT = "data data.use_auto_smooth"
184 # _property_chart_draw needs these
185 context_data_path_active = "active_object"
186 context_data_path_selected = "selected_objects"
188 draw = _property_chart_draw
191 class SequencerEditProps(Panel):
192 bl_idname = "SYSPROP_CHART_PT_sequencer"
193 bl_space_type = 'SEQUENCE_EDITOR'
194 bl_region_type = 'UI'
195 bl_category = "Item"
196 bl_label = "Property Chart"
197 bl_options = {'DEFAULT_CLOSED'}
199 _PROP_STORAGE_ID = "sequencer_edit_props"
200 _PROP_STORAGE_ID_DESCR = "Properties of sequencer strips in the context"
201 _PROP_STORAGE_DEFAULT = "blend_type blend_alpha"
203 # _property_chart_draw needs these
204 context_data_path_active = "scene.sequence_editor.active_strip"
205 context_data_path_selected = "selected_sequences"
207 draw = _property_chart_draw
209 @classmethod
210 def poll(cls, context):
211 return context.scene.sequence_editor is not None
213 # Operator to copy properties
216 def _property_chart_copy(self, context):
217 obj, selected_objects = _property_chart_data_get(self, context)
219 if not obj:
220 return
222 data_path = self.data_path
224 data_path_with_dot = data_path
225 if not data_path_with_dot.startswith("["):
226 data_path_with_dot = "." + data_path_with_dot
228 code = compile(
229 "obj_iter%s = obj%s" % (data_path_with_dot, data_path_with_dot),
230 "<property_chart>",
231 'exec',
234 for obj_iter in selected_objects:
235 if obj != obj_iter:
236 try:
237 exec(code, {}, {"obj": obj, "obj_iter": obj_iter})
238 except:
239 # just in case we need to know what went wrong!
240 import traceback
241 traceback.print_exc()
244 from bpy.props import StringProperty
247 class CopyPropertyChart(Operator):
248 "Open a path in a file browser"
249 bl_idname = "wm.chart_copy"
250 bl_label = "Copy properties from active to selected"
252 data_path_active: StringProperty()
253 data_path_selected: StringProperty()
254 data_path: StringProperty()
256 def execute(self, context):
257 # so attributes are found for '_property_chart_data_get()'
258 self.context_data_path_active = self.data_path_active
259 self.context_data_path_selected = self.data_path_selected
261 _property_chart_copy(self, context)
263 return {'FINISHED'}
265 # List The Classes #
268 classes = (
269 AddPresetProperties,
270 SCENE_MT_properties_presets,
271 View3DEditProps,
272 SequencerEditProps,
273 CopyPropertyChart
277 def register():
278 for cls in classes:
279 bpy.utils.register_class(cls)
281 Scene = bpy.types.Scene
283 for cls in View3DEditProps, SequencerEditProps:
284 setattr(
285 Scene,
286 cls._PROP_STORAGE_ID,
287 StringProperty(
288 name="Properties",
289 description=cls._PROP_STORAGE_ID_DESCR + " (separated by spaces)",
290 default=cls._PROP_STORAGE_DEFAULT, maxlen=1024,
295 def unregister():
296 for cls in classes:
297 bpy.utils.unregister_class(cls)
299 Scene = bpy.types.Scene
301 for cls in View3DEditProps, SequencerEditProps:
302 delattr(Scene,
303 cls._PROP_STORAGE_ID,
307 if __name__ == "__main__":
308 register()