1 # SPDX-FileCopyrightText: 2016-2023 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
6 "name": "Modifier Tools",
7 "author": "Meta Androcto, saidenka",
10 "location": "Properties > Modifiers",
11 "description": "Modifiers Specials Show/Hide/Apply Selected",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/modifier_tools.html",
14 "category": "Interface"
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
34 for obj
in bpy
.context
.selected_objects
:
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
46 with bpy
.context
.temp_override(**context_override
):
47 if obj
.type != 'GPENCIL':
48 bpy
.ops
.object.modifier_apply(modifier
=mod
.name
)
50 bpy
.ops
.object.gpencil_modifier_apply(modifier
=mod
.name
)
52 obj_name
= getattr(obj
, "name", "NO NAME")
53 collect_names
.append(obj_name
)
59 message_a
= "Applying modifiers on all Selected Objects"
61 message_a
= "No Modifiers on Selected Objects"
63 self
.report({"INFO"}, "No Selection. No changes applied")
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)")
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
)))
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
94 for obj
in context
.selected_objects
:
96 modifiers
= modifier_type(obj
)
98 for modi
in modifiers
[:]:
100 modifiers
.remove(modi
)
104 message_a
= "Removing modifiers on all Selected Objects"
106 message_a
= "No Modifiers on Selected Objects"
108 self
.report({"INFO"}, "No Selection. No changes applied")
111 self
.report({"INFO"}, message_a
)
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'}
122 def poll(cls
, context
):
123 return context
.active_object
is not None
125 def execute(self
, context
):
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
)
144 if mod
.type in skip_type
:
145 skipped
.add(mod
.name
)
148 if mod
.show_viewport
:
152 for mod
in modifiers
:
153 if mod
.type in skip_type
:
154 skipped
.add(mod
.name
)
157 if mod
.show_viewport
:
161 count_modifiers
= len(modifiers
)
162 # active object - no selection
163 for mod
in modifiers
:
164 if mod
.type in skip_type
:
166 skipped
.add(mod
.name
)
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
)
183 mod
.show_viewport
= is_apply
185 message_a
= "{} modifiers in the 3D View".format("Displaying" if is_apply
else "Hiding")
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
)
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'}
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
)
212 for mod
in modifiers
:
213 if (mod
.show_expanded
):
220 for mod
in modifiers
:
221 mod
.show_expanded
= not is_close
223 self
.report({'WARNING'}, "Not a single modifier to Expand/Collapse")
226 for area
in context
.screen
.areas
:
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',
247 row
.operator(ToggleAllShowExpanded
.bl_idname
,
248 icon
='FULLSCREEN_ENTER',
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
)):
257 layout
.operator(ApplyAllModifiers
.bl_idname
,
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
270 ToggleApplyModifiersView
,
271 ToggleAllShowExpanded
,
275 from bpy
.utils
import register_class
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
)
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__":