More Fixes

- Fixes an other error in the updater due to urllib not being imported and used correctly.
- Small Atlas Texturing fixes and UI Improvements.
This commit is contained in:
Yusarina
2024-11-26 03:12:42 +00:00
parent 346f73ebb0
commit 4d20ce77f7
4 changed files with 148 additions and 105 deletions
+18
View File
@@ -29,6 +29,24 @@ def register() -> None:
description=t("MergeArmatures.selected_armature.label") description=t("MergeArmatures.selected_armature.label")
))) )))
register_property((bpy.types.Object, "material_group_expanded", bpy.props.BoolProperty(
name="Expand Material Group",
description="Show/hide materials for this mesh",
default=False
)))
register_property((bpy.types.Material, "material_expanded", bpy.props.BoolProperty(
name="Expand Material",
description="Show/hide material properties",
default=False
)))
register_property((bpy.types.Material, "include_in_atlas", bpy.props.BoolProperty(
name=t("TextureAtlas.include_in_atlas"),
description=t("TextureAtlas.include_in_atlas_desc"),
default=True
)))
register_property((bpy.types.Scene, "merge_armature_apply_transforms", bpy.props.BoolProperty( register_property((bpy.types.Scene, "merge_armature_apply_transforms", bpy.props.BoolProperty(
default=False, default=False,
name=t("MergeArmature.merge_armatures.apply_transforms.label"), name=t("MergeArmature.merge_armatures.apply_transforms.label"),
+4 -3
View File
@@ -7,6 +7,7 @@ import shutil
import pathlib import pathlib
import zipfile import zipfile
import time import time
from urllib import request, error
from threading import Thread from threading import Thread
from bpy.app.handlers import persistent from bpy.app.handlers import persistent
from ..functions.translations import t from ..functions.translations import t
@@ -140,9 +141,9 @@ def get_github_releases() -> bool:
try: try:
ssl._create_default_https_context = ssl._create_unverified_context ssl._create_default_https_context = ssl._create_unverified_context
with urllib.request.urlopen(f'https://api.github.com/repos/{GITHUB_REPO}/releases') as url: with request.urlopen(f'https://api.github.com/repos/{GITHUB_REPO}/releases') as url:
data = json.loads(url.read().decode()) data = json.loads(url.read().decode())
except urllib.error.URLError: except error.URLError:
print('URL ERROR') print('URL ERROR')
return False return False
@@ -214,7 +215,7 @@ def download_file(update_url: str) -> None:
try: try:
ssl._create_default_https_context = ssl._create_unverified_context ssl._create_default_https_context = ssl._create_unverified_context
urllib.request.urlretrieve(update_url, update_zip_file) urllib.request.urlretrieve(update_url, update_zip_file)
except urllib.error.URLError: except error.URLError:
finish_update(error=t('download_file.cantConnect')) finish_update(error=t('download_file.cantConnect'))
return return
+62 -46
View File
@@ -10,6 +10,20 @@ from ..core.common import SceneMatClass, MaterialListBool
from ..core.packer.rectangle_packer import MaterialImageList, BinPacker from ..core.packer.rectangle_packer import MaterialImageList, BinPacker
from ..functions.translations import t from ..functions.translations import t
class MaterialImageList:
def __init__(self):
self.albedo: Image = None
self.normal: Image = None
self.emission: Image = None
self.ambient_occlusion: Image = None
self.height: Image = None
self.roughness: Image = None
self.material: Material = None
self.parent_mesh: Object = None
self.w: int = 0
self.h: int = 0
self.fit = None
def scale_images_to_largest(images:list[Image]) -> set: def scale_images_to_largest(images:list[Image]) -> set:
print([image.name for image in images]) print([image.name for image in images])
x: int=0 x: int=0
@@ -38,64 +52,67 @@ def MaterialImageList_to_Image_list(classitem: MaterialImageList) -> list[Image]
def get_material_images_from_scene(context: Context) -> list[MaterialImageList]: def get_material_images_from_scene(context: Context) -> list[MaterialImageList]:
mat: SceneMatClass = None
material_image_list: list[MaterialImageList] = [] material_image_list: list[MaterialImageList] = []
for mat in context.scene.materials:
new_mat_image_item: MaterialImageList = MaterialImageList() for obj in context.scene.objects:
if obj.type == 'MESH':
for mat_slot in obj.material_slots:
if mat_slot.material and mat_slot.material.include_in_atlas:
new_mat_image_item = MaterialImageList()
try: try:
new_mat_image_item.albedo = bpy.data.images[mat.mat.texture_atlas_albedo] new_mat_image_item.albedo = bpy.data.images[mat_slot.material.texture_atlas_albedo]
except Exception as e: except Exception:
name: str = mat.mat.name+"_albedo_replacement" name = mat_slot.material.name + "_albedo_replacement"
if name in bpy.data.images: if name in bpy.data.images:
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) 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 = 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) new_mat_image_item.albedo.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
try: try:
new_mat_image_item.normal = bpy.data.images[mat.mat.texture_atlas_normal] new_mat_image_item.normal = bpy.data.images[mat_slot.material.texture_atlas_normal]
except Exception: except Exception:
name: str = mat.mat.name+"_normal_replacement" name = mat_slot.material.name + "_normal_replacement"
if name in bpy.data.images: if name in bpy.data.images:
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) 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 = 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) new_mat_image_item.normal.pixels[:] = numpy.tile(numpy.array([0.5,0.5,1.0,1.0]), 32*32)
try: try:
new_mat_image_item.emission = bpy.data.images[mat.mat.texture_atlas_emission] new_mat_image_item.emission = bpy.data.images[mat_slot.material.texture_atlas_emission]
except Exception: except Exception:
name: str = mat.mat.name+"_emission_replacement" name = mat_slot.material.name + "_emission_replacement"
if name in bpy.data.images: if name in bpy.data.images:
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) 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 = 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) new_mat_image_item.emission.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
try: try:
new_mat_image_item.ambient_occlusion = bpy.data.images[mat.mat.texture_atlas_ambient_occlusion] new_mat_image_item.ambient_occlusion = bpy.data.images[mat_slot.material.texture_atlas_ambient_occlusion]
except Exception: except Exception:
name: str = mat.mat.name+"_ambient_occlusion_replacement" name = mat_slot.material.name + "_ambient_occlusion_replacement"
if name in bpy.data.images: if name in bpy.data.images:
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) 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 = 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) new_mat_image_item.ambient_occlusion.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,1.0]), 32*32)
try: try:
new_mat_image_item.height = bpy.data.images[mat.mat.texture_atlas_height] new_mat_image_item.height = bpy.data.images[mat_slot.material.texture_atlas_height]
except Exception: except Exception:
name: str = mat.mat.name+"_height_replacement" name = mat_slot.material.name + "_height_replacement"
if name in bpy.data.images: if name in bpy.data.images:
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) 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 = 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) new_mat_image_item.height.pixels[:] = numpy.tile(numpy.array([0.5,0.5,0.5,1.0]), 32*32)
try: try:
new_mat_image_item.roughness = bpy.data.images[mat.mat.texture_atlas_roughness] new_mat_image_item.roughness = bpy.data.images[mat_slot.material.texture_atlas_roughness]
except Exception: except Exception:
name: str = mat.mat.name+"_roughness_replacement" name = mat_slot.material.name + "_roughness_replacement"
if name in bpy.data.images: if name in bpy.data.images:
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) 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 = 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) new_mat_image_item.roughness.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,0.0]), 32*32)
new_mat_image_item.material = mat.mat
material_image_list.append(new_mat_image_item)
return material_image_list
new_mat_image_item.material = mat_slot.material
new_mat_image_item.parent_mesh = obj
material_image_list.append(new_mat_image_item)
return material_image_list
def prep_images_in_scene(context: Context) -> list[MaterialImageList]: def prep_images_in_scene(context: Context) -> list[MaterialImageList]:
preped_images: list[MaterialImageList] = get_material_images_from_scene(context) preped_images: list[MaterialImageList] = get_material_images_from_scene(context)
@@ -124,13 +141,16 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
def execute(self, context: Context) -> set: def execute(self, context: Context) -> set:
try: try:
mat_images: list[MaterialImageList] = prep_images_in_scene(context) # Only get materials marked for atlas creation
mat_images: list[MaterialImageList] = [m for m in prep_images_in_scene(context) if m.material.include_in_atlas]
if not mat_images:
self.report({'WARNING'}, t("TextureAtlas.no_materials_selected"))
return {'CANCELLED'}
packer: BinPacker = BinPacker(mat_images) packer: BinPacker = BinPacker(mat_images)
mat_images = packer.fit() mat_images = packer.fit()
size: list[int] = [max([matimg.fit.w + matimg.albedo.size[0] for matimg in mat_images]), 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])] 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]) print([matimg.fit.w + matimg.albedo.size[1] for matimg in mat_images])
@@ -144,9 +164,8 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
h: int = int(mat.albedo.size[1]) h: int = int(mat.albedo.size[1])
for obj in bpy.data.objects: for obj in bpy.data.objects:
if obj.type == 'MESH':
mesh: Mesh = obj.data mesh: Mesh = obj.data
for layer in mesh.polygons: for layer in mesh.polygons:
if obj.material_slots[layer.material_index].material: if obj.material_slots[layer.material_index].material:
if obj.material_slots[layer.material_index].material == mat.material: if obj.material_slots[layer.material_index].material == mat.material:
@@ -167,7 +186,6 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
canvas: Image = bpy.data.images.new(name=new_image_name, width=int(size[0]),height=int(size[1]), alpha=True) 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] c_w = canvas.size[0]
#c_h = canvas.size[1]
canvas_pixels: list[float] = list(canvas.pixels[:]) canvas_pixels: list[float] = list(canvas.pixels[:])
for mat in mat_images: for mat in mat_images:
x: int = int(mat.fit.x) x: int = int(mat.fit.x)
@@ -199,14 +217,12 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
canvas.save(filepath=os.path.join(os.path.dirname(bpy.data.filepath),new_image_name+".png")) canvas.save(filepath=os.path.join(os.path.dirname(bpy.data.filepath),new_image_name+".png"))
exec("atlased_mat."+type+" = canvas") exec("atlased_mat."+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
atlased_mat.material = bpy.data.materials.new(name="Atlas_Final_"+bpy.context.scene.name+"_"+Path(bpy.data.filepath).stem) atlased_mat.material = bpy.data.materials.new(name="Atlas_Final_"+bpy.context.scene.name+"_"+Path(bpy.data.filepath).stem)
atlased_mat.material.use_nodes = True atlased_mat.material.use_nodes = True
atlased_mat.material.node_tree.nodes.clear() atlased_mat.material.node_tree.nodes.clear()
#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
principled_node: ShaderNodeBsdfPrincipled = atlased_mat.material.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled") principled_node: ShaderNodeBsdfPrincipled = atlased_mat.material.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled")
principled_node.location.x = 7.29706335067749 principled_node.location.x = 7.29706335067749
principled_node.location.y = 298.918212890625 principled_node.location.y = 298.918212890625
@@ -258,11 +274,12 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
atlased_mat.material.node_tree.links.new(output_node.inputs["Surface"], principled_node.outputs["BSDF"]) atlased_mat.material.node_tree.links.new(output_node.inputs["Surface"], principled_node.outputs["BSDF"])
atlased_mat.material.node_tree.links.new(normal_map_node.inputs["Color"], normal_node.outputs["Color"]) atlased_mat.material.node_tree.links.new(normal_map_node.inputs["Color"], normal_node.outputs["Color"])
# Only update materials for meshes that had materials included in the atlas
for obj in context.scene.objects: for obj in context.scene.objects:
if obj.type == 'MESH':
if any(mat_slot.material and mat_slot.material.include_in_atlas for mat_slot in obj.material_slots):
mesh: Mesh = obj.data mesh: Mesh = obj.data
mesh.materials.clear() mesh.materials.clear()
mesh.materials.append(atlased_mat.material) mesh.materials.append(atlased_mat.material)
self.report({'INFO'}, t("TextureAtlas.atlas_completed")) self.report({'INFO'}, t("TextureAtlas.atlas_completed"))
@@ -272,4 +289,3 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
raise e raise e
return {"FINISHED"} return {"FINISHED"}
+22 -14
View File
@@ -40,22 +40,25 @@ class AvatarToolKit_UL_MaterialTextureAtlasProperties(UIList):
bl_space_type = 'VIEW_3D' bl_space_type = 'VIEW_3D'
bl_region_type = 'UI' bl_region_type = 'UI'
def draw_item(self , context: Context, layout: UILayout, data: bpy.types.Object, item:SceneMatClass, icon, active_data, active_propname, index): def draw_item(self, context: Context, layout: UILayout, data: Object, item: SceneMatClass, icon, active_data, active_propname, index):
if context.scene.texture_atlas_Has_Mat_List_Shown: if context.scene.texture_atlas_Has_Mat_List_Shown:
box = layout.box() box = layout.box()
row = box.row() row = box.row()
row.label(text=item.mat.name, icon = "MATERIAL")
col = box.row() # Draw material entry
row.prop(item.mat, "material_expanded",
text=item.mat.name,
icon='DOWNARROW_HLT' if item.mat.material_expanded else 'RIGHTARROW',
emboss=False)
row.prop(item.mat, "include_in_atlas", text="")
if item.mat.material_expanded and item.mat.include_in_atlas:
col = box.column(align=True)
col.prop(item.mat, "texture_atlas_albedo") col.prop(item.mat, "texture_atlas_albedo")
col = box.row()
col.prop(item.mat, "texture_atlas_normal") col.prop(item.mat, "texture_atlas_normal")
col = box.row()
col.prop(item.mat, "texture_atlas_emission") col.prop(item.mat, "texture_atlas_emission")
col = box.row()
col.prop(item.mat, "texture_atlas_ambient_occlusion") col.prop(item.mat, "texture_atlas_ambient_occlusion")
col = box.row()
col.prop(item.mat, "texture_atlas_height") col.prop(item.mat, "texture_atlas_height")
col = box.row()
col.prop(item.mat, "texture_atlas_roughness") col.prop(item.mat, "texture_atlas_roughness")
@register_wrap @register_wrap
@@ -74,7 +77,6 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel):
if armature: if armature:
layout.label(text=t("TextureAtlas.label"), icon='TEXTURE') layout.label(text=t("TextureAtlas.label"), icon='TEXTURE')
layout.separator(factor=0.5) layout.separator(factor=0.5)
box = layout.box() box = layout.box()
@@ -86,16 +88,22 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel):
if context.scene.texture_atlas_Has_Mat_List_Shown: if context.scene.texture_atlas_Has_Mat_List_Shown:
row = box.row() row = box.row()
row.template_list(AvatarToolKit_UL_MaterialTextureAtlasProperties.bl_idname, 'material_list', row.template_list(AvatarToolKit_UL_MaterialTextureAtlasProperties.bl_idname,
context.scene, 'materials', context.scene, 'texture_atlas_material_index', 'material_list',
rows=12, type='DEFAULT') context.scene,
'materials',
context.scene,
'texture_atlas_material_index',
rows=12,
type='DEFAULT')
layout.separator(factor=1.0) layout.separator(factor=1.0)
row = layout.row() row = layout.row()
row.scale_y = 1.5 row.scale_y = 1.5
row.operator(AvatarToolKit_OT_AtlasMaterials.bl_idname, text=t("TextureAtlas.atlas_materials"), icon='NODE_TEXTURE') row.operator(AvatarToolKit_OT_AtlasMaterials.bl_idname,
text=t("TextureAtlas.atlas_materials"),
icon='NODE_TEXTURE')
else: else:
layout.label(text=t("Tools.select_armature"), icon='ERROR') layout.label(text=t("Tools.select_armature"), icon='ERROR')