Fixes
This fixes is to get everything working on the new auto load and properties system. Also some other small fixes.
This commit is contained in:
+147
-123
@@ -23,24 +23,24 @@ class MaterialImageList:
|
||||
self.h: int = 0
|
||||
self.fit = None
|
||||
|
||||
def scale_images_to_largest(images: list[Image]) -> set:
|
||||
x: int = 0
|
||||
y: int = 0
|
||||
def scale_images_to_largest(images: list[Image]) -> tuple[int, int]:
|
||||
try:
|
||||
valid_images = []
|
||||
for img in images:
|
||||
if img and hasattr(img, 'name'):
|
||||
image_data = bpy.data.images.get(img.name)
|
||||
if image_data and image_data.has_data:
|
||||
valid_images.append(image_data)
|
||||
|
||||
# Filter out None or invalid images
|
||||
valid_images = [img for img in images if img and img.has_data]
|
||||
|
||||
if not valid_images:
|
||||
return 0, 0
|
||||
if not valid_images:
|
||||
return 1, 1
|
||||
|
||||
max_width = max(img.size[0] for img in valid_images)
|
||||
max_height = max(img.size[1] for img in valid_images)
|
||||
|
||||
for image in valid_images:
|
||||
x = max(x, image.size[0])
|
||||
y = max(y, image.size[1])
|
||||
|
||||
for image in valid_images:
|
||||
image.scale(width=int(x), height=int(y))
|
||||
|
||||
return x, y
|
||||
return max_width, max_height
|
||||
except:
|
||||
return 1, 1
|
||||
|
||||
def MaterialImageList_to_Image_list(classitem: MaterialImageList) -> list[Image]:
|
||||
list_of_images: list[Image] = []
|
||||
@@ -62,56 +62,75 @@ def get_material_images_from_scene(context: Context) -> list[MaterialImageList]:
|
||||
if obj.type == 'MESH':
|
||||
for mat_slot in obj.material_slots:
|
||||
# Only process materials that are selected for atlas
|
||||
if mat_slot.material and mat_slot.material.include_in_atlas is True:
|
||||
if mat_slot.material and mat_slot.material.avatar_toolkit.include_in_atlas:
|
||||
new_mat_image_item = MaterialImageList()
|
||||
try:
|
||||
new_mat_image_item.albedo = bpy.data.images[mat_slot.material.texture_atlas_albedo]
|
||||
except Exception:
|
||||
name = mat_slot.material.name + "_albedo_replacement"
|
||||
if name in bpy.data.images:
|
||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
||||
new_mat_image_item.albedo = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||
new_mat_image_item.albedo.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
|
||||
try:
|
||||
new_mat_image_item.normal = bpy.data.images[mat_slot.material.texture_atlas_normal]
|
||||
except Exception:
|
||||
name = mat_slot.material.name + "_normal_replacement"
|
||||
if name in bpy.data.images:
|
||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
||||
new_mat_image_item.normal = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||
new_mat_image_item.normal.pixels[:] = numpy.tile(numpy.array([0.5,0.5,1.0,1.0]), 32*32)
|
||||
try:
|
||||
new_mat_image_item.emission = bpy.data.images[mat_slot.material.texture_atlas_emission]
|
||||
except Exception:
|
||||
name = mat_slot.material.name + "_emission_replacement"
|
||||
if name in bpy.data.images:
|
||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
||||
new_mat_image_item.emission = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||
new_mat_image_item.emission.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
|
||||
try:
|
||||
new_mat_image_item.ambient_occlusion = bpy.data.images[mat_slot.material.texture_atlas_ambient_occlusion]
|
||||
except Exception:
|
||||
name = mat_slot.material.name + "_ambient_occlusion_replacement"
|
||||
if name in bpy.data.images:
|
||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
||||
new_mat_image_item.ambient_occlusion = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||
new_mat_image_item.ambient_occlusion.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,1.0]), 32*32)
|
||||
try:
|
||||
new_mat_image_item.height = bpy.data.images[mat_slot.material.texture_atlas_height]
|
||||
except Exception:
|
||||
name = mat_slot.material.name + "_height_replacement"
|
||||
if name in bpy.data.images:
|
||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
||||
new_mat_image_item.height = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||
new_mat_image_item.height.pixels[:] = numpy.tile(numpy.array([0.5,0.5,0.5,1.0]), 32*32)
|
||||
try:
|
||||
new_mat_image_item.roughness = bpy.data.images[mat_slot.material.texture_atlas_roughness]
|
||||
except Exception:
|
||||
name = mat_slot.material.name + "_roughness_replacement"
|
||||
if name in bpy.data.images:
|
||||
bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
|
||||
new_mat_image_item.roughness = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
|
||||
new_mat_image_item.roughness.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,0.0]), 32*32)
|
||||
|
||||
def get_or_create_image(image_name, replacement_name, default_color):
|
||||
if image_name and image_name in bpy.data.images:
|
||||
image = bpy.data.images[image_name]
|
||||
else:
|
||||
# Create a new image with the replacement name if it doesn't exist
|
||||
if replacement_name in bpy.data.images:
|
||||
image = bpy.data.images[replacement_name]
|
||||
else:
|
||||
image = bpy.data.images.new(
|
||||
name=replacement_name, width=32, height=32, alpha=True
|
||||
)
|
||||
# Set the pixel data to the default color
|
||||
num_pixels = 32 * 32
|
||||
pixel_data = numpy.tile(numpy.array(default_color), num_pixels)
|
||||
image.pixels[:] = pixel_data
|
||||
# Set use_fake_user to True to prevent Blender from removing the image
|
||||
image.use_fake_user = True
|
||||
return image
|
||||
|
||||
# Albedo
|
||||
albedo_name = getattr(mat_slot.material, 'texture_atlas_albedo', '')
|
||||
new_mat_image_item.albedo = get_or_create_image(
|
||||
albedo_name,
|
||||
mat_slot.material.name + "_albedo_replacement",
|
||||
[0.0, 0.0, 0.0, 1.0]
|
||||
)
|
||||
|
||||
# Normal
|
||||
normal_name = getattr(mat_slot.material, 'texture_atlas_normal', '')
|
||||
new_mat_image_item.normal = get_or_create_image(
|
||||
normal_name,
|
||||
mat_slot.material.name + "_normal_replacement",
|
||||
[0.5, 0.5, 1.0, 1.0]
|
||||
)
|
||||
|
||||
# Emission
|
||||
emission_name = getattr(mat_slot.material, 'texture_atlas_emission', '')
|
||||
new_mat_image_item.emission = get_or_create_image(
|
||||
emission_name,
|
||||
mat_slot.material.name + "_emission_replacement",
|
||||
[0.0, 0.0, 0.0, 1.0]
|
||||
)
|
||||
|
||||
# Ambient Occlusion
|
||||
ao_name = getattr(mat_slot.material, 'texture_atlas_ambient_occlusion', '')
|
||||
new_mat_image_item.ambient_occlusion = get_or_create_image(
|
||||
ao_name,
|
||||
mat_slot.material.name + "_ambient_occlusion_replacement",
|
||||
[1.0, 1.0, 1.0, 1.0]
|
||||
)
|
||||
|
||||
# Height
|
||||
height_name = getattr(mat_slot.material, 'texture_atlas_height', '')
|
||||
new_mat_image_item.height = get_or_create_image(
|
||||
height_name,
|
||||
mat_slot.material.name + "_height_replacement",
|
||||
[0.5, 0.5, 0.5, 1.0]
|
||||
)
|
||||
|
||||
# Roughness
|
||||
roughness_name = getattr(mat_slot.material, 'texture_atlas_roughness', '')
|
||||
new_mat_image_item.roughness = get_or_create_image(
|
||||
roughness_name,
|
||||
mat_slot.material.name + "_roughness_replacement",
|
||||
[1.0, 1.0, 1.0, 0.0]
|
||||
)
|
||||
|
||||
new_mat_image_item.material = mat_slot.material
|
||||
new_mat_image_item.parent_mesh = obj
|
||||
@@ -120,6 +139,7 @@ def get_material_images_from_scene(context: Context) -> list[MaterialImageList]:
|
||||
return material_image_list
|
||||
|
||||
|
||||
|
||||
def prep_images_in_scene(context: Context) -> list[MaterialImageList]:
|
||||
preped_images: list[MaterialImageList] = get_material_images_from_scene(context)
|
||||
for MaterialImageClass in preped_images:
|
||||
@@ -135,7 +155,6 @@ def prep_images_in_scene(context: Context) -> list[MaterialImageList]:
|
||||
|
||||
|
||||
class AvatarToolKit_OT_AtlasMaterials(Operator):
|
||||
|
||||
bl_idname = "avatar_toolkit.atlas_materials"
|
||||
bl_label = t("TextureAtlas.atlas_materials")
|
||||
bl_description = t("TextureAtlas.atlas_materials_desc")
|
||||
@@ -143,12 +162,13 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: Context) -> bool:
|
||||
return context.scene.texture_atlas_Has_Mat_List_Shown
|
||||
return context.scene.avatar_toolkit.texture_atlas_Has_Mat_List_Shown
|
||||
|
||||
def execute(self, context: Context) -> set:
|
||||
try:
|
||||
# Get only materials that are explicitly marked for inclusion
|
||||
selected_materials = [m for m in prep_images_in_scene(context) if m.material and m.material.include_in_atlas is True]
|
||||
selected_materials = [m for m in prep_images_in_scene(context)
|
||||
if m.material and m.material.avatar_toolkit.include_in_atlas is True]
|
||||
|
||||
if not selected_materials:
|
||||
self.report({'WARNING'}, t("TextureAtlas.no_materials_selected"))
|
||||
@@ -157,71 +177,75 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
|
||||
packer: BinPacker = BinPacker(selected_materials)
|
||||
mat_images = packer.fit()
|
||||
|
||||
size: list[int] = [max([matimg.fit.w + matimg.albedo.size[0] for matimg in mat_images]),
|
||||
max([matimg.fit.h + matimg.albedo.size[1] for matimg in mat_images])]
|
||||
print([matimg.fit.w + matimg.albedo.size[1] for matimg in mat_images])
|
||||
size: list[int] = [
|
||||
max([
|
||||
matimg.fit.w + matimg.albedo.size[0]
|
||||
for matimg in mat_images
|
||||
if matimg.albedo and matimg.albedo.has_data
|
||||
] or [1]),
|
||||
max([
|
||||
matimg.fit.h + matimg.albedo.size[1]
|
||||
for matimg in mat_images
|
||||
if matimg.albedo and matimg.albedo.has_data
|
||||
] or [1])
|
||||
]
|
||||
print([matimg.fit.w + matimg.albedo.size[0] for matimg in mat_images if matimg.albedo and matimg.albedo.has_data])
|
||||
|
||||
atlased_mat: MaterialImageList = MaterialImageList()
|
||||
|
||||
for mat in mat_images:
|
||||
x: int = int(mat.fit.x)
|
||||
y: int = int(mat.fit.y)
|
||||
w: int = int(mat.albedo.size[0])
|
||||
h: int = int(mat.albedo.size[1])
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH':
|
||||
mesh: Mesh = obj.data
|
||||
for layer in mesh.polygons:
|
||||
if obj.material_slots[layer.material_index].material:
|
||||
if obj.material_slots[layer.material_index].material == mat.material:
|
||||
for loop_idx in layer.loop_indices:
|
||||
layer_loops: MeshUVLoopLayer
|
||||
for layer_loops in mesh.uv_layers:
|
||||
uv_item: Float2AttributeValue = layer_loops.uv[loop_idx]
|
||||
uv_item.vector.x = (uv_item.vector.x*(w/size[0]))+(x/size[0])
|
||||
uv_item.vector.y = (uv_item.vector.y*(h/size[1]))+(y/size[1])
|
||||
|
||||
for type in ["albedo","normal", "emission","ambient_occlusion","height", "roughness"]:
|
||||
new_image_name: str= "Atlas_"+type+"_"+context.scene.name+"_"+Path(bpy.data.filepath).stem
|
||||
|
||||
print("Processing "+type+" atlas image")
|
||||
|
||||
if new_image_name in bpy.data.images:
|
||||
bpy.data.images.remove(bpy.data.images[new_image_name])
|
||||
|
||||
canvas: Image = bpy.data.images.new(name=new_image_name, width=int(size[0]),height=int(size[1]), alpha=True)
|
||||
c_w = canvas.size[0]
|
||||
canvas_pixels: list[float] = list(canvas.pixels[:])
|
||||
for mat in mat_images:
|
||||
if mat.albedo and mat.albedo.has_data:
|
||||
x: int = int(mat.fit.x)
|
||||
y: int = int(mat.fit.y)
|
||||
w: int = int(mat.albedo.size[0])
|
||||
h: int = int(mat.albedo.size[1])
|
||||
|
||||
image_var: Image = eval("mat."+type)
|
||||
|
||||
image_pixels: list[float] = list(image_var.pixels[:])
|
||||
|
||||
print("writing image \""+image_var.name+"\" to canvas.")
|
||||
print("x: \""+str(x)+"\" "+"y: \""+str(y)+"\" "+"w: \""+str(w)+"\" "+"h: \""+str(h)+"\" ")
|
||||
for k in range(0,h):
|
||||
for i in range(0, w):
|
||||
for channel in range(0,4):
|
||||
canvas_pixels[
|
||||
int((((k+y)*c_w)
|
||||
+
|
||||
(i+x))*4)
|
||||
+int(channel)
|
||||
] = image_pixels[
|
||||
int((
|
||||
(k*w)
|
||||
+i)*4)
|
||||
+int(channel)]
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == 'MESH':
|
||||
mesh: Mesh = obj.data
|
||||
for layer in mesh.polygons:
|
||||
if obj.material_slots[layer.material_index].material:
|
||||
if obj.material_slots[layer.material_index].material == mat.material:
|
||||
for loop_idx in layer.loop_indices:
|
||||
layer_loops: MeshUVLoopLayer
|
||||
for layer_loops in mesh.uv_layers:
|
||||
uv_item: Float2AttributeValue = layer_loops.uv[loop_idx]
|
||||
uv_item.vector.x = (uv_item.vector.x * (w / size[0])) + (x / size[0])
|
||||
uv_item.vector.y = (uv_item.vector.y * (h / size[1])) + (y / size[1])
|
||||
|
||||
for texture_type in ["albedo", "normal", "emission", "ambient_occlusion", "height", "roughness"]:
|
||||
new_image_name: str = f"Atlas_{texture_type}_{context.scene.name}_{Path(bpy.data.filepath).stem}"
|
||||
|
||||
print(f"Processing {texture_type} atlas image")
|
||||
|
||||
if new_image_name in bpy.data.images:
|
||||
bpy.data.images.remove(bpy.data.images[new_image_name])
|
||||
|
||||
canvas: Image = bpy.data.images.new(name=new_image_name, width=int(size[0]), height=int(size[1]), alpha=True)
|
||||
c_w = canvas.size[0]
|
||||
canvas_pixels: list[float] = list(canvas.pixels[:])
|
||||
for mat in mat_images:
|
||||
image_var: Image = getattr(mat, texture_type, None)
|
||||
if image_var and image_var.has_data:
|
||||
x: int = int(mat.fit.x)
|
||||
y: int = int(mat.fit.y)
|
||||
w: int = int(image_var.size[0])
|
||||
h: int = int(image_var.size[1])
|
||||
|
||||
image_pixels: list[float] = list(image_var.pixels[:])
|
||||
|
||||
print(f"Writing image \"{image_var.name}\" to canvas.")
|
||||
print(f"x: \"{x}\" y: \"{y}\" w: \"{w}\" h: \"{h}\"")
|
||||
for k in range(0, h):
|
||||
for i in range(0, w):
|
||||
for channel in range(0, 4):
|
||||
canvas_index = (((k + y) * c_w) + (i + x)) * 4 + channel
|
||||
image_index = ((k * w) + i) * 4 + channel
|
||||
canvas_pixels[int(canvas_index)] = image_pixels[int(image_index)]
|
||||
|
||||
canvas.pixels[:] = canvas_pixels[:]
|
||||
canvas.save(filepath=os.path.join(os.path.dirname(bpy.data.filepath),new_image_name+".png"))
|
||||
exec("atlased_mat."+type+" = canvas")
|
||||
canvas.save(filepath=os.path.join(os.path.dirname(bpy.data.filepath), new_image_name + ".png"))
|
||||
setattr(atlased_mat, texture_type, canvas)
|
||||
|
||||
#I am sorry for the amount of nodes I'm instanciating here and their values.
|
||||
#This is so that the nodes look pretty in the UI, which I think looks kinda nice. - @989onan
|
||||
@@ -285,7 +309,7 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
|
||||
if obj.type == 'MESH':
|
||||
mesh: Mesh = obj.data
|
||||
for i, mat_slot in enumerate(obj.material_slots):
|
||||
if mat_slot.material and mat_slot.material.include_in_atlas is True:
|
||||
if mat_slot.material and mat_slot.material.avatar_toolkit.include_in_atlas is True:
|
||||
mesh.materials[i] = atlased_mat.material
|
||||
|
||||
self.report({'INFO'}, t("TextureAtlas.atlas_completed"))
|
||||
|
||||
Reference in New Issue
Block a user