1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
18 # by meta-androcto, saidenka #
21 "name": "Modifier Tools",
22 "author": "Meta Androcto, saidenka",
24 "blender": (2, 80, 0),
25 "location": "Properties > Modifiers",
26 "description": "Modifiers Specials Show/Hide/Apply Selected",
28 "doc_url": "{BLENDER_MANUAL_URL}/addons/interface/modifier_tools.html",
29 "category": "Interface"
33 from bpy
.types
import Operator
36 class ApplyAllModifiers(Operator
):
37 bl_idname
= "object.apply_all_modifiers"
38 bl_label
= "Apply All Modifiers"
39 bl_description
= ("Apply All modifiers of the selected object(s) \n"
40 "Active object has to have modifiers for the option to show up")
41 bl_options
= {'REGISTER', 'UNDO'}
43 def execute(self
, context
):
44 is_select
, is_mod
= False, False
45 message_a
, message_b
= "", ""
46 # collect names for objects failed to apply modifiers
49 for obj
in bpy
.context
.selected_objects
:
52 # copying context for the operator's override
53 contx
= bpy
.context
.copy()
56 for mod
in obj
.modifiers
[:]:
57 contx
['modifier'] = mod
60 bpy
.ops
.object.modifier_apply(
61 contx
, apply_as
='DATA',
62 modifier
=contx
['modifier'].name
65 obj_name
= getattr(obj
, "name", "NO NAME")
66 collect_names
.append(obj_name
)
72 message_a
= "Applying modifiers on all Selected Objects"
74 message_a
= "No Modifiers on Selected Objects"
76 self
.report({"INFO"}, "No Selection. No changes applied")
79 # applying failed for some objects, show report
80 message_obj
= (",".join(collect_names
) if collect_names
and
81 len(collect_names
) < 8 else "some objects (Check System Console)")
84 (message_a
if not message_b
else
85 "Applying modifiers failed for {}".format(message_obj
)))
87 if (collect_names
and message_obj
== "some objects (Check System Console)"):
88 print("\n[Modifier Tools]\n\nApplying failed on:"
89 "\n\n{} \n".format(", ".join(collect_names
)))
94 class DeleteAllModifiers(Operator
):
95 bl_idname
= "object.delete_all_modifiers"
96 bl_label
= "Remove All Modifiers"
97 bl_description
= "Remove All modifiers of the selected object(s)"
98 bl_options
= {'REGISTER', 'UNDO'}
100 def invoke(self
, context
, event
):
101 return context
.window_manager
.invoke_confirm(self
, event
)
103 def execute(self
, context
):
104 is_select
, is_mod
= False, False
107 for obj
in context
.selected_objects
:
109 modifiers
= obj
.modifiers
[:]
110 for modi
in modifiers
:
112 obj
.modifiers
.remove(modi
)
116 message_a
= "Removing modifiers on all Selected Objects"
118 message_a
= "No Modifiers on Selected Objects"
120 self
.report({"INFO"}, "No Selection. No changes applied")
123 self
.report({"INFO"}, message_a
)
127 class ToggleApplyModifiersView(Operator
):
128 bl_idname
= "object.toggle_apply_modifiers_view"
129 bl_label
= "Toggle Visibility of Modifiers"
130 bl_description
= "Shows/Hide modifiers of the active / selected object(s) in the 3D View"
131 bl_options
= {'REGISTER'}
134 def poll(cls
, context
):
135 return context
.active_object
is not None
137 def execute(self
, context
):
141 # avoid toggling not exposed modifiers (currently only Collision, see T53406)
142 skip_type
= ["COLLISION"] # types of modifiers to skip
143 skipped
= set() # collect names
144 count_modifiers
= 0 # check for message_a (all skipped)
146 # check if the active object has only one non exposed modifier as the logic will fail
147 if len(context
.active_object
.modifiers
) == 1 and \
148 context
.active_object
.modifiers
[0].type in skip_type
:
150 for obj
in context
.selected_objects
:
151 for mod
in obj
.modifiers
:
152 if mod
.type in skip_type
:
153 skipped
.add(mod
.name
)
156 if mod
.show_viewport
:
160 for mod
in context
.active_object
.modifiers
:
161 if mod
.type in skip_type
:
162 skipped
.add(mod
.name
)
165 if mod
.show_viewport
:
169 count_modifiers
= len(context
.active_object
.modifiers
)
170 # active object - no selection
171 for mod
in context
.active_object
.modifiers
:
172 if mod
.type in skip_type
:
174 skipped
.add(mod
.name
)
177 mod
.show_viewport
= is_apply
179 for obj
in context
.selected_objects
:
180 count_modifiers
+= len(obj
.modifiers
)
182 for mod
in obj
.modifiers
:
183 if mod
.type in skip_type
:
184 skipped
.add(mod
.name
)
188 mod
.show_viewport
= is_apply
190 message_a
= "{} modifiers in the 3D View".format("Displaying" if is_apply
else "Hiding")
193 message_a
= "{}, {}".format(message_a
, "skipping: " + ", ".join(skipped
)) if \
194 count_modifiers
> 0 else "No change of Modifiers' Visibility, all skipped"
196 self
.report({"INFO"}, message_a
)
201 class ToggleAllShowExpanded(Operator
):
202 bl_idname
= "wm.toggle_all_show_expanded"
203 bl_label
= "Expand/Collapse All Modifiers"
204 bl_description
= "Expand/Collapse Modifier Stack"
205 bl_options
= {'REGISTER'}
208 def poll(cls
, context
):
209 return context
.active_object
is not None
211 def execute(self
, context
):
212 obj
= context
.active_object
213 if (len(obj
.modifiers
)):
215 for mod
in obj
.modifiers
:
216 if (mod
.show_expanded
):
223 for mod
in obj
.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
)):
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
)):
260 layout
.operator(ApplyAllModifiers
.bl_idname
,
262 text
="Apply All Modifiers")
268 ToggleApplyModifiersView
,
269 ToggleAllShowExpanded
,
273 from bpy
.utils
import register_class
277 # Add "Specials" menu to the "Modifiers" menu
278 bpy
.types
.DATA_PT_modifiers
.prepend(menu
)
280 # Add apply operator to the Apply 3D View Menu
281 bpy
.types
.VIEW3D_MT_object_apply
.append(menu_func
)
285 # Remove "Specials" menu from the "Modifiers" menu.
286 bpy
.types
.DATA_PT_modifiers
.remove(menu
)
288 # Remove apply operator to the Apply 3D View Menu
289 bpy
.types
.VIEW3D_MT_object_apply
.remove(menu_func
)
291 from bpy
.utils
import unregister_class
292 for cls
in reversed(classes
):
293 unregister_class(cls
)
295 if __name__
== "__main__":