diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2019-08-17 03:19:02 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2019-08-17 03:19:02 +0300 |
commit | 4fa93f6eb1b63c2662190336d685358c34209cb2 (patch) | |
tree | fe7897ecac117a6c3e2e97c48fdd8085438a26f4 /materials_utils | |
parent | 6c882f7d817ad092f72d53b37daefc8622e9e74e (diff) |
materials_utils: add search and preferences T67990
Diffstat (limited to 'materials_utils')
-rw-r--r-- | materials_utils/__init__.py | 65 | ||||
-rw-r--r-- | materials_utils/enum_values.py | 24 | ||||
-rw-r--r-- | materials_utils/functions.py | 102 | ||||
-rw-r--r-- | materials_utils/menus.py | 94 | ||||
-rw-r--r-- | materials_utils/operators.py | 249 | ||||
-rw-r--r-- | materials_utils/preferences.py | 118 |
6 files changed, 587 insertions, 65 deletions
diff --git a/materials_utils/__init__.py b/materials_utils/__init__.py index 40f976d7..2a4561f4 100644 --- a/materials_utils/__init__.py +++ b/materials_utils/__init__.py @@ -1,3 +1,19 @@ +# Material Utilities v2.2.0-Beta +# +# Usage: Shift + Q in the 3D viewport +# +# Ported from 2.6/2.7 to 2.8x by +# Christopher Hindefjord (chrishinde) 2019 +# +# ## Port based on 2010 version by MichaelW with some code added from latest 2.7x version +# ## Same code may be attributed to one of the following awesome people! +# (c) 2016 meta-androcto, parts based on work by Saidenka, lijenstina +# Materials Utils: by MichaleW, lijenstina, +# (some code thanks to: CoDEmanX, SynaGl0w, ideasman42) +# Link to base names: Sybren, Texture renamer: Yadoob +# ### +# +# # ##### BEGIN GPL LICENSE BLOCK ##### # # This program is free software; you can redistribute it and/or @@ -15,22 +31,15 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### - -# Based on 2010 version by MichaelW -# (c) 2016 meta-androcto, parts based on work by Saidenka, lijenstina -# Materials Utils: by MichaleW, meta-androcto, lijenstina, -# (some code thanks to: CoDEmanX, SynaGl0w, ideasman42) -# Link to base names: Sybren, Texture renamer: Yadoob -# Ported from 2.6/2.7 to 2.8x by Christopher Hindefjord (chrishinde) 2019 bl_info = { - "name": "Material Utils", + "name": "Material Utilities", "author": "MichaleW, ChrisHinde", - "version": (1, 0, 6), + "version": (2, 2, 0), "blender": (2, 80, 0), "location": "View3D > Shift + Q key", "description": "Menu of material tools (assign, select..) in the 3D View", - "warning": "", + "warning": "Beta", "wiki_url": "https://github.com/ChrisHinde/MaterialUtilities", "category": "Material" } @@ -78,13 +87,34 @@ This script has several functions and operators, grouped for convenience: """ +if "bpy" in locals(): + import importlib + if "enum_values" in locals(): + importlib.reload(enum_values) + if "functions" in locals(): + importlib.reload(functions) + if "operators" in locals(): + importlib.reload(operators) + if "menues" in locals(): + importlib.reload(menus) + if "preferences" in locals(): + importlib.reload(preferences) +else: + from .enum_values import * + from .functions import * + from .operators import * + from .menus import * + from .preferences import * import bpy +from bpy.props import ( + PointerProperty, + ) +from bpy.types import ( + AddonPreferences, + PropertyGroup, + ) -from .enum_values import * -from .functions import * -from .operators import * -from .menus import * # All classes used by Material Utilities, that need to be registred classes = ( @@ -102,6 +132,8 @@ classes = ( VIEW3D_OT_materialutilities_change_material_link, MATERIAL_OT_materialutilities_merge_base_names, + MATERIAL_OT_materialutilities_join_objects, + MATERIAL_OT_materialutilities_auto_smooth_angle, MATERIAL_OT_materialutilities_material_slot_move, @@ -112,12 +144,13 @@ classes = ( VIEW3D_MT_materialutilities_specials, VIEW3D_MT_materialutilities_main, + + VIEW3D_MT_materialutilities_preferences ) # This allows you to right click on a button and link to the manual def materialutilities_manual_map(): - print("ManMap") url_manual_prefix = "https://github.com/ChrisHinde/MaterialUtilities" url_manual_map = [] #url_manual_mapping = () @@ -156,7 +189,6 @@ def register(): def unregister(): """Unregister the classes of Material Utilities together with the default shortcut for the menu""" - mu_classes_unregister() bpy.utils.unregister_manual_map(materialutilities_manual_map) @@ -174,6 +206,7 @@ def unregister(): km.keymap_items.remove(kmi) break + mu_classes_unregister() if __name__ == "__main__": register() diff --git a/materials_utils/enum_values.py b/materials_utils/enum_values.py index f7f8f65b..2f257400 100644 --- a/materials_utils/enum_values.py +++ b/materials_utils/enum_values.py @@ -4,21 +4,33 @@ import bpy mu_override_type_enums = [ ('OVERRIDE_ALL', "Override all assigned slots", "Remove any current material slots, and assign the current material"), + ('OVERRIDE_CURRENT', 'Assign material to currently selected slot', + 'Only assign the material to the material slot that\'s currently selected'), ('OVERRIDE_SLOTS', 'Assign material to each slot', 'Keep the material slots, but assign the selected material in each slot'), ('APPEND_MATERIAL', 'Append Material', 'Add the material in a new slot, and assign it to the whole object') ] +mu_clean_slots_enums = (('ACTIVE', "Active object", "Materials of active object only"), + ('SELECTED', "Selected objects", "Materials of selected objects"), + ('SCENE', "Scene objects", "Materials of objects in current scene"), + ('ALL', "All", "All materials in this blend file")) + +mu_affect_enums = (('ACTIVE', "Active object", "Affect the active object only"), + ('SELECTED', "Selected objects", "Affect all selected objects"), + ('SCENE', "Scene objects", "Affect all objects in the current scene"), + ('ALL', "All", "All objects in this blend file")) + mu_fake_user_set_enums = (('ON', "On", "Enable fake user"), ('OFF', "Off", "Disable fake user"), ('TOGGLE', "Toggle", "Toggle fake user")) -mu_fake_user_materials_enums = (('ACTIVE', "Active object", "Materials of active object only"), - ('SELECTED', "Selected objects", "Materials of selected objects"), - ('SCENE', "Scene objects", "Materials of objects in current scene"), - ('USED', "Used", "All materials used by objects"), - ('UNUSED', "Unused", "Currently unused materials"), - ('ALL', "All", "All materials in this blend file")) +mu_fake_user_affect_enums = (('ACTIVE', "Active object", "Materials of active object only"), + ('SELECTED', "Selected objects", "Materials of selected objects"), + ('SCENE', "Scene objects", "Materials of objects in current scene"), + ('USED', "Used", "All materials used by objects"), + ('UNUSED', "Unused", "Currently unused materials"), + ('ALL', "All", "All materials in this blend file")) mu_link_to_enums = (('DATA', "Data", "Link the materials to the data"), ('OBJECT', "Object", "Link the materials to the object"), diff --git a/materials_utils/functions.py b/materials_utils/functions.py index 6c8c72bd..b8afc04f 100644 --- a/materials_utils/functions.py +++ b/materials_utils/functions.py @@ -1,4 +1,5 @@ import bpy +from math import radians, degrees # ----------------------------------------------------------------------------- # utility functions @@ -54,6 +55,19 @@ def mu_assign_to_data(object, material, index, edit_mode, all = True): bpy.ops.object.mode_set(mode = 'OBJECT') def mu_new_material_name(material): + for mat in bpy.data.materials: + name = mat.name + + if (name == material): + try: + base, suffix = name.rsplit('.', 1) + + # trigger the exception + num = int(suffix, 10) + material = base + "." + '%03d' % (num + 1) + except ValueError: + material = material + ".001" + return material @@ -139,6 +153,15 @@ def mu_assign_material(self, material_name = "Default", override_type = 'APPEND_ obj.material_slots[i].material = target i += 1 + elif override_type == 'OVERRIDE_CURRENT': + active_slot = obj.active_material_index + + if len(obj.material_slots) == 0: + self.report({'INFO'}, 'No material slots found! A material slot was added!') + bpy.ops.object.material_slot_add() + + obj.material_slots[active_slot].material = target + # if we should keep the material slots and just append the selected material (if not already assigned) elif override_type == 'APPEND_MATERIAL': found = False @@ -184,7 +207,7 @@ def mu_assign_material(self, material_name = "Default", override_type = 'APPEND_ return {'FINISHED'} -def mu_select_by_material_name(self, find_material_name, extend_selection = False): +def mu_select_by_material_name(self, find_material_name, extend_selection = False, internal = False): """Searches through all objects, or the polygons/curves of the current object to find and select objects/data with the desired material""" @@ -195,7 +218,7 @@ def mu_select_by_material_name(self, find_material_name, extend_selection = Fals if find_material is None: self.report({'INFO'}, "The material " + find_material_name + " doesn't exists!") - return {'CANCELLED'} + return {'CANCELLED'} if not internal else -1 # check for edit_mode edit_mode = False @@ -236,9 +259,10 @@ def mu_select_by_material_name(self, find_material_name, extend_selection = Fals obj.select_set(state=False) if not found_material: - self.report({'INFO'}, "No objects found with the material " + - find_material_name + "!") - return {'FINISHED'} + if not internal: + self.report({'INFO'}, "No objects found with the material " + + find_material_name + "!") + return {'FINISHED'} if not internal else 0 else: # it's edit_mode, so select the polygons @@ -253,7 +277,6 @@ def mu_select_by_material_name(self, find_material_name, extend_selection = Fals objects = bpy.context.selected_editable_objects for obj in objects: - print("Obj:" + obj.name) bpy.context.view_layer.objects.active = obj if obj.type == 'MESH': @@ -305,7 +328,7 @@ def mu_select_by_material_name(self, find_material_name, extend_selection = Fals i += 1 - else: + elif not internal: # Some object types are not supported # mostly because don't really support selecting by material (like Font/Text objects) # ore that they don't support multiple materials/are just "weird" (i.e. Meta balls) @@ -316,10 +339,10 @@ def mu_select_by_material_name(self, find_material_name, extend_selection = Fals bpy.context.view_layer.objects.active = active_object - if not found_material: + if (not found_material) and (not internal): self.report({'INFO'}, "Material " + find_material_name + " isn't assigned to anything!") - return {'FINISHED'} + return {'FINISHED'} if not internal else 1 def mu_copy_material_to_others(self): @@ -334,7 +357,7 @@ def mu_copy_material_to_others(self): return {'FINISHED'} -def mu_cleanmatslots(self): +def mu_cleanmatslots(self, affect): """Clean the material slots of the seleceted objects""" # check for edit mode @@ -344,7 +367,16 @@ def mu_cleanmatslots(self): edit_mode = True bpy.ops.object.mode_set() - objects = bpy.context.selected_editable_objects + objects = [] + + if affect == 'ACTIVE': + objects = [active_object] + elif affect == 'SELECTED': + objects = bpy.context.selected_editable_objects + elif affect == 'SCENE': + objects = bpy.context.scene.objects + else: # affect == 'ALL' + objects = bpy.data.objects for obj in objects: used_mat_index = [] # we'll store used materials indices here @@ -602,3 +634,51 @@ def mu_change_material_link(self, link, affect, override_data_material = False): index = index + 1 return {'FINISHED'} + +def mu_join_objects(self, materials): + """Join objects together based on their material""" + + for material in materials: + mu_select_by_material_name(self, material, False, True) + + bpy.ops.object.join() + + return {'FINISHED'} + +def mu_set_auto_smooth(self, angle, affect, set_smooth_shading): + """Set Auto smooth values for selected objects""" + # Inspired by colkai + + objects = [] + objects_affected = 0 + + if affect == "ACTIVE": + objects = [bpy.context.active_object] + elif affect == "SELECTED": + objects = bpy.context.selected_editable_objects + elif affect == "SCENE": + objects = bpy.context.scene.objects + elif affect == "ALL": + objects = bpy.data.objects + + if len(objects) == 0: + self.report({'WARNING'}, 'No objects available to set Auto Smooth on') + return {'CANCELLED'} + + for object in objects: + if object.type == "MESH": + if set_smooth_shading: + for poly in object.data.polygons: + poly.use_smooth = True + + #bpy.ops.object.shade_smooth() + + object.data.use_auto_smooth = 1 + object.data.auto_smooth_angle = angle # 35 degrees as radians + + objects_affected += 1 + + self.report({'INFO'}, 'Auto smooth angle set to %.0f° on %d of %d objects' % + (degrees(angle), objects_affected, len(objects))) + + return {'FINISHED'} diff --git a/materials_utils/menus.py b/materials_utils/menus.py index faf6da0c..2e444298 100644 --- a/materials_utils/menus.py +++ b/materials_utils/menus.py @@ -2,6 +2,7 @@ import bpy from .functions import * from .operators import * +from .preferences import * # ----------------------------------------------------------------------------- # menu classes @@ -16,21 +17,48 @@ class VIEW3D_MT_materialutilities_assign_material(bpy.types.Menu): def draw(self, context): layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' + edit_mode = False + + materials = bpy.data.materials.items() bl_id = VIEW3D_OT_materialutilities_assign_material_object.bl_idname obj = context.object + mu_prefs = materialutilities_get_preferences(context) if (not obj is None) and obj.mode == 'EDIT': bl_id = VIEW3D_OT_materialutilities_assign_material_edit.bl_idname + edit_mode = True + + if len(materials) > mu_prefs.search_show_limit: + op = layout.operator(bl_id, + text = 'Search', + icon = 'VIEWZOOM') + op.material_name = "" + op.new_material = False + op.show_dialog = True + if not edit_mode: + op.override_type = mu_prefs.override_type + + op = layout.operator(bl_id, + text = "Add New Material", + icon = 'ADD') + op.material_name = mu_new_material_name(mu_prefs.new_material_name) + op.new_material = True + op.show_dialog = True + if not edit_mode: + op.override_type = mu_prefs.override_type - for material_name, material in bpy.data.materials.items(): - layout.operator(bl_id, - text = material_name, - icon_value = material.preview.icon_id).material_name = material_name + layout.separator() - layout.operator(bl_id, - text = "Add New Material", - icon = 'ADD').material_name = "Unnamed material" + for material_name, material in materials: + op = layout.operator(bl_id, + text = material_name, + icon_value = material.preview.icon_id) + op.material_name = material_name + op.new_material = False + op.show_dialog = False + if not edit_mode: + op.override_type = mu_prefs.override_type class VIEW3D_MT_materialutilities_clean_slots(bpy.types.Menu): @@ -65,19 +93,34 @@ class VIEW3D_MT_materialutilities_select_by_material(bpy.types.Menu): def draw(self, context): layout = self.layout + bl_id = VIEW3D_OT_materialutilities_select_by_material_name.bl_idname obj = context.object + mu_prefs = materialutilities_get_preferences(context) + layout.label if obj is None or obj.mode == 'OBJECT': + materials = bpy.data.materials.items() + + if len(materials) > mu_prefs.search_show_limit: + layout.operator(bl_id, + text = 'Search', + icon = 'VIEWZOOM' + ).show_dialog = True + + layout.separator() + #show all used materials in entire blend file - for material_name, material in bpy.data.materials.items(): + for material_name, material in materials: # There's no point in showing materials with 0 users # (It will still show materials with fake user though) if material.users > 0: - layout.operator(VIEW3D_OT_materialutilities_select_by_material_name.bl_idname, + op = layout.operator(bl_id, text = material_name, icon_value = material.preview.icon_id - ).material_name = material_name + ) + op.material_name = material_name + op.show_dialog = False elif obj.mode == 'EDIT': objects = context.selected_editable_objects @@ -93,10 +136,12 @@ class VIEW3D_MT_materialutilities_select_by_material(bpy.types.Menu): if material.name in materials_added: continue - layout.operator(VIEW3D_OT_materialutilities_select_by_material_name.bl_idname, - text = material.name, - icon_value = material.preview.icon_id - ).material_name = material.name + op = layout.operator(bl_id, + text = material.name, + icon_value = material.preview.icon_id + ) + op.material_name = material.name + op.show_dialog = False materials_added.append(material.name) @@ -107,6 +152,7 @@ class VIEW3D_MT_materialutilities_specials(bpy.types.Menu): bl_label = "Specials" def draw(self, context): + mu_prefs = materialutilities_get_preferences(context) layout = self.layout #layout.operator(VIEW3D_OT_materialutilities_set_new_material_name.bl_idname, icon = "SETTINGS") @@ -117,6 +163,17 @@ class VIEW3D_MT_materialutilities_specials(bpy.types.Menu): text = "Merge Base Names", icon = "GREASEPENCIL") + layout.operator(MATERIAL_OT_materialutilities_join_objects.bl_idname, + text = "Join by material", + icon = "OBJECT_DATAMODE") + + layout.separator() + + op = layout.operator(MATERIAL_OT_materialutilities_auto_smooth_angle.bl_idname, + text = "Set Auto Smooth", + icon = "SHADING_SOLID") + op.affect = mu_prefs.set_smooth_affect + op.angle = mu_prefs.auto_smooth_angle class VIEW3D_MT_materialutilities_main(bpy.types.Menu): """Main menu for Material Utilities""" @@ -126,6 +183,7 @@ class VIEW3D_MT_materialutilities_main(bpy.types.Menu): def draw(self, context): obj = context.object + mu_prefs = materialutilities_get_preferences(context) layout = self.layout layout.operator_context = 'INVOKE_REGION_WIN' @@ -150,13 +208,17 @@ class VIEW3D_MT_materialutilities_main(bpy.types.Menu): text = 'Replace Material', icon = 'OVERLAY') - layout.operator(VIEW3D_OT_materialutilities_fake_user_set.bl_idname, + op = layout.operator(VIEW3D_OT_materialutilities_fake_user_set.bl_idname, text = 'Set Fake User', icon = 'FAKE_USER_OFF') + op.fake_user = mu_prefs.fake_user + op.affect = mu_prefs.fake_user_affect - layout.operator(VIEW3D_OT_materialutilities_change_material_link.bl_idname, + op = layout.operator(VIEW3D_OT_materialutilities_change_material_link.bl_idname, text = 'Change Material Link', icon = 'LINKED') + op.link_to = mu_prefs.link_to + op.affect = mu_prefs.link_to_affect layout.separator() layout.menu(VIEW3D_MT_materialutilities_specials.bl_idname, diff --git a/materials_utils/operators.py b/materials_utils/operators.py index 6bb451e6..a91c2301 100644 --- a/materials_utils/operators.py +++ b/materials_utils/operators.py @@ -1,12 +1,20 @@ import bpy from bpy.types import Operator -from bpy.props import StringProperty, BoolProperty, EnumProperty +from bpy.props import ( + StringProperty, + BoolProperty, + EnumProperty, + IntProperty, + FloatProperty + ) from .enum_values import * from .functions import * +from math import radians + # ----------------------------------------------------------------------------- # operator classes @@ -23,15 +31,49 @@ class VIEW3D_OT_materialutilities_assign_material_edit(bpy.types.Operator): default = "", maxlen = 63 ) + new_material: BoolProperty( + name = '', + description = 'Add a new material, enter the name in the box', + default = False + ) + show_dialog: BoolProperty( + name = 'Show Dialog', + default = False + ) @classmethod def poll(cls, context): return context.active_object is not None + def invoke(self, context, event): + if self.show_dialog: + return context.window_manager.invoke_props_dialog(self) + else: + return self.execute(context) + + def draw(self, context): + layout = self.layout + + col = layout.column() + row = col.split(factor = 0.9, align = True) + + if self.new_material: + row.prop(self, "material_name") + else: + row.prop_search(self, "material_name", bpy.data, "materials") + + row.prop(self, "new_material", expand = True, icon = 'ADD') + def execute(self, context): material_name = self.material_name - return mu_assign_material(self, material_name, 'APPEND_MATERIAL') + if self.new_material: + material_name = mu_new_material_name(material_name) + elif material_name == "": + self.report({'WARNING'}, "No Material Name given!") + return {'CANCELLED'} + + return mu_assign_material(self, material_name, 'APPEND_MATERIAL') class VIEW3D_OT_materialutilities_assign_material_object(bpy.types.Operator): @@ -45,7 +87,7 @@ class VIEW3D_OT_materialutilities_assign_material_object(bpy.types.Operator): material_name: StringProperty( name = 'Material Name', description = 'Name of Material to assign to current selection', - default = "Unnamed Material", + default = "", maxlen = 63 ) override_type: EnumProperty( @@ -53,24 +95,55 @@ class VIEW3D_OT_materialutilities_assign_material_object(bpy.types.Operator): description = '', items = mu_override_type_enums ) + new_material: BoolProperty( + name = '', + description = 'Add a new material, enter the name in the box', + default = False + ) + show_dialog: BoolProperty( + name = 'Show Dialog', + default = False + ) @classmethod def poll(cls, context): return len(context.selected_editable_objects) > 0 + def invoke(self, context, event): + if self.show_dialog: + return context.window_manager.invoke_props_dialog(self) + else: + return self.execute(context) + def draw(self, context): layout = self.layout - layout.prop_search(self, "material_name", bpy.data, "materials") + + col = layout.column() + row = col.split(factor=0.9, align = True) + + if self.new_material: + row.prop(self, "material_name") + else: + row.prop_search(self, "material_name", bpy.data, "materials") + + row.prop(self, "new_material", expand = True, icon = 'ADD') layout.prop(self, "override_type") + def execute(self, context): material_name = self.material_name override_type = self.override_type + + if self.new_material: + material_name = mu_new_material_name(material_name) + elif material_name == "": + self.report({'WARNING'}, "No Material Name given!") + return {'CANCELLED'} + result = mu_assign_material(self, material_name, override_type) return result - class VIEW3D_OT_materialutilities_select_by_material_name(bpy.types.Operator): """Select geometry that has the chosen material assigned to it (See the operator panel [F9] for more options)""" @@ -88,11 +161,21 @@ class VIEW3D_OT_materialutilities_select_by_material_name(bpy.types.Operator): description = 'Name of Material to find and Select', maxlen = 63 ) + show_dialog: BoolProperty( + name = 'Show Dialog', + default = False + ) @classmethod def poll(cls, context): return len(context.visible_objects) > 0 + def invoke(self, context, event): + if self.show_dialog: + return context.window_manager.invoke_props_dialog(self) + else: + return self.execute(context) + def draw(self, context): layout = self.layout layout.prop_search(self, "material_name", bpy.data, "materials") @@ -127,12 +210,32 @@ class VIEW3D_OT_materialutilities_clean_material_slots(bpy.types.Operator): bl_label = "Clean Material Slots (Material Utilities)" bl_options = {'REGISTER', 'UNDO'} + # affect: EnumProperty( + # name = "Affect", + # description = "Which objects material slots should be cleaned", + # items = mu_clean_slots_enums, + # default = 'ACTIVE' + # ) + + only_active: BoolProperty( + name = 'Only active object', + description = 'Only remove the material slots for the active object ' + + '(otherwise do it for every selected object)', + default = True + ) + @classmethod def poll(cls, context): return len(context.selected_editable_objects) > 0 + def draw(self, context): + layout = self.layout + layout.prop(self, "only_active", icon = "PIVOT_ACTIVE") + def execute(self, context): - return mu_cleanmatslots(self) + affect = "ACTIVE" if self.only_active else "SELECTED" + + return mu_cleanmatslots(self, affect) class VIEW3D_OT_materialutilities_remove_material_slot(bpy.types.Operator): @@ -146,7 +249,8 @@ class VIEW3D_OT_materialutilities_remove_material_slot(bpy.types.Operator): only_active: BoolProperty( name = 'Only active object', description = 'Only remove the active material slot for the active object ' + - '(otherwise do it for every selected object)' + '(otherwise do it for every selected object)', + default = True ) @classmethod @@ -171,7 +275,8 @@ class VIEW3D_OT_materialutilities_remove_all_material_slots(bpy.types.Operator): only_active: BoolProperty( name = 'Only active object', description = 'Only remove the material slots for the active object ' + - '(otherwise do it for every selected object)' + '(otherwise do it for every selected object)', + default = True ) @classmethod @@ -243,10 +348,10 @@ class VIEW3D_OT_materialutilities_fake_user_set(bpy.types.Operator): default = 'TOGGLE' ) - materials: EnumProperty( - name = "Materials", + affect: EnumProperty( + name = "Affect", description = "Which materials of objects to affect", - items = mu_fake_user_materials_enums, + items = mu_fake_user_affect_enums, default = 'UNUSED' ) @@ -259,13 +364,13 @@ class VIEW3D_OT_materialutilities_fake_user_set(bpy.types.Operator): layout.prop(self, "fake_user", expand = True) layout.separator() - layout.prop(self, "materials") + layout.prop(self, "affect") def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def execute(self, context): - return mu_set_fake_user(self, self.fake_user, self.materials) + return mu_set_fake_user(self, self.fake_user, self.affect) class VIEW3D_OT_materialutilities_change_material_link(bpy.types.Operator): @@ -290,7 +395,7 @@ class VIEW3D_OT_materialutilities_change_material_link(bpy.types.Operator): ) affect: EnumProperty( - name = "Materials", + name = "Affect", description = "Which materials of objects to affect", items = mu_link_affect_enums, default = 'SELECTED' @@ -428,7 +533,7 @@ class MATERIAL_OT_materialutilities_merge_base_names(bpy.types.Operator): @classmethod def poll(self, context): - return len(context.selected_editable_objects) > 0 + return (context.mode == 'OBJECT') and (len(context.visible_objects) > 0) def draw(self, context): layout = self.layout @@ -492,7 +597,7 @@ class MATERIAL_OT_materialutilities_material_slot_move(bpy.types.Operator): @classmethod def poll(self, context): - # would prefer to access sely.movement here, but can'-'t.. + # would prefer to access self.movement here, but can't.. obj = context.active_object if not obj: return False @@ -523,3 +628,115 @@ class MATERIAL_OT_materialutilities_material_slot_move(bpy.types.Operator): self.report({'INFO'}, active_material.name + ' moved to ' + self.movement.lower()) return {'FINISHED'} + + + +class MATERIAL_OT_materialutilities_join_objects(bpy.types.Operator): + """Join objects that have the same (selected) material(s)""" + + bl_idname = "material.materialutilities_join_objects" + bl_label = "Join by material (Material Utilities)" + bl_description = "Join objects that share the same material" + bl_options = {'REGISTER', 'UNDO'} + + material_name: StringProperty( + name = "Material", + default = "", + description = 'Material to use to join objects' + ) + is_auto: BoolProperty( + name = "Auto Join", + description = "Join objects for all materials" + ) + + is_not_undo = True + material_error = [] # collect mat for warning messages + + + @classmethod + def poll(self, context): + # This operator only works in Object mode + return (context.mode == 'OBJECT') and (len(context.visible_objects) > 0) + + def draw(self, context): + layout = self.layout + + box_1 = layout.box() + box_1.prop_search(self, "material_name", bpy.data, "materials") + box_1.enabled = not self.is_auto + layout.separator() + + layout.prop(self, "is_auto", text = "Auto Join", icon = "SYNTAX_ON") + + def invoke(self, context, event): + self.is_not_undo = True + return context.window_manager.invoke_props_dialog(self) + + def execute(self, context): + # Reset Material errors, otherwise we risk reporting errors erroneously.. + self.material_error = [] + materials = [] + + if not self.is_auto: + if self.material_name == "": + self.report({'WARNING'}, "No Material Name given!") + + self.is_not_undo = False + return {'CANCELLED'} + materials = [self.material_name] + else: + materials = bpy.data.materials.keys() + + result = mu_join_objects(self, materials) + self.is_not_undo = False + + return result + + +class MATERIAL_OT_materialutilities_auto_smooth_angle(bpy.types.Operator): + """Set Auto smooth values for selected objects""" + # Inspired by colkai + + bl_idname = "view3d.materialutilities_auto_smooth_angle" + bl_label = "Set Auto Smooth Angle (Material Utilities)" + bl_options = {'REGISTER', 'UNDO'} + + affect: EnumProperty( + name = "Affect", + description = "Which objects of to affect", + items = mu_affect_enums, + default = 'SELECTED' + ) + angle: FloatProperty( + name = "Angle", + description = "Maximum angle between face normals that will be considered as smooth", + subtype = 'ANGLE', + min = 0, + max = radians(180), + default = radians(35) + ) + set_smooth_shading: BoolProperty( + name = "Set Smooth", + description = "Set Smooth shading for the affected objects\n" + "This overrides the currenth smooth/flat shading that might be set to different parts of the object", + default = True + ) + + @classmethod + def poll(cls, context): + return (len(bpy.data.objects) > 0) and (context.mode == 'OBJECT') + + def invoke(self, context, event): + self.is_not_undo = True + return context.window_manager.invoke_props_dialog(self) + + def draw(self, context): + layout = self.layout + + layout.prop(self, "angle") + layout.prop(self, "affect") + + layout.prop(self, "set_smooth_shading", icon = "BLANK1") + + def execute(self, context): + return mu_set_auto_smooth(self, self.angle, self.affect, self.set_smooth_shading) diff --git a/materials_utils/preferences.py b/materials_utils/preferences.py new file mode 100644 index 00000000..7fdbd9ff --- /dev/null +++ b/materials_utils/preferences.py @@ -0,0 +1,118 @@ +import bpy + +from bpy.types import ( + AddonPreferences, + PropertyGroup, + ) +from bpy.props import ( + StringProperty, + BoolProperty, + EnumProperty, + IntProperty, + FloatProperty + ) +from math import radians + +from .enum_values import * + +# Addon Preferences +class VIEW3D_MT_materialutilities_preferences(AddonPreferences): + bl_idname = __package__ + + new_material_name: StringProperty( + name = "New Material name", + description = "What Base name pattern to use for a new created Material\n" + "It is appended by an automatic numeric pattern depending\n" + "on the number of Scene's materials containing the Base", + default = "Unnamed Material", + ) + override_type: EnumProperty( + name = 'Assignment method', + description = '', + items = mu_override_type_enums + ) + fake_user: EnumProperty( + name = "Set Fake User", + description = "Default option for the Set Fake User (Turn fake user on or off)", + items = mu_fake_user_set_enums, + default = 'TOGGLE' + ) + fake_user_affect: EnumProperty( + name = "Affect", + description = "Which materials of objects to affect", + items = mu_fake_user_affect_enums, + default = 'UNUSED' + ) + link_to: EnumProperty( + name = "Change Material Link To", + description = "Default option for the Change Material Link operator", + items = mu_link_to_enums, + default = 'OBJECT' + ) + link_to_affect: EnumProperty( + name = "Affect", + description = "Which materials of objects to affect by default with Change Material Link", + items = mu_link_affect_enums, + default = 'SELECTED' + ) + search_show_limit: IntProperty( + name = "Show 'Search' Limit", + description = "How many materials should there be before the 'Search' option is shown " + "in the Assign Material and Select By Material menus\n" + "Set it to 0 to always show 'Search'", + min = 0, + default = 0 + ) + + set_smooth_affect: EnumProperty( + name = "Set Auto Smooth Affect", + description = "Which objects to affect", + items = mu_affect_enums, + default = 'SELECTED' + ) + auto_smooth_angle: FloatProperty( + name = "Auto Smooth Angle", + description = "Maximum angle between face normals that will be considered as smooth", + subtype = 'ANGLE', + min = 0, + max = radians(180), + default = radians(35) + ) + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + + box = layout.box() + box.label(text = "Defaults") + + a = box.box() + a.label(text = "Assign Material") + a.prop(self, "new_material_name", icon = "MATERIAL") + a.prop(self, "override_type", expand = False) + + b = box.box() + b.label(text = "Set Fake User") + b.row().prop(self, "fake_user", expand = False) + b.row().prop(self, "fake_user_affect", expand = False) + + c = box.box() + c.label(text = "Set Link To") + c.row().prop(self, "link_to", expand = False) + c.row().prop(self, "link_to_affect", expand = False) + + d = box.box() + d.label(text = "Set Auto Smooth") + d.row().prop(self, "auto_smooth_angle", expand = False) + d.row().prop(self, "set_smooth_affect", expand = False) + + box = layout.box() + box.label(text = "Miscellaneous") + + #col = box.column() + #row = col.split(factor = 0.5) + box.prop(self, "search_show_limit", expand = False) + + +def materialutilities_get_preferences(context): + return context.preferences.addons[__package__].preferences |