Fix #104714: Missing shape keys in FBX export when original mesh data cannot be used
[blender-addons.git] / space_view3d_modifier_tools.py
blob5dffb55e06c7f648418671faa885df54214a4f59
1 # SPDX-FileCopyrightText: 2016-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "Modifier Tools",
7 "author": "Meta Androcto, saidenka",
8 "version": (0, 2, 6),
9 "blender": (2, 80, 0),
10 "location": "Properties > Modifiers",
11 "description": "Modifiers Specials Show/Hide/Apply Selected",
12 "warning": "",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/modifier_tools.html",
14 "category": "Interface"
17 import bpy
18 from bpy.types import Operator
21 class ApplyAllModifiers(Operator):
22 bl_idname = "object.apply_all_modifiers"
23 bl_label = "Apply All Modifiers"
24 bl_description = ("Apply All modifiers of the selected object(s) \n"
25 "Active object has to have modifiers for the option to show up")
26 bl_options = {'REGISTER', 'UNDO'}
28 def execute(self, context):
29 is_select, is_mod = False, False
30 message_a, message_b = "", ""
31 # collect names for objects failed to apply modifiers
32 collect_names = []
34 for obj in bpy.context.selected_objects:
35 is_select = True
37 # copying context for the operator's override
38 context_override = {'object': obj}
40 modifiers = modifier_type(obj)
42 for mod in modifiers[:]:
43 context_override['modifier'] = mod
44 is_mod = True
45 try:
46 with bpy.context.temp_override(**context_override):
47 if obj.type != 'GPENCIL':
48 bpy.ops.object.modifier_apply(modifier=mod.name)
49 else:
50 bpy.ops.object.gpencil_modifier_apply(modifier=mod.name)
51 except:
52 obj_name = getattr(obj, "name", "NO NAME")
53 collect_names.append(obj_name)
54 message_b = True
55 pass
57 if is_select:
58 if is_mod:
59 message_a = "Applying modifiers on all Selected Objects"
60 else:
61 message_a = "No Modifiers on Selected Objects"
62 else:
63 self.report({"INFO"}, "No Selection. No changes applied")
64 return {'CANCELLED'}
66 # applying failed for some objects, show report
67 message_obj = (",".join(collect_names) if collect_names and
68 len(collect_names) < 8 else "some objects (Check System Console)")
70 self.report({"INFO"},
71 (message_a if not message_b else
72 "Applying modifiers failed for {}".format(message_obj)))
74 if (collect_names and message_obj == "some objects (Check System Console)"):
75 print("\n[Modifier Tools]\n\nApplying failed on:"
76 "\n\n{} \n".format(", ".join(collect_names)))
78 return {'FINISHED'}
81 class DeleteAllModifiers(Operator):
82 bl_idname = "object.delete_all_modifiers"
83 bl_label = "Remove All Modifiers"
84 bl_description = "Remove All modifiers of the selected object(s)"
85 bl_options = {'REGISTER', 'UNDO'}
87 def invoke(self, context, event):
88 return context.window_manager.invoke_confirm(self, event)
90 def execute(self, context):
91 is_select, is_mod = False, False
92 message_a = ""
94 for obj in context.selected_objects:
95 is_select = True
96 modifiers = modifier_type(obj)
98 for modi in modifiers[:]:
99 is_mod = True
100 modifiers.remove(modi)
102 if is_select:
103 if is_mod:
104 message_a = "Removing modifiers on all Selected Objects"
105 else:
106 message_a = "No Modifiers on Selected Objects"
107 else:
108 self.report({"INFO"}, "No Selection. No changes applied")
109 return {'CANCELLED'}
111 self.report({"INFO"}, message_a)
112 return {'FINISHED'}
115 class ToggleApplyModifiersView(Operator):
116 bl_idname = "object.toggle_apply_modifiers_view"
117 bl_label = "Toggle Visibility of Modifiers"
118 bl_description = "Shows/Hide modifiers of the active / selected object(s) in the 3D View"
119 bl_options = {'REGISTER'}
121 @classmethod
122 def poll(cls, context):
123 return context.active_object is not None
125 def execute(self, context):
126 is_apply = True
127 message_a = ""
129 # avoid toggling not exposed modifiers (currently only Collision, see T53406)
130 skip_type = ["COLLISION"] # types of modifiers to skip
131 skipped = set() # collect names
132 count_modifiers = 0 # check for message_a (all skipped)
134 modifiers = modifier_type(context.active_object)
136 # check if the active object has only one non exposed modifier as the logic will fail
137 if len(modifiers) == 1 and \
138 modifiers[0].type in skip_type:
140 for obj in context.selected_objects:
141 mod_sel = modifier_type(obj)
143 for mod in mod_sel:
144 if mod.type in skip_type:
145 skipped.add(mod.name)
146 continue
148 if mod.show_viewport:
149 is_apply = False
150 break
151 else:
152 for mod in modifiers:
153 if mod.type in skip_type:
154 skipped.add(mod.name)
155 continue
157 if mod.show_viewport:
158 is_apply = False
159 break
161 count_modifiers = len(modifiers)
162 # active object - no selection
163 for mod in modifiers:
164 if mod.type in skip_type:
165 count_modifiers -= 1
166 skipped.add(mod.name)
167 continue
169 mod.show_viewport = is_apply
171 for obj in context.selected_objects:
173 modifiers = modifier_type(obj)
175 count_modifiers += len(modifiers)
177 for mod in modifiers:
178 if mod.type in skip_type:
179 skipped.add(mod.name)
180 count_modifiers -= 1
181 continue
183 mod.show_viewport = is_apply
185 message_a = "{} modifiers in the 3D View".format("Displaying" if is_apply else "Hiding")
187 if skipped:
188 message_a = "{}, {}".format(message_a, "skipping: " + ", ".join(skipped)) if \
189 count_modifiers > 0 else "No change of Modifiers' Visibility, all skipped"
191 self.report({"INFO"}, message_a)
193 return {'FINISHED'}
196 class ToggleAllShowExpanded(Operator):
197 bl_idname = "wm.toggle_all_show_expanded"
198 bl_label = "Expand/Collapse All Modifiers"
199 bl_description = "Expand/Collapse Modifier Stack"
200 bl_options = {'REGISTER'}
202 @classmethod
203 def poll(cls, context):
204 return context.active_object is not None
206 def execute(self, context):
207 obj = context.active_object
208 modifiers = modifier_type(obj)
210 if (len(modifiers)):
211 vs = 0
212 for mod in modifiers:
213 if (mod.show_expanded):
214 vs += 1
215 else:
216 vs -= 1
217 is_close = False
218 if (0 < vs):
219 is_close = True
220 for mod in modifiers:
221 mod.show_expanded = not is_close
222 else:
223 self.report({'WARNING'}, "Not a single modifier to Expand/Collapse")
224 return {'CANCELLED'}
226 for area in context.screen.areas:
227 area.tag_redraw()
228 return {'FINISHED'}
231 # Menus #
232 def menu(self, context):
233 if (context.active_object):
234 if (len(context.active_object.modifiers) or len(context.active_object.grease_pencil_modifiers)):
235 col = self.layout.column(align=True)
237 row = col.row(align=True)
238 row.operator(ApplyAllModifiers.bl_idname,
239 icon='IMPORT', text="Apply All")
240 row.operator(DeleteAllModifiers.bl_idname,
241 icon='X', text="Delete All")
243 row = col.row(align=True)
244 row.operator(ToggleApplyModifiersView.bl_idname,
245 icon='RESTRICT_VIEW_OFF',
246 text="Viewport Vis")
247 row.operator(ToggleAllShowExpanded.bl_idname,
248 icon='FULLSCREEN_ENTER',
249 text="Toggle Stack")
252 def menu_func(self, context):
253 if (context.active_object):
254 if (len(context.active_object.modifiers) or len(context.active_object.grease_pencil_modifiers)):
255 layout = self.layout
256 layout.separator()
257 layout.operator(ApplyAllModifiers.bl_idname,
258 icon='IMPORT',
259 text="Apply All Modifiers")
261 def modifier_type(object):
262 if object.type == 'GPENCIL':
263 return object.grease_pencil_modifiers
264 return object.modifiers
266 # Register
267 classes = [
268 ApplyAllModifiers,
269 DeleteAllModifiers,
270 ToggleApplyModifiersView,
271 ToggleAllShowExpanded,
274 def register():
275 from bpy.utils import register_class
276 for cls in classes:
277 register_class(cls)
279 # Add "Specials" menu to the "Modifiers" menu
280 bpy.types.DATA_PT_modifiers.prepend(menu)
282 bpy.types.DATA_PT_gpencil_modifiers.prepend(menu)
284 # Add apply operator to the Apply 3D View Menu
285 bpy.types.VIEW3D_MT_object_apply.append(menu_func)
288 def unregister():
289 # Remove "Specials" menu from the "Modifiers" menu.
290 bpy.types.DATA_PT_modifiers.remove(menu)
292 bpy.types.DATA_PT_gpencil_modifiers.remove(menu)
294 # Remove apply operator to the Apply 3D View Menu
295 bpy.types.VIEW3D_MT_object_apply.remove(menu_func)
297 from bpy.utils import unregister_class
298 for cls in reversed(classes):
299 unregister_class(cls)
301 if __name__ == "__main__":
302 register()