materials_utils: add search and preferences T67990

This commit is contained in:
Brendon Murphy 2019-08-17 10:19:02 +10:00
parent 6c882f7d81
commit 4fa93f6eb1
6 changed files with 587 additions and 65 deletions

View File

@ -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()

View File

@ -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"),

View File

@ -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'}

View File

@ -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
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
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
layout.operator(bl_id,
text = "Add New Material",
icon = 'ADD').material_name = "Unnamed material"
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
layout.separator()
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,

View File

@ -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)

View File

@ -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