import bpy import bmesh import math import re import os image_material_prefix = "TT_checker_" def get_object_texture_image(obj): # Search in material & texture slots for slot_mat in obj.material_slots: if slot_mat.material: # Check for traditional texture slots in material for slot_tex in slot_mat.material.texture_paint_slots: if slot_tex and slot_tex.texture and hasattr(slot_tex.texture , 'image'): return slot_tex.texture.image # Check if material uses Nodes if hasattr(slot_mat.material , 'node_tree'): if slot_mat.material.node_tree: for node in slot_mat.material.node_tree.nodes: if type(node) is bpy.types.ShaderNodeTexImage: if node.image: return node.image return None def image_resize(image, size_x, size_y): if image and image.source == 'FILE' or image.source == 'GENERATED': image.generated_width = int(size_x) image.generated_height = int(size_y) image.scale( int(size_x), int(size_y) ) def checker_images_cleanup(): #Unneeded materials have to be deleted before unneeded images because images have them as users for material in bpy.data.materials: if material and image_material_prefix in material.name: if not material.users: bpy.data.materials.remove(material, do_unlink=True) for image in bpy.data.images: if image and image_material_prefix in image.name: if not image.users: bpy.data.images.remove(image, do_unlink=True) def get_checker_name(mode, size_x, size_y): return (image_material_prefix+"{1}x{2}_{0}").format(mode, size_x, size_y) def get_area_triangle_uv(A,B,C, size_x, size_y): scale_x = size_x / max(size_x, size_y) scale_y = size_y / max(size_x, size_y) A.x/=scale_x B.x/=scale_x C.x/=scale_x A.y/=scale_y B.y/=scale_y C.y/=scale_y return get_area_triangle(A,B,C) def get_area_triangle(A,B,C): # Heron's formula: http://www.1728.org/triang.htm # area = square root (s • (s - a) • (s - b) • (s - c)) a = (B-A).length b = (C-B).length c = (A-C).length s = (a+b+c)/2 # Use abs(s-a) for values that otherwise generate negative values e.g. pinched UV verts, otherwise math domain error return math.sqrt(s * abs(s-a) * abs(s-b) * abs(s-c)) stored_materials = {} stored_material_faces = {} def store_materials_clear(): stored_materials.clear() stored_material_faces.clear() def store_materials(obj): stored_materials[obj] = [] stored_material_faces[obj] = [] # Enter edit mode bpy.ops.object.select_all(action='DESELECT') obj.select_set( state = True, view_layer = None) bpy.context.view_layer.objects.active = obj bpy.ops.object.mode_set(mode='EDIT') bm = bmesh.from_edit_mesh(obj.data) # for each slot backup the material for s in range(len(obj.material_slots)): slot = obj.material_slots[s] stored_materials[obj].append(slot.material) stored_material_faces[obj].append( [face.index for face in bm.faces if face.material_index == s] ) if slot and slot.material: slot.material.name = "backup_"+slot.material.name slot.material.use_fake_user = True # Back to object mode bpy.ops.object.mode_set(mode='OBJECT') def restore_materials(objs): if len(objs) == 0 : return else: for obj in objs : if stored_materials.get(obj) == None : bpy.ops.object.mode_set(mode='OBJECT') bpy.ops.object.select_all(action='DESELECT') obj.select_set( state = True, view_layer = None) bpy.context.view_layer.objects.active = obj count = len(obj.material_slots) for i in range(count): bpy.ops.object.material_slot_remove() objs = [obj for obj in objs if obj in stored_materials] for obj in objs : # Enter edit mode bpy.context.view_layer.objects.active = obj bpy.ops.object.mode_set(mode='EDIT') bm = bmesh.from_edit_mesh(obj.data) # Restore slots for index in range(len(stored_materials[obj])): material = stored_materials[obj][index] faces = stored_material_faces[obj][index] if material: material.name = material.name.replace("backup_","") obj.material_slots[index].material = material material.use_fake_user = False # Face material indexies for face in bm.faces: if face.index in faces: face.material_index = index # Back to object mode bpy.ops.object.mode_set(mode='OBJECT') # Remove material slots if none before if len(stored_materials[obj]) == 0 : for i in range(len(obj.material_slots)): bpy.ops.object.material_slot_remove() def get_tile_size(self, image, udim_tile): tile_name = image.name+"."+str(udim_tile)+"."+image.file_format.lower() purge = False if tile_name not in bpy.data.images: base_image_location = bpy.path.abspath(image.filepath) base_tile = re.findall('\d{4}', base_image_location)[-1] image_location = base_image_location.replace(base_tile, str(udim_tile)) if not os.path.isfile(image_location): self.report({'INFO'}, "Missing tile image "+tile_name) return 0 else: bpy.data.images.load(image_location, check_existing=False) #bpy.ops.image.open(filepath=image_location, relative_path=False, use_udim_detecting=False) purge = True tile = bpy.data.images[tile_name] size = min(tile.size[0], tile.size[1]) if purge: #bpy.data.batch_remove([tile]) bpy.data.images.remove(tile, do_unlink=True) return size