Update scripts to account for removal of the context override to bpy.ops
[blender-addons.git] / space_view3d_modifier_tools.py
blobef312072953c313538cb205e4e1d57668e1bf718
1 # SPDX-License-Identifier: GPL-2.0-or-later
2 # by meta-androcto, saidenka.
4 bl_info = {
5 "name": "Modifier Tools",
6 "author": "Meta Androcto, saidenka",
7 "version": (0, 2, 6),
8 "blender": (2, 80, 0),
9 "location": "Properties > Modifiers",
10 "description": "Modifiers Specials Show/Hide/Apply Selected",
11 "warning": "",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/modifier_tools.html",
13 "category": "Interface"
16 import bpy
17 from bpy.types import Operator
20 class ApplyAllModifiers(Operator):
21 bl_idname = "object.apply_all_modifiers"
22 bl_label = "Apply All Modifiers"
23 bl_description = ("Apply All modifiers of the selected object(s) \n"
24 "Active object has to have modifiers for the option to show up")
25 bl_options = {'REGISTER', 'UNDO'}
27 def execute(self, context):
28 is_select, is_mod = False, False
29 message_a, message_b = "", ""
30 # collect names for objects failed to apply modifiers
31 collect_names = []
33 for obj in bpy.context.selected_objects:
34 is_select = True
36 # copying context for the operator's override
37 contx = bpy.context.copy()
38 contx['object'] = obj
40 modifiers = modifier_type(obj)
42 for mod in modifiers[:]:
43 contx['modifier'] = mod
44 is_mod = True
45 try:
46 bpy.ops.object.modifier_apply(
47 contx,
48 modifier=contx['modifier'].name
51 bpy.ops.object.gpencil_modifier_apply(
52 modifier=contx['modifier'].name
54 except:
55 obj_name = getattr(obj, "name", "NO NAME")
56 collect_names.append(obj_name)
57 message_b = True
58 pass
60 if is_select:
61 if is_mod:
62 message_a = "Applying modifiers on all Selected Objects"
63 else:
64 message_a = "No Modifiers on Selected Objects"
65 else:
66 self.report({"INFO"}, "No Selection. No changes applied")
67 return {'CANCELLED'}
69 # applying failed for some objects, show report
70 message_obj = (",".join(collect_names) if collect_names and
71 len(collect_names) < 8 else "some objects (Check System Console)")
73 self.report({"INFO"},
74 (message_a if not message_b else
75 "Applying modifiers failed for {}".format(message_obj)))
77 if (collect_names and message_obj == "some objects (Check System Console)"):
78 print("\n[Modifier Tools]\n\nApplying failed on:"
79 "\n\n{} \n".format(", ".join(collect_names)))
81 return {'FINISHED'}
84 class DeleteAllModifiers(Operator):
85 bl_idname = "object.delete_all_modifiers"
86 bl_label = "Remove All Modifiers"
87 bl_description = "Remove All modifiers of the selected object(s)"
88 bl_options = {'REGISTER', 'UNDO'}
90 def invoke(self, context, event):
91 return context.window_manager.invoke_confirm(self, event)
93 def execute(self, context):
94 is_select, is_mod = False, False
95 message_a = ""
97 for obj in context.selected_objects:
98 is_select = True
99 modifiers = modifier_type(obj)
101 for modi in modifiers[:]:
102 is_mod = True
103 modifiers.remove(modi)
105 if is_select:
106 if is_mod:
107 message_a = "Removing modifiers on all Selected Objects"
108 else:
109 message_a = "No Modifiers on Selected Objects"
110 else:
111 self.report({"INFO"}, "No Selection. No changes applied")
112 return {'CANCELLED'}
114 self.report({"INFO"}, message_a)
115 return {'FINISHED'}
118 class ToggleApplyModifiersView(Operator):
119 bl_idname = "object.toggle_apply_modifiers_view"
120 bl_label = "Toggle Visibility of Modifiers"
121 bl_description = "Shows/Hide modifiers of the active / selected object(s) in the 3D View"
122 bl_options = {'REGISTER'}
124 @classmethod
125 def poll(cls, context):
126 return context.active_object is not None
128 def execute(self, context):
129 is_apply = True
130 message_a = ""
132 # avoid toggling not exposed modifiers (currently only Collision, see T53406)
133 skip_type = ["COLLISION"] # types of modifiers to skip
134 skipped = set() # collect names
135 count_modifiers = 0 # check for message_a (all skipped)
137 modifiers = modifier_type(context.active_object)
139 # check if the active object has only one non exposed modifier as the logic will fail
140 if len(modifiers) == 1 and \
141 modifiers[0].type in skip_type:
143 for obj in context.selected_objects:
144 mod_sel = modifier_type(obj)
146 for mod in mod_sel:
147 if mod.type in skip_type:
148 skipped.add(mod.name)
149 continue
151 if mod.show_viewport:
152 is_apply = False
153 break
154 else:
155 for mod in modifiers:
156 if mod.type in skip_type:
157 skipped.add(mod.name)
158 continue
160 if mod.show_viewport:
161 is_apply = False
162 break
164 count_modifiers = len(modifiers)
165 # active object - no selection
166 for mod in modifiers:
167 if mod.type in skip_type:
168 count_modifiers -= 1
169 skipped.add(mod.name)
170 continue
172 mod.show_viewport = is_apply
174 for obj in context.selected_objects:
176 modifiers = modifier_type(obj)
178 count_modifiers += len(modifiers)
180 for mod in modifiers:
181 if mod.type in skip_type:
182 skipped.add(mod.name)
183 count_modifiers -= 1
184 continue
186 mod.show_viewport = is_apply
188 message_a = "{} modifiers in the 3D View".format("Displaying" if is_apply else "Hiding")
190 if skipped:
191 message_a = "{}, {}".format(message_a, "skipping: " + ", ".join(skipped)) if \
192 count_modifiers > 0 else "No change of Modifiers' Visibility, all skipped"
194 self.report({"INFO"}, message_a)
196 return {'FINISHED'}
199 class ToggleAllShowExpanded(Operator):
200 bl_idname = "wm.toggle_all_show_expanded"
201 bl_label = "Expand/Collapse All Modifiers"
202 bl_description = "Expand/Collapse Modifier Stack"
203 bl_options = {'REGISTER'}
205 @classmethod
206 def poll(cls, context):
207 return context.active_object is not None
209 def execute(self, context):
210 obj = context.active_object
211 modifiers = modifier_type(obj)
213 if (len(modifiers)):
214 vs = 0
215 for mod in modifiers:
216 if (mod.show_expanded):
217 vs += 1
218 else:
219 vs -= 1
220 is_close = False
221 if (0 < vs):
222 is_close = True
223 for mod in modifiers:
224 mod.show_expanded = not is_close
225 else:
226 self.report({'WARNING'}, "Not a single modifier to Expand/Collapse")
227 return {'CANCELLED'}
229 for area in context.screen.areas:
230 area.tag_redraw()
231 return {'FINISHED'}
234 # Menus #
235 def menu(self, context):
236 if (context.active_object):
237 if (len(context.active_object.modifiers) or len(context.active_object.grease_pencil_modifiers)):
238 col = self.layout.column(align=True)
240 row = col.row(align=True)
241 row.operator(ApplyAllModifiers.bl_idname,
242 icon='IMPORT', text="Apply All")
243 row.operator(DeleteAllModifiers.bl_idname,
244 icon='X', text="Delete All")
246 row = col.row(align=True)
247 row.operator(ToggleApplyModifiersView.bl_idname,
248 icon='RESTRICT_VIEW_OFF',
249 text="Viewport Vis")
250 row.operator(ToggleAllShowExpanded.bl_idname,
251 icon='FULLSCREEN_ENTER',
252 text="Toggle Stack")
255 def menu_func(self, context):
256 if (context.active_object):
257 if (len(context.active_object.modifiers) or len(context.active_object.grease_pencil_modifiers)):
258 layout = self.layout
259 layout.separator()
260 layout.operator(ApplyAllModifiers.bl_idname,
261 icon='IMPORT',
262 text="Apply All Modifiers")
264 def modifier_type(object):
265 if object.type == 'GPENCIL':
266 return object.grease_pencil_modifiers
267 return object.modifiers
269 # Register
270 classes = [
271 ApplyAllModifiers,
272 DeleteAllModifiers,
273 ToggleApplyModifiersView,
274 ToggleAllShowExpanded,
277 def register():
278 from bpy.utils import register_class
279 for cls in classes:
280 register_class(cls)
282 # Add "Specials" menu to the "Modifiers" menu
283 bpy.types.DATA_PT_modifiers.prepend(menu)
285 bpy.types.DATA_PT_gpencil_modifiers.prepend(menu)
287 # Add apply operator to the Apply 3D View Menu
288 bpy.types.VIEW3D_MT_object_apply.append(menu_func)
291 def unregister():
292 # Remove "Specials" menu from the "Modifiers" menu.
293 bpy.types.DATA_PT_modifiers.remove(menu)
295 bpy.types.DATA_PT_gpencil_modifiers.remove(menu)
297 # Remove apply operator to the Apply 3D View Menu
298 bpy.types.VIEW3D_MT_object_apply.remove(menu_func)
300 from bpy.utils import unregister_class
301 for cls in reversed(classes):
302 unregister_class(cls)
304 if __name__ == "__main__":
305 register()