Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'materials_utils/operators.py')
-rw-r--r--materials_utils/operators.py525
1 files changed, 525 insertions, 0 deletions
diff --git a/materials_utils/operators.py b/materials_utils/operators.py
new file mode 100644
index 00000000..6bb451e6
--- /dev/null
+++ b/materials_utils/operators.py
@@ -0,0 +1,525 @@
+import bpy
+
+from bpy.types import Operator
+from bpy.props import StringProperty, BoolProperty, EnumProperty
+
+
+from .enum_values import *
+from .functions import *
+
+# -----------------------------------------------------------------------------
+# 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
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def execute(self, context):
+ material_name = self.material_name
+ 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 = "Unnamed Material",
+ maxlen = 63
+ )
+ override_type: EnumProperty(
+ name = 'Assignment method',
+ description = '',
+ items = mu_override_type_enums
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.selected_editable_objects) > 0
+
+ def draw(self, context):
+ layout = self.layout
+ layout.prop_search(self, "material_name", bpy.data, "materials")
+
+ layout.prop(self, "override_type")
+
+ def execute(self, context):
+ material_name = self.material_name
+ override_type = self.override_type
+ 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
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.visible_objects) > 0
+
+ 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'}
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.selected_editable_objects) > 0
+
+ def execute(self, context):
+ return mu_cleanmatslots(self)
+
+
+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)'
+ )
+
+ @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)'
+ )
+
+ @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'
+ )
+
+ materials: EnumProperty(
+ name = "Materials",
+ description = "Which materials of objects to affect",
+ items = mu_fake_user_materials_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, "materials")
+
+ 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)
+
+
+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 = "Materials",
+ 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 len(context.selected_editable_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 sely.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'}