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 for mod
in obj
.modifiers
[:]:
41 contx
['modifier'] = mod
44 bpy
.ops
.object.modifier_apply(
46 modifier
=contx
['modifier'].name
49 obj_name
= getattr(obj
, "name", "NO NAME")
50 collect_names
.append(obj_name
)
56 message_a
= "Applying modifiers on all Selected Objects"
58 message_a
= "No Modifiers on Selected Objects"
60 self
.report({"INFO"}, "No Selection. No changes applied")
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)")
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
)))
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
91 for obj
in context
.selected_objects
:
93 modifiers
= obj
.modifiers
[:]
94 for modi
in modifiers
:
96 obj
.modifiers
.remove(modi
)
100 message_a
= "Removing modifiers on all Selected Objects"
102 message_a
= "No Modifiers on Selected Objects"
104 self
.report({"INFO"}, "No Selection. No changes applied")
107 self
.report({"INFO"}, message_a
)
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'}
118 def poll(cls
, context
):
119 return context
.active_object
is not None
121 def execute(self
, context
):
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
)
140 if mod
.show_viewport
:
144 for mod
in context
.active_object
.modifiers
:
145 if mod
.type in skip_type
:
146 skipped
.add(mod
.name
)
149 if mod
.show_viewport
:
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
:
158 skipped
.add(mod
.name
)
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
)
172 mod
.show_viewport
= is_apply
174 message_a
= "{} modifiers in the 3D View".format("Displaying" if is_apply
else "Hiding")
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
)
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'}
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
)):
199 for mod
in obj
.modifiers
:
200 if (mod
.show_expanded
):
207 for mod
in obj
.modifiers
:
208 mod
.show_expanded
= not is_close
210 self
.report({'WARNING'}, "Not a single modifier to Expand/Collapse")
213 for area
in context
.screen
.areas
:
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',
234 row
.operator(ToggleAllShowExpanded
.bl_idname
,
235 icon
='FULLSCREEN_ENTER',
239 def menu_func(self
, context
):
240 if (context
.active_object
):
241 if (len(context
.active_object
.modifiers
)):
244 layout
.operator(ApplyAllModifiers
.bl_idname
,
246 text
="Apply All Modifiers")
252 ToggleApplyModifiersView
,
253 ToggleAllShowExpanded
,
257 from bpy
.utils
import register_class
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
)
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__":