Cleanup: remove "Tweak" event type
[blender-addons.git] / space_view3d_modifier_tools.py
blob71aa7cf3efafe461a25d1f0fc5cd088da30a8e78
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 for mod in obj.modifiers[:]:
41 contx['modifier'] = mod
42 is_mod = True
43 try:
44 bpy.ops.object.modifier_apply(
45 contx,
46 modifier=contx['modifier'].name
48 except:
49 obj_name = getattr(obj, "name", "NO NAME")
50 collect_names.append(obj_name)
51 message_b = True
52 pass
54 if is_select:
55 if is_mod:
56 message_a = "Applying modifiers on all Selected Objects"
57 else:
58 message_a = "No Modifiers on Selected Objects"
59 else:
60 self.report({"INFO"}, "No Selection. No changes applied")
61 return {'CANCELLED'}
63 # applying failed for some objects, show report
64 message_obj = (",".join(collect_names) if collect_names and
65 len(collect_names) < 8 else "some objects (Check System Console)")
67 self.report({"INFO"},
68 (message_a if not message_b else
69 "Applying modifiers failed for {}".format(message_obj)))
71 if (collect_names and message_obj == "some objects (Check System Console)"):
72 print("\n[Modifier Tools]\n\nApplying failed on:"
73 "\n\n{} \n".format(", ".join(collect_names)))
75 return {'FINISHED'}
78 class DeleteAllModifiers(Operator):
79 bl_idname = "object.delete_all_modifiers"
80 bl_label = "Remove All Modifiers"
81 bl_description = "Remove All modifiers of the selected object(s)"
82 bl_options = {'REGISTER', 'UNDO'}
84 def invoke(self, context, event):
85 return context.window_manager.invoke_confirm(self, event)
87 def execute(self, context):
88 is_select, is_mod = False, False
89 message_a = ""
91 for obj in context.selected_objects:
92 is_select = True
93 modifiers = obj.modifiers[:]
94 for modi in modifiers:
95 is_mod = True
96 obj.modifiers.remove(modi)
98 if is_select:
99 if is_mod:
100 message_a = "Removing modifiers on all Selected Objects"
101 else:
102 message_a = "No Modifiers on Selected Objects"
103 else:
104 self.report({"INFO"}, "No Selection. No changes applied")
105 return {'CANCELLED'}
107 self.report({"INFO"}, message_a)
108 return {'FINISHED'}
111 class ToggleApplyModifiersView(Operator):
112 bl_idname = "object.toggle_apply_modifiers_view"
113 bl_label = "Toggle Visibility of Modifiers"
114 bl_description = "Shows/Hide modifiers of the active / selected object(s) in the 3D View"
115 bl_options = {'REGISTER'}
117 @classmethod
118 def poll(cls, context):
119 return context.active_object is not None
121 def execute(self, context):
122 is_apply = True
123 message_a = ""
125 # avoid toggling not exposed modifiers (currently only Collision, see T53406)
126 skip_type = ["COLLISION"] # types of modifiers to skip
127 skipped = set() # collect names
128 count_modifiers = 0 # check for message_a (all skipped)
130 # check if the active object has only one non exposed modifier as the logic will fail
131 if len(context.active_object.modifiers) == 1 and \
132 context.active_object.modifiers[0].type in skip_type:
134 for obj in context.selected_objects:
135 for mod in obj.modifiers:
136 if mod.type in skip_type:
137 skipped.add(mod.name)
138 continue
140 if mod.show_viewport:
141 is_apply = False
142 break
143 else:
144 for mod in context.active_object.modifiers:
145 if mod.type in skip_type:
146 skipped.add(mod.name)
147 continue
149 if mod.show_viewport:
150 is_apply = False
151 break
153 count_modifiers = len(context.active_object.modifiers)
154 # active object - no selection
155 for mod in context.active_object.modifiers:
156 if mod.type in skip_type:
157 count_modifiers -= 1
158 skipped.add(mod.name)
159 continue
161 mod.show_viewport = is_apply
163 for obj in context.selected_objects:
164 count_modifiers += len(obj.modifiers)
166 for mod in obj.modifiers:
167 if mod.type in skip_type:
168 skipped.add(mod.name)
169 count_modifiers -= 1
170 continue
172 mod.show_viewport = is_apply
174 message_a = "{} modifiers in the 3D View".format("Displaying" if is_apply else "Hiding")
176 if skipped:
177 message_a = "{}, {}".format(message_a, "skipping: " + ", ".join(skipped)) if \
178 count_modifiers > 0 else "No change of Modifiers' Visibility, all skipped"
180 self.report({"INFO"}, message_a)
182 return {'FINISHED'}
185 class ToggleAllShowExpanded(Operator):
186 bl_idname = "wm.toggle_all_show_expanded"
187 bl_label = "Expand/Collapse All Modifiers"
188 bl_description = "Expand/Collapse Modifier Stack"
189 bl_options = {'REGISTER'}
191 @classmethod
192 def poll(cls, context):
193 return context.active_object is not None
195 def execute(self, context):
196 obj = context.active_object
197 if (len(obj.modifiers)):
198 vs = 0
199 for mod in obj.modifiers:
200 if (mod.show_expanded):
201 vs += 1
202 else:
203 vs -= 1
204 is_close = False
205 if (0 < vs):
206 is_close = True
207 for mod in obj.modifiers:
208 mod.show_expanded = not is_close
209 else:
210 self.report({'WARNING'}, "Not a single modifier to Expand/Collapse")
211 return {'CANCELLED'}
213 for area in context.screen.areas:
214 area.tag_redraw()
215 return {'FINISHED'}
218 # Menus #
219 def menu(self, context):
220 if (context.active_object):
221 if (len(context.active_object.modifiers)):
222 col = self.layout.column(align=True)
224 row = col.row(align=True)
225 row.operator(ApplyAllModifiers.bl_idname,
226 icon='IMPORT', text="Apply All")
227 row.operator(DeleteAllModifiers.bl_idname,
228 icon='X', text="Delete All")
230 row = col.row(align=True)
231 row.operator(ToggleApplyModifiersView.bl_idname,
232 icon='RESTRICT_VIEW_OFF',
233 text="Viewport Vis")
234 row.operator(ToggleAllShowExpanded.bl_idname,
235 icon='FULLSCREEN_ENTER',
236 text="Toggle Stack")
239 def menu_func(self, context):
240 if (context.active_object):
241 if (len(context.active_object.modifiers)):
242 layout = self.layout
243 layout.separator()
244 layout.operator(ApplyAllModifiers.bl_idname,
245 icon='IMPORT',
246 text="Apply All Modifiers")
248 # Register
249 classes = [
250 ApplyAllModifiers,
251 DeleteAllModifiers,
252 ToggleApplyModifiersView,
253 ToggleAllShowExpanded,
256 def register():
257 from bpy.utils import register_class
258 for cls in classes:
259 register_class(cls)
261 # Add "Specials" menu to the "Modifiers" menu
262 bpy.types.DATA_PT_modifiers.prepend(menu)
264 # Add apply operator to the Apply 3D View Menu
265 bpy.types.VIEW3D_MT_object_apply.append(menu_func)
268 def unregister():
269 # Remove "Specials" menu from the "Modifiers" menu.
270 bpy.types.DATA_PT_modifiers.remove(menu)
272 # Remove apply operator to the Apply 3D View Menu
273 bpy.types.VIEW3D_MT_object_apply.remove(menu_func)
275 from bpy.utils import unregister_class
276 for cls in reversed(classes):
277 unregister_class(cls)
279 if __name__ == "__main__":
280 register()