import bpy from os import path, walk from pathlib import Path class TEXTURE_OT_search_bars(bpy.types.Operator): """Add or remove search bars""" bl_idname = "texture.bars" bl_label = "Add search bar" bl_options = {'REGISTER', 'UNDO'} bl_region_type = 'UI' add : bpy.props.StringProperty(default="") def execute(self, context): if self.add[0] == "T": context.scene.fmt_search_bars.add() elif self.add[0] == "F": context.scene.fmt_search_bars.remove(int(self.add[1:])) return {'FINISHED'} class TEXTURE_OT_remove_image(bpy.types.Operator): """Delete the image from the file""" bl_idname = "texture.remove_image" bl_label = "Delete image" bl_options = {'REGISTER', 'UNDO'} bl_region_type = 'UI' def execute(self, context): if len(bpy.context.scene.fmt_list) > 0: image_name = bpy.context.scene.fmt_list[bpy.context.scene.fmt_list_index].img_name bpy.context.scene.fmt_list.remove(bpy.context.scene.fmt_list_index) bpy.data.images.remove(bpy.data.images[image_name]) return {'FINISHED'} class TEXTURE_OT_find_in_folder(bpy.types.Operator): """Find your missing textures in this folder and subfolders""" bl_idname = "texture.folder_find" bl_label = "Find in folder" bl_options = {'REGISTER', 'UNDO'} bl_region_type = 'UI' def execute(self, context): scene = context.scene files_dir_list = [] try: for search_path in scene.fmt_search_bars: if search_path.bars: for dirpath, dirnames, filenames in walk(bpy.path.abspath(search_path.bars)): files_dir_list.append([dirpath, filenames]) except FileNotFoundError: print(f"Error - Bad file path {search_path.bars}") return {'FINISHED'} for missing_img in scene.fmt_list: img_file_name = bpy.path.basename(bpy.data.images[missing_img.img_name].filepath) for imgs in files_dir_list: if img_file_name in imgs[1]: bpy.data.images[missing_img.img_name].filepath = path.join(imgs[0], img_file_name) elif bpy.context.scene.fmt_extension_replace: self.search_different_extension(missing_img, img_file_name, imgs) bpy.ops.texture.find_missing_textures() return {'FINISHED'} def search_different_extension(self, missing_img, img_file_name, imgs): base_img_name = path.splitext(img_file_name)[0] for img_in_folders in imgs[1]: if base_img_name == path.splitext(img_in_folders)[0]: bpy.data.images[missing_img.img_name].filepath = path.join(imgs[0], img_in_folders) class TEXTURE_OT_find_missing_textures(bpy.types.Operator): """Search for misssing textures""" bl_idname = "texture.find_missing_textures" bl_label = "Search missing textures" bl_options = {'REGISTER', 'UNDO'} bl_region_type = 'UI' def execute(self, context): bpy.context.scene.fmt_list.clear() self.search_for_missing_texture() self.report_full_list() if len(bpy.context.scene.fmt_list) > 0: self.report({'WARNING'}, "Some texture are missing!") return {'FINISHED'} def search_for_missing_texture(self): if len(bpy.context.scene.fmt_search_bars) == 0: bpy.context.scene.fmt_search_bars.add() for img in bpy.data.images: if img.name == "Render Result" or img.name == "Viewer Node": continue if hasattr(img, 'has_data'): img.size[0] # Need to do this to refresh the data at the first time if img.has_data == False: self.add_texture_to_missing_list(img) elif not img.packed_file: if not Path(bpy.path.abspath(img.filepath)).is_file(): self.add_texture_to_missing_list(img) def report_full_list(self): # Get list of bad materials mat_list = [] self.report({'INFO'}, f"------------------------------------------") self.report({'INFO'}, f"Missing material report:") self.report({'INFO'}, f"(Material name >> Node name (Node label) >> Image name)") self.report({'INFO'}, f"(Image path)") for mat in bpy.data.materials: if not mat.name == "Dots Stroke" and hasattr(mat.node_tree, 'nodes'): for node in mat.node_tree.nodes: if node.type == 'TEX_IMAGE': if hasattr(node.image, 'has_data'): if not node.image.has_data: if node.label == "": node_label = node.image.name else: node_label = node.label if not mat.name in mat_list: mat_list.append(mat.name) self.report({'WARNING'}, f"{mat.name} >> {node.name} ({node_label}) >> {node.image.name}") self.report({'WARNING'}, f"{node.image.filepath}") if not mat_list: self.report({'INFO'}, f"Good work! All the shaders are good!") else: self.search_object_with_bad_mat(mat_list) def search_object_with_bad_mat(self, mat_list): self.deselect_all_objs() self.report({'INFO'}, f"------------------------------------------") self.report({'INFO'}, f"List of objects with missing textures:") if len(bpy.data.scenes) == 1: self.report({'INFO'}, f"Object name >> Material name") else: self.report({'INFO'}, f"Scene name >> Object name >> Material name") for scene in bpy.data.scenes: for obj in scene.objects: if obj.type == "MESH": if len(obj.material_slots.items()) > 0: for miss_mat in mat_list: if miss_mat in obj.material_slots: if len(bpy.data.scenes) == 1: if bpy.context.scene.fmt_select_bad_obj: obj.select_set(True) self.report({'WARNING'}, f"{obj.name} >> {miss_mat}") else: self.report({'WARNING'}, f"{scene.name} >> {obj.name} >> {miss_mat}") def deselect_all_objs(self): if bpy.context.scene.fmt_select_bad_obj: for obj in bpy.context.selected_objects: obj.select_set(False) def add_texture_to_missing_list(self, img): img.reload() new_image_list = bpy.context.scene.fmt_list.add() new_image_list.img_name = img.name new_image_list.img_path = img.filepath