Merge branch 'blender-v3.3-release'
[blender-addons.git] / system_property_chart.py
blobbb6d4682151bcd7fd56e3b6b1d78f913fe0c8603
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "Property Chart",
5 "author": "Campbell Barton (ideasman42)",
6 "version": (0, 1, 1),
7 "blender": (2, 80, 0),
8 "location": "View3D > Sidebar > Item Tab",
9 "description": ("Edit arbitrary selected properties for "
10 "objects/sequence strips of the same type"),
11 "warning": "",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/system/property_chart.html",
13 "category": "System",
16 """List properties of selected objects"""
18 import bpy
19 from bl_operators.presets import AddPresetBase
20 from bpy.types import (
21 Menu,
22 Operator,
23 Panel,
27 class AddPresetProperties(AddPresetBase, Operator):
28 """Add an properties preset"""
29 bl_idname = "scene.properties_preset_add"
30 bl_label = "Add Properties Preset"
31 preset_menu = "SCENE_MT_properties_presets"
33 preset_defines = [
34 "scene = bpy.context.scene",
37 def pre_cb(self, context):
38 space_type = context.space_data.type
39 if space_type == 'VIEW_3D':
40 self.preset_subdir = "system_property_chart_view3d"
41 self.preset_values = ["scene.view3d_edit_props"]
42 else:
43 self.preset_subdir = "system_property_chart_sequencer"
44 self.preset_values = ["scene.sequencer_edit_props"]
47 class SCENE_MT_properties_presets(Menu):
48 bl_label = "Properties Presets"
49 preset_operator = "script.execute_preset"
51 def draw(self, context):
52 space_type = context.space_data.type
54 if space_type == 'VIEW_3D':
55 self.preset_subdir = "system_property_chart_view3d"
56 else:
57 self.preset_subdir = "system_property_chart_sequencer"
59 Menu.draw_preset(self, context)
62 def _property_chart_data_get(self, context):
63 # eg. context.active_object
64 obj = eval("context.%s" % self.context_data_path_active)
66 if obj is None:
67 return None, None
69 # eg. context.selected_objects[:]
70 selected_objects = eval("context.%s" % self.context_data_path_selected)[:]
72 if not selected_objects:
73 return None, None
75 return obj, selected_objects
78 def _property_chart_draw(self, context):
79 """
80 This function can run for different types.
81 """
82 obj, selected_objects = _property_chart_data_get(self, context)
84 if not obj:
85 return
87 # active first
88 try:
89 active_index = selected_objects.index(obj)
90 except ValueError:
91 active_index = -1
93 if active_index > 0: # not the first already
94 selected_objects[0], selected_objects[active_index] = selected_objects[active_index], selected_objects[0]
96 id_storage = context.scene
98 strings = getattr(id_storage, self._PROP_STORAGE_ID)
100 # Collected all props, now display them all
101 layout = self.layout
103 if strings:
105 def obj_prop_get(obj, attr_string):
106 """return a pair (rna_base, "rna_property") to give to the rna UI property function"""
107 attrs = attr_string.split(".")
108 val_new = obj
109 for i, attr in enumerate(attrs):
110 val_old = val_new
111 val_new = getattr(val_old, attr, Ellipsis)
113 if val_new == Ellipsis:
114 return None, None
115 return val_old, attrs[-1]
117 strings = strings.split()
119 prop_all = []
121 for obj in selected_objects:
122 prop_pairs = []
123 prop_found = False
124 for attr_string in strings:
125 prop_pairs.append(obj_prop_get(obj, attr_string))
126 if prop_found is False and prop_pairs[-1] != (None, None):
127 prop_found = True
129 if prop_found:
130 prop_all.append((obj, prop_pairs))
132 row = layout.row(align=True)
134 col = row.column(align=True)
135 col.label(text="name")
136 for obj, prop_pairs in prop_all:
137 col.prop(obj, "name", text="")
139 for i in range(len(strings)):
140 col = row.column(align=True)
142 # name and copy button
143 rowsub = col.row(align=False)
144 rowsub.label(text=strings[i].rsplit(".", 1)[-1])
145 props = rowsub.operator("wm.chart_copy", text="", icon='PASTEDOWN', emboss=False)
146 props.data_path_active = self.context_data_path_active
147 props.data_path_selected = self.context_data_path_selected
148 props.data_path = strings[i]
150 for obj, prop_pairs in prop_all:
151 data, attr = prop_pairs[i]
152 # row is needed for vector buttons
153 if data:
154 col.row().prop(data, attr, text="") # , emboss=obj==active_object
155 else:
156 col.label(text="<missing>")
158 # Presets for properties
159 col = layout.column()
160 col.label(text="Properties")
161 row = col.row(align=True)
162 row.menu("SCENE_MT_properties_presets", text=bpy.types.SCENE_MT_properties_presets.bl_label)
163 row.operator("scene.properties_preset_add", text="", icon='ADD')
164 row.operator("scene.properties_preset_add", text="", icon='REMOVE').remove_active = True
165 # edit the display props
166 col.prop(id_storage, self._PROP_STORAGE_ID, text="")
169 class View3DEditProps(Panel):
170 bl_idname = "SYSPROP_CHART_PT_view3d"
171 bl_space_type = 'VIEW_3D'
172 bl_region_type = 'UI'
173 bl_category = "Item"
174 bl_label = "Property Chart"
175 bl_context = "objectmode"
176 bl_options = {'DEFAULT_CLOSED'}
178 _PROP_STORAGE_ID = "view3d_edit_props"
179 _PROP_STORAGE_ID_DESCR = "Properties of objects in the context"
180 _PROP_STORAGE_DEFAULT = "data data.use_auto_smooth"
182 # _property_chart_draw needs these
183 context_data_path_active = "active_object"
184 context_data_path_selected = "selected_objects"
186 draw = _property_chart_draw
189 class SequencerEditProps(Panel):
190 bl_idname = "SYSPROP_CHART_PT_sequencer"
191 bl_space_type = 'SEQUENCE_EDITOR'
192 bl_region_type = 'UI'
193 bl_category = "Item"
194 bl_label = "Property Chart"
195 bl_options = {'DEFAULT_CLOSED'}
197 _PROP_STORAGE_ID = "sequencer_edit_props"
198 _PROP_STORAGE_ID_DESCR = "Properties of sequencer strips in the context"
199 _PROP_STORAGE_DEFAULT = "blend_type blend_alpha"
201 # _property_chart_draw needs these
202 context_data_path_active = "scene.sequence_editor.active_strip"
203 context_data_path_selected = "selected_sequences"
205 draw = _property_chart_draw
207 @classmethod
208 def poll(cls, context):
209 return context.scene.sequence_editor is not None
211 # Operator to copy properties
214 def _property_chart_copy(self, context):
215 obj, selected_objects = _property_chart_data_get(self, context)
217 if not obj:
218 return
220 data_path = self.data_path
222 data_path_with_dot = data_path
223 if not data_path_with_dot.startswith("["):
224 data_path_with_dot = "." + data_path_with_dot
226 code = compile(
227 "obj_iter%s = obj%s" % (data_path_with_dot, data_path_with_dot),
228 "<property_chart>",
229 'exec',
232 for obj_iter in selected_objects:
233 if obj != obj_iter:
234 try:
235 exec(code, {}, {"obj": obj, "obj_iter": obj_iter})
236 except:
237 # just in case we need to know what went wrong!
238 import traceback
239 traceback.print_exc()
242 from bpy.props import StringProperty
245 class CopyPropertyChart(Operator):
246 "Open a path in a file browser"
247 bl_idname = "wm.chart_copy"
248 bl_label = "Copy properties from active to selected"
250 data_path_active: StringProperty()
251 data_path_selected: StringProperty()
252 data_path: StringProperty()
254 def execute(self, context):
255 # so attributes are found for '_property_chart_data_get()'
256 self.context_data_path_active = self.data_path_active
257 self.context_data_path_selected = self.data_path_selected
259 _property_chart_copy(self, context)
261 return {'FINISHED'}
263 # List The Classes #
266 classes = (
267 AddPresetProperties,
268 SCENE_MT_properties_presets,
269 View3DEditProps,
270 SequencerEditProps,
271 CopyPropertyChart
275 def register():
276 for cls in classes:
277 bpy.utils.register_class(cls)
279 Scene = bpy.types.Scene
281 for cls in View3DEditProps, SequencerEditProps:
282 setattr(
283 Scene,
284 cls._PROP_STORAGE_ID,
285 StringProperty(
286 name="Properties",
287 description=cls._PROP_STORAGE_ID_DESCR + " (separated by spaces)",
288 default=cls._PROP_STORAGE_DEFAULT, maxlen=1024,
293 def unregister():
294 for cls in classes:
295 bpy.utils.unregister_class(cls)
297 Scene = bpy.types.Scene
299 for cls in View3DEditProps, SequencerEditProps:
300 delattr(Scene,
301 cls._PROP_STORAGE_ID,
305 if __name__ == "__main__":
306 register()