1 # SPDX-License-Identifier: GPL-2.0-or-later
2 # by meta-androcto, saidenka.
5 "name": "Modifier Tools",
6 "author": "Meta Androcto, saidenka",
9 "location": "Properties > Modifiers",
10 "description": "Modifiers Specials Show/Hide/Apply Selected",
12 "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/modifier_tools.html",
13 "category": "Interface"
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
33 for obj
in bpy
.context
.selected_objects
:
36 # copying context for the operator's override
37 contx
= bpy
.context
.copy()
40 modifiers
= modifier_type(obj
)
42 for mod
in modifiers
[:]:
43 contx
['modifier'] = mod
46 bpy
.ops
.object.modifier_apply(
48 modifier
=contx
['modifier'].name
51 bpy
.ops
.object.gpencil_modifier_apply(
52 modifier
=contx
['modifier'].name
55 obj_name
= getattr(obj
, "name", "NO NAME")
56 collect_names
.append(obj_name
)
62 message_a
= "Applying modifiers on all Selected Objects"
64 message_a
= "No Modifiers on Selected Objects"
66 self
.report({"INFO"}, "No Selection. No changes applied")
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)")
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
)))
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
97 for obj
in context
.selected_objects
:
99 modifiers
= modifier_type(obj
)
101 for modi
in modifiers
[:]:
103 modifiers
.remove(modi
)
107 message_a
= "Removing modifiers on all Selected Objects"
109 message_a
= "No Modifiers on Selected Objects"
111 self
.report({"INFO"}, "No Selection. No changes applied")
114 self
.report({"INFO"}, message_a
)
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'}
125 def poll(cls
, context
):
126 return context
.active_object
is not None
128 def execute(self
, context
):
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
)
147 if mod
.type in skip_type
:
148 skipped
.add(mod
.name
)
151 if mod
.show_viewport
:
155 for mod
in modifiers
:
156 if mod
.type in skip_type
:
157 skipped
.add(mod
.name
)
160 if mod
.show_viewport
:
164 count_modifiers
= len(modifiers
)
165 # active object - no selection
166 for mod
in modifiers
:
167 if mod
.type in skip_type
:
169 skipped
.add(mod
.name
)
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
)
186 mod
.show_viewport
= is_apply
188 message_a
= "{} modifiers in the 3D View".format("Displaying" if is_apply
else "Hiding")
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
)
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'}
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
)
215 for mod
in modifiers
:
216 if (mod
.show_expanded
):
223 for mod
in modifiers
:
224 mod
.show_expanded
= not is_close
226 self
.report({'WARNING'}, "Not a single modifier to Expand/Collapse")
229 for area
in context
.screen
.areas
:
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',
250 row
.operator(ToggleAllShowExpanded
.bl_idname
,
251 icon
='FULLSCREEN_ENTER',
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
)):
260 layout
.operator(ApplyAllModifiers
.bl_idname
,
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
273 ToggleApplyModifiersView
,
274 ToggleAllShowExpanded
,
278 from bpy
.utils
import register_class
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
)
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__":