mirror of
https://github.com/blender/blender-addons.git
synced 2025-07-25 16:05:20 +00:00

Move copyright text to SPDX-FileCopyrightText or set to the Blender Foundation so "make check_licenses" now runs without warnings.
747 lines
24 KiB
Python
747 lines
24 KiB
Python
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import bpy
|
|
|
|
from bpy.types import Operator
|
|
from bpy.props import (
|
|
StringProperty,
|
|
BoolProperty,
|
|
EnumProperty,
|
|
IntProperty,
|
|
FloatProperty
|
|
)
|
|
|
|
|
|
from .enum_values import *
|
|
from .functions import *
|
|
|
|
from math import radians
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# operator classes
|
|
|
|
class VIEW3D_OT_materialutilities_assign_material_edit(bpy.types.Operator):
|
|
"""Assign a material to the current selection"""
|
|
|
|
bl_idname = "view3d.materialutilities_assign_material_edit"
|
|
bl_label = "Assign Material (Material Utilities)"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
material_name: StringProperty(
|
|
name = 'Material Name',
|
|
description = 'Name of Material to assign to current selection',
|
|
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
|
|
|
|
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):
|
|
"""Assign a material to the current selection
|
|
(See the operator panel [F9] for more options)"""
|
|
|
|
bl_idname = "view3d.materialutilities_assign_material_object"
|
|
bl_label = "Assign Material (Material Utilities)"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
material_name: StringProperty(
|
|
name = 'Material Name',
|
|
description = 'Name of Material to assign to current selection',
|
|
default = "",
|
|
maxlen = 63
|
|
)
|
|
override_type: EnumProperty(
|
|
name = 'Assignment method',
|
|
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
|
|
|
|
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)"""
|
|
|
|
bl_idname = "view3d.materialutilities_select_by_material_name"
|
|
bl_label = "Select By Material Name (Material Utilities)"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
extend_selection: BoolProperty(
|
|
name = 'Extend Selection',
|
|
description = 'Keeps the current selection and adds faces with the material to the selection'
|
|
)
|
|
material_name: StringProperty(
|
|
name = 'Material Name',
|
|
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")
|
|
|
|
layout.prop(self, "extend_selection", icon = "SELECT_EXTEND")
|
|
|
|
def execute(self, context):
|
|
material_name = self.material_name
|
|
ext = self.extend_selection
|
|
return mu_select_by_material_name(self, material_name, ext)
|
|
|
|
|
|
class VIEW3D_OT_materialutilities_copy_material_to_others(bpy.types.Operator):
|
|
"""Copy the material(s) of the active object to the other selected objects"""
|
|
|
|
bl_idname = "view3d.materialutilities_copy_material_to_others"
|
|
bl_label = "Copy material(s) to others (Material Utilities)"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.active_object is not None) and (context.active_object.mode != 'EDIT')
|
|
|
|
def execute(self, context):
|
|
return mu_copy_material_to_others(self)
|
|
|
|
|
|
class VIEW3D_OT_materialutilities_clean_material_slots(bpy.types.Operator):
|
|
"""Removes any material slots from the selected objects that are not used"""
|
|
|
|
bl_idname = "view3d.materialutilities_clean_material_slots"
|
|
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):
|
|
affect = "ACTIVE" if self.only_active else "SELECTED"
|
|
|
|
return mu_cleanmatslots(self, affect)
|
|
|
|
|
|
class VIEW3D_OT_materialutilities_remove_material_slot(bpy.types.Operator):
|
|
"""Remove the active material slot from selected object(s)
|
|
(See the operator panel [F9] for more options)"""
|
|
|
|
bl_idname = "view3d.materialutilities_remove_material_slot"
|
|
bl_label = "Remove Active Material Slot (Material Utilities)"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
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)',
|
|
default = True
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.active_object is not None) and (context.active_object.mode != 'EDIT')
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.prop(self, "only_active", icon = "PIVOT_ACTIVE")
|
|
|
|
def execute(self, context):
|
|
return mu_remove_material(self, self.only_active)
|
|
|
|
class VIEW3D_OT_materialutilities_remove_all_material_slots(bpy.types.Operator):
|
|
"""Remove all material slots from selected object(s)
|
|
(See the operator panel [F9] for more options)"""
|
|
|
|
bl_idname = "view3d.materialutilities_remove_all_material_slots"
|
|
bl_label = "Remove All Material Slots (Material Utilities)"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
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 (context.active_object is not None) and (context.active_object.mode != 'EDIT')
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.prop(self, "only_active", icon = "PIVOT_ACTIVE")
|
|
|
|
def execute(self, context):
|
|
return mu_remove_all_materials(self, self.only_active)
|
|
|
|
|
|
class VIEW3D_OT_materialutilities_replace_material(bpy.types.Operator):
|
|
"""Replace a material by name"""
|
|
bl_idname = "view3d.materialutilities_replace_material"
|
|
bl_label = "Replace Material (Material Utilities)"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
matorg: StringProperty(
|
|
name = "Original",
|
|
description = "Material to find and replace",
|
|
maxlen = 63,
|
|
)
|
|
matrep: StringProperty(name="Replacement",
|
|
description = "Material that will be used instead of the Original material",
|
|
maxlen = 63,
|
|
)
|
|
all_objects: BoolProperty(
|
|
name = "All Objects",
|
|
description = "Replace for all objects in this blend file (otherwise only selected objects)",
|
|
default = True,
|
|
)
|
|
update_selection: BoolProperty(
|
|
name = "Update Selection",
|
|
description = "Select affected objects and deselect unaffected",
|
|
default = True,
|
|
)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
layout.prop_search(self, "matorg", bpy.data, "materials")
|
|
layout.prop_search(self, "matrep", bpy.data, "materials")
|
|
layout.separator()
|
|
|
|
layout.prop(self, "all_objects", icon = "BLANK1")
|
|
layout.prop(self, "update_selection", icon = "SELECT_INTERSECT")
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def execute(self, context):
|
|
return mu_replace_material(self.matorg, self.matrep, self.all_objects, self.update_selection)
|
|
|
|
|
|
class VIEW3D_OT_materialutilities_fake_user_set(bpy.types.Operator):
|
|
"""Enable/disable fake user for materials"""
|
|
|
|
bl_idname = "view3d.materialutilities_fake_user_set"
|
|
bl_label = "Set Fake User (Material Utilities)"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
fake_user: EnumProperty(
|
|
name = "Fake User",
|
|
description = "Turn fake user on or off",
|
|
items = mu_fake_user_set_enums,
|
|
default = 'TOGGLE'
|
|
)
|
|
|
|
affect: EnumProperty(
|
|
name = "Affect",
|
|
description = "Which materials of objects to affect",
|
|
items = mu_fake_user_affect_enums,
|
|
default = 'UNUSED'
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.active_object is not None)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
layout.prop(self, "fake_user", expand = True)
|
|
layout.separator()
|
|
|
|
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.affect)
|
|
|
|
|
|
class VIEW3D_OT_materialutilities_change_material_link(bpy.types.Operator):
|
|
"""Link the materials to Data or Object, while keepng materials assigned"""
|
|
|
|
bl_idname = "view3d.materialutilities_change_material_link"
|
|
bl_label = "Change Material Linking (Material Utilities)"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
override: BoolProperty(
|
|
name = "Override Data material",
|
|
description = "Override the materials assigned to the object data/mesh when switching to 'Linked to Data'\n" +
|
|
"(WARNING: This will override the materials of other linked objects, " +
|
|
"which have the materials linked to Data)",
|
|
default = False,
|
|
)
|
|
link_to: EnumProperty(
|
|
name = "Link",
|
|
description = "What should the material be linked to",
|
|
items = mu_link_to_enums,
|
|
default = 'OBJECT'
|
|
)
|
|
|
|
affect: EnumProperty(
|
|
name = "Affect",
|
|
description = "Which materials of objects to affect",
|
|
items = mu_link_affect_enums,
|
|
default = 'SELECTED'
|
|
)
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return (context.active_object is not None)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
layout.prop(self, "link_to", expand = True)
|
|
layout.separator()
|
|
|
|
layout.prop(self, "affect")
|
|
layout.separator()
|
|
|
|
layout.prop(self, "override", icon = "DECORATE_OVERRIDE")
|
|
|
|
def invoke(self, context, event):
|
|
return context.window_manager.invoke_props_dialog(self)
|
|
|
|
def execute(self, context):
|
|
return mu_change_material_link(self, self.link_to, self.affect, self.override)
|
|
|
|
class MATERIAL_OT_materialutilities_merge_base_names(bpy.types.Operator):
|
|
"""Merges materials that has the same base names but ends with .xxx (.001, .002 etc)"""
|
|
|
|
bl_idname = "material.materialutilities_merge_base_names"
|
|
bl_label = "Merge Base Names"
|
|
bl_description = "Merge materials that has the same base names but ends with .xxx (.001, .002 etc)"
|
|
|
|
material_base_name: StringProperty(
|
|
name = "Material Base Name",
|
|
default = "",
|
|
description = 'Base name for materials to merge ' +
|
|
'(e.g. "Material" is the base name of "Material.001", "Material.002" etc.)'
|
|
)
|
|
is_auto: BoolProperty(
|
|
name = "Auto Merge",
|
|
description = "Find all available duplicate materials and Merge them"
|
|
)
|
|
|
|
is_not_undo = False
|
|
material_error = [] # collect mat for warning messages
|
|
|
|
|
|
def replace_name(self):
|
|
"""If the user chooses a material like 'Material.042', clean it up to get a base name ('Material')"""
|
|
|
|
# use the chosen material as a base one, check if there is a name
|
|
self.check_no_name = (False if self.material_base_name in {""} else True)
|
|
|
|
# No need to do this if it's already "clean"
|
|
# (Also lessens the potential of error given about the material with the Base name)
|
|
if '.' not in self.material_base_name:
|
|
return
|
|
|
|
if self.check_no_name is True:
|
|
for mat in bpy.data.materials:
|
|
name = mat.name
|
|
|
|
if name == self.material_base_name:
|
|
try:
|
|
base, suffix = name.rsplit('.', 1)
|
|
|
|
# trigger the exception
|
|
num = int(suffix, 10)
|
|
self.material_base_name = base
|
|
mat.name = self.material_base_name
|
|
return
|
|
except ValueError:
|
|
if name not in self.material_error:
|
|
self.material_error.append(name)
|
|
return
|
|
|
|
return
|
|
|
|
def split_name(self, material):
|
|
"""Split the material name into a base and a suffix"""
|
|
|
|
name = material.name
|
|
|
|
# No need to do this if it's already "clean"/there is no suffix
|
|
if '.' not in name:
|
|
return name, None
|
|
|
|
base, suffix = name.rsplit('.', 1)
|
|
|
|
try:
|
|
# trigger the exception
|
|
num = int(suffix, 10)
|
|
except ValueError:
|
|
# Not a numeric suffix
|
|
# Don't report on materials not actually included in the merge!
|
|
if ((self.is_auto or base == self.material_base_name)
|
|
and (name not in self.material_error)):
|
|
self.material_error.append(name)
|
|
return name, None
|
|
|
|
if self.is_auto is False:
|
|
if base == self.material_base_name:
|
|
return base, suffix
|
|
else:
|
|
return name, None
|
|
|
|
return base, suffix
|
|
|
|
def fixup_slot(self, slot):
|
|
"""Fix material slots that was assigned to materials now removed"""
|
|
|
|
if not slot.material:
|
|
return
|
|
|
|
base, suffix = self.split_name(slot.material)
|
|
if suffix is None:
|
|
return
|
|
|
|
try:
|
|
base_mat = bpy.data.materials[base]
|
|
except KeyError:
|
|
print("\n[Materials Utilities Specials]\nLink to base names\nError:"
|
|
"Base material %r not found\n" % base)
|
|
return
|
|
|
|
slot.material = base_mat
|
|
|
|
def main_loop(self, context):
|
|
"""Loops through all objects and material slots to make sure they are assigned to the right material"""
|
|
|
|
for obj in context.scene.objects:
|
|
for slot in obj.material_slots:
|
|
self.fixup_slot(slot)
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
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_base_name", bpy.data, "materials")
|
|
box_1.enabled = not self.is_auto
|
|
layout.separator()
|
|
|
|
layout.prop(self, "is_auto", text = "Auto Rename/Replace", 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 = []
|
|
|
|
if not self.is_auto:
|
|
self.replace_name()
|
|
|
|
if self.check_no_name:
|
|
self.main_loop(context)
|
|
else:
|
|
self.report({'WARNING'}, "No Material Base Name given!")
|
|
|
|
self.is_not_undo = False
|
|
return {'CANCELLED'}
|
|
|
|
self.main_loop(context)
|
|
|
|
if self.material_error:
|
|
materials = ", ".join(self.material_error)
|
|
|
|
if len(self.material_error) == 1:
|
|
waswere = " was"
|
|
suff_s = ""
|
|
else:
|
|
waswere = " were"
|
|
suff_s = "s"
|
|
|
|
self.report({'WARNING'}, materials + waswere + " not removed or set as Base" + suff_s)
|
|
|
|
self.is_not_undo = False
|
|
return {'FINISHED'}
|
|
|
|
class MATERIAL_OT_materialutilities_material_slot_move(bpy.types.Operator):
|
|
"""Move the active material slot"""
|
|
|
|
bl_idname = "material.materialutilities_slot_move"
|
|
bl_label = "Move Slot"
|
|
bl_description = "Move the material slot"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
movement: EnumProperty(
|
|
name = "Move",
|
|
description = "How to move the material slot",
|
|
items = mu_material_slot_move_enums
|
|
)
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
# would prefer to access self.movement here, but can't..
|
|
obj = context.active_object
|
|
if not obj:
|
|
return False
|
|
if (obj.active_material_index < 0) or (len(obj.material_slots) <= 1):
|
|
return False
|
|
return True
|
|
|
|
def execute(self, context):
|
|
active_object = context.active_object
|
|
active_material = context.object.active_material
|
|
|
|
if self.movement == 'TOP':
|
|
dir = 'UP'
|
|
|
|
steps = active_object.active_material_index
|
|
else:
|
|
dir = 'DOWN'
|
|
|
|
last_slot_index = len(active_object.material_slots) - 1
|
|
steps = last_slot_index - active_object.active_material_index
|
|
|
|
if steps == 0:
|
|
self.report({'WARNING'}, active_material.name + " already at " + self.movement.lower() + '!')
|
|
else:
|
|
for i in range(steps):
|
|
bpy.ops.object.material_slot_move(direction = dir)
|
|
|
|
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 current 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)
|