190 lines
5.1 KiB
Python
190 lines
5.1 KiB
Python
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
|