Merge pull request #77 from Yusarina/bug-fixes

Bug fixes
This commit is contained in:
Yusarina
2024-12-01 15:34:05 +00:00
committed by GitHub
9 changed files with 644 additions and 320 deletions
+86 -96
View File
@@ -113,10 +113,16 @@ def get_armature(context: Context, armature_name: Optional[str] = None) -> Optio
return next((obj for obj in context.view_layer.objects if obj.type == 'ARMATURE'), None) return next((obj for obj in context.view_layer.objects if obj.type == 'ARMATURE'), None)
def get_armatures(self, context: Context) -> List[Tuple[str, str, str]]: def get_armatures(self, context: Context) -> List[Tuple[str, str, str]]:
return [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'ARMATURE'] armatures = [(obj.name, obj.name, "") for obj in bpy.data.objects if obj.type == 'ARMATURE']
if not armatures:
return [('NONE', 'No Armature', '')]
return armatures
def get_armatures_that_are_not_selected(self, context: Context) -> List[Tuple[str, str, str]]: def get_armatures_that_are_not_selected(self, context: Context) -> List[Tuple[str, str, str]]:
return [(obj.name, obj.name, "") for obj in bpy.data.objects if ((obj.type == 'ARMATURE') and (obj.name != context.scene.selected_armature))] armatures = [(obj.name, obj.name, "") for obj in bpy.data.objects if ((obj.type == 'ARMATURE') and (obj.name != context.scene.selected_armature))]
if not armatures:
return [('NONE', 'No Other Armature', '')]
return armatures
def get_selected_armature(context: Context) -> Optional[Object]: def get_selected_armature(context: Context) -> Optional[Object]:
try: try:
@@ -211,106 +217,90 @@ def apply_shapekey_to_basis(context: bpy.types.Context, obj: bpy.types.Object, s
mesh.shape_keys.key_blocks[shape_key_name].name = shape_key_name + "_reversed" mesh.shape_keys.key_blocks[shape_key_name].name = shape_key_name + "_reversed"
return True return True
def apply_pose_as_rest(context: Context, armature_obj: bpy.types.Object, meshes: list[bpy.types.Object]) -> bool: def apply_pose_as_rest(context: Context, armature_obj: Object, meshes: list[Object]) -> bool:
for obj in meshes: for mesh_obj in meshes:
mesh_data: Mesh = obj.data if not mesh_obj.data:
continue
if mesh_data.shape_keys: if mesh_obj.data.shape_keys and mesh_obj.data.shape_keys.key_blocks:
shape_key_obj_list: list[bpy.types.Object] = [] if len(mesh_obj.data.shape_keys.key_blocks) == 1:
modifier_armature_name: str = "" basis = mesh_obj.data.shape_keys.key_blocks[0]
basis_name = basis.name
for modifier in obj.modifiers: mesh_obj.shape_key_remove(basis)
if modifier.type == "ARMATURE": apply_armature_to_mesh(armature_obj, mesh_obj)
arm_modifier: bpy.types.ArmatureModifier = modifier mesh_obj.shape_key_add(name=basis_name)
if not (arm_modifier.object == armature_obj): else:
continue apply_armature_to_mesh_with_shapekeys(armature_obj, mesh_obj, context)
modifier_armature_name = arm_modifier.object.name
if modifier_armature_name == "":
continue
for idx,shape in enumerate(mesh_data.shape_keys.key_blocks):
if idx == 0:
continue
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
context.view_layer.objects.active = obj
obj.select_set(True)
#create duplicate of object
bpy.ops.object.duplicate()
shape_obj = context.view_layer.objects.active
#make current shapekey a separate object
shape_obj.active_shape_key_index = idx
shape_obj.name = shape.name
bpy.ops.object.shape_key_move(type="TOP")
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.shape_key_remove(all=True)
bpy.ops.object.modifier_apply(modifier=modifier_armature_name)
#for modifier_name in [i.name for i in shape_obj.modifiers]:
# bpy.ops.object.modifier_remove(modifier=modifier_name)
shape_key_obj_list.append(shape_obj) #add to a list of shape key objects
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="OBJECT")
context.view_layer.objects.active.select_set(True)
bpy.ops.object.shape_key_remove(all=True)
bpy.ops.object.modifier_apply(modifier=modifier_armature_name)
bpy.ops.object.select_all(action="DESELECT")
for shapekey_obj in shape_key_obj_list:
shapekey_obj.select_set(True)
context.view_layer.objects.active = obj
context.view_layer.objects.active.select_set(True)
try:
bpy.ops.object.join_shapes()
except:
#delete shapekey objects to not leave ourselves in a bad exit state - @989onan
context.view_layer.objects.active = shape_key_obj_list[0]
obj.select_set(False)
bpy.ops.object.delete(confirm=False)
return False
context.view_layer.objects.active = shape_key_obj_list[0]
obj.select_set(False)
bpy.ops.object.delete(confirm=False)
else: else:
modifier_armature_name: str = "" apply_armature_to_mesh(armature_obj, mesh_obj)
for modifier in obj.modifiers:
if modifier.type == "ARMATURE":
arm_modifier: bpy.types.ArmatureModifier = modifier
if not (arm_modifier.object == armature_obj):
continue
modifier_armature_name = arm_modifier.object.name
if modifier_armature_name == "":
continue
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
context.view_layer.objects.active.select_set(True)
bpy.ops.object.modifier_apply(modifier=modifier_armature_name)
context.view_layer.objects.active = armature_obj
armature_obj.select_set(True)
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="POSE")
bpy.ops.object.mode_set(mode='POSE')
bpy.ops.pose.armature_apply(selected=False) bpy.ops.pose.armature_apply(selected=False)
bpy.ops.object.mode_set(mode='OBJECT')
return True return True
def apply_armature_to_mesh(armature_obj: Object, mesh_obj: Object) -> None:
armature_mod = mesh_obj.modifiers.new('PoseToRest', 'ARMATURE')
armature_mod.object = armature_obj
if bpy.app.version >= (3, 5):
mesh_obj.modifiers.move(mesh_obj.modifiers.find(armature_mod.name), 0)
else:
for _ in range(len(mesh_obj.modifiers) - 1):
bpy.ops.object.modifier_move_up(modifier=armature_mod.name)
with bpy.context.temp_override(object=mesh_obj):
bpy.ops.object.modifier_apply(modifier=armature_mod.name)
def apply_armature_to_mesh_with_shapekeys(armature_obj: Object, mesh_obj: Object, context: Context) -> None:
old_active_index = mesh_obj.active_shape_key_index
old_show_only = mesh_obj.show_only_shape_key
mesh_obj.show_only_shape_key = True
shape_keys = mesh_obj.data.shape_keys.key_blocks
vertex_groups = []
mutes = []
for sk in shape_keys:
vertex_groups.append(sk.vertex_group)
sk.vertex_group = ''
mutes.append(sk.mute)
sk.mute = False
disabled_mods = []
for mod in mesh_obj.modifiers:
if mod.show_viewport:
mod.show_viewport = False
disabled_mods.append(mod)
arm_mod = mesh_obj.modifiers.new('PoseToRest', 'ARMATURE')
arm_mod.object = armature_obj
co_length = len(mesh_obj.data.vertices) * 3
eval_cos = np.empty(co_length, dtype=np.single)
for i, shape_key in enumerate(shape_keys):
mesh_obj.active_shape_key_index = i
depsgraph = context.evaluated_depsgraph_get()
eval_mesh = mesh_obj.evaluated_get(depsgraph)
eval_mesh.data.vertices.foreach_get('co', eval_cos)
shape_key.data.foreach_set('co', eval_cos)
if i == 0:
mesh_obj.data.vertices.foreach_set('co', eval_cos)
for mod in disabled_mods:
mod.show_viewport = True
mesh_obj.modifiers.remove(arm_mod)
for sk, vg, mute in zip(shape_keys, vertex_groups, mutes):
sk.vertex_group = vg
sk.mute = mute
mesh_obj.active_shape_key_index = old_active_index
mesh_obj.show_only_shape_key = old_show_only
def get_all_meshes(context: Context) -> List[Object]: def get_all_meshes(context: Context) -> List[Object]:
armature = get_selected_armature(context) armature = get_selected_armature(context)
if armature and is_valid_armature(armature): if armature and is_valid_armature(armature):
+31 -5
View File
@@ -23,10 +23,28 @@ def register() -> None:
description=t("VisemePanel.selected_mesh.desc") description=t("VisemePanel.selected_mesh.desc")
))) )))
register_property((bpy.types.Scene, "merge_armature_source", bpy.props.EnumProperty( register_property((bpy.types.Object, "material_group_expanded", bpy.props.BoolProperty(
items=get_armatures_that_are_not_selected, name="Expand Material Group",
name=t("MergeArmatures.selected_armature.label"), description="Show/hide materials for this mesh",
description=t("MergeArmatures.selected_armature.label") 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.Scene, "material_search_filter", bpy.props.StringProperty(
name="Search Materials",
description="Filter materials by name",
default=""
)))
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(
@@ -73,7 +91,15 @@ def register() -> None:
register_property((bpy.types.Scene, "selected_armature", bpy.props.EnumProperty( register_property((bpy.types.Scene, "selected_armature", bpy.props.EnumProperty(
items=get_armatures, items=get_armatures,
name=t("Quick_Access.selected_armature.label"), name=t("Quick_Access.selected_armature.label"),
description=t("Quick_Access.selected_armature.desc") description=t("Quick_Access.selected_armature.desc"),
default=0
)))
register_property((bpy.types.Scene, "merge_armature_source", bpy.props.EnumProperty(
items=get_armatures_that_are_not_selected,
name=t("MergeArmatures.selected_armature.label"),
description=t("MergeArmatures.selected_armature.label"),
default=0
))) )))
register_property((bpy.types.Scene, "avatar_toolkit_updater_version_list", bpy.props.EnumProperty( register_property((bpy.types.Scene, "avatar_toolkit_updater_version_list", bpy.props.EnumProperty(
+10 -1
View File
@@ -26,10 +26,19 @@ def register_properties():
setattr(prop[0], prop[1], prop[2]) setattr(prop[0], prop[1], prop[2])
else: else:
prop() prop()
def clear_registration():
__bl_classes.clear()
__bl_ordered_classes.clear()
__bl_props.clear()
def unregister_properties(): def unregister_properties():
for prop in reversed(__bl_props): for prop in reversed(__bl_props):
delattr(prop[0], prop[1]) try:
delattr(prop[0], prop[1])
except AttributeError:
continue
clear_registration()
#- @989onan had to add this from Cats. This is extremely important else you will be screamed at by register order issues! #- @989onan had to add this from Cats. This is extremely important else you will be screamed at by register order issues!
# Find order to register to solve dependencies # Find order to register to solve dependencies
+8 -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
@@ -191,6 +192,10 @@ def finish_update_checking(error: str = '') -> None:
return None # Important for bpy.app.timers return None # Important for bpy.app.timers
def update_now(latest: bool = False) -> None: def update_now(latest: bool = False) -> None:
if not version_list:
print("No version list available. Please check for updates first.")
return
if latest: if latest:
update_link = version_list[latest_version_str][0] update_link = version_list[latest_version_str][0]
else: else:
@@ -210,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
+36 -21
View File
@@ -64,25 +64,38 @@ class AvatarToolkit_OT_ApplyPoseAsShapekey(Operator):
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return get_selected_armature(context) != None and context.mode == "POSE" armature = common.get_selected_armature(context)
return armature and context.mode == 'POSE'
def execute(self, context: Context):
bpy.ops.object.mode_set(mode="OBJECT")
for obj in get_all_meshes(context):
modifier_armature_name: str = ""
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.select_all(action="DESELECT")
context.view_layer.objects.active = obj
obj.select_set(True)
for modifier in obj.modifiers:
if modifier.type == "ARMATURE":
arm_modifier: bpy.types.ArmatureModifier = modifier
modifier_armature_name = arm_modifier.object.name
bpy.ops.object.modifier_apply_as_shapekey(modifier=modifier_armature_name,keep_modifier=True,report=True)
def execute(self, context):
armature_obj = common.get_selected_armature(context)
mesh_objects = common.get_all_meshes(context)
for mesh_obj in mesh_objects:
if not mesh_obj.data:
continue
# Ensure basis exists
if not mesh_obj.data.shape_keys:
mesh_obj.shape_key_add(name='Basis')
# Store current pose as new shapekey
new_shape = mesh_obj.shape_key_add(name='Pose_Shapekey', from_mix=False)
# Evaluate mesh in current pose
depsgraph = context.evaluated_depsgraph_get()
eval_mesh = mesh_obj.evaluated_get(depsgraph)
# Apply evaluated vertices to new shapekey
for i, v in enumerate(eval_mesh.data.vertices):
new_shape.data[i].co = v.co.copy()
# Reset pose
bpy.ops.pose.select_all(action='SELECT')
bpy.ops.pose.transforms_clear()
bpy.ops.object.mode_set(mode='OBJECT')
self.report({'INFO'}, t('Tools.apply_pose_as_rest.success'))
return {'FINISHED'} return {'FINISHED'}
@register_wrap @register_wrap
@@ -97,12 +110,14 @@ class AvatarToolkit_OT_ApplyPoseAsRest(Operator):
return get_selected_armature(context) != None and context.mode == "POSE" return get_selected_armature(context) != None and context.mode == "POSE"
def execute(self, context: Context): def execute(self, context: Context):
if not common.apply_pose_as_rest(armature_obj=get_selected_armature(context),
if common.apply_pose_as_rest(armature_obj=get_selected_armature(context),meshes=get_all_meshes(context), context=context): meshes=get_all_meshes(context),
context=context):
self.report({'ERROR'}, t("Quick_Access.apply_armature_failed")) self.report({'ERROR'}, t("Quick_Access.apply_armature_failed"))
return {'FINISHED'} return {'CANCELLED'}
return {'FINISHED'} return {'FINISHED'}
@register_wrap @register_wrap
class AvatarToolkit_OT_RemoveZeroWeightBones(Operator): class AvatarToolkit_OT_RemoveZeroWeightBones(Operator):
bl_idname = "avatar_toolkit.remove_zero_weight_bones" bl_idname = "avatar_toolkit.remove_zero_weight_bones"
+115 -92
View File
@@ -10,19 +10,38 @@ 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
def scale_images_to_largest(images:list[Image]) -> set: class MaterialImageList:
print([image.name for image in images]) def __init__(self):
x: int=0 self.albedo: Image = None
y: int=0 self.normal: Image = None
for image in images: self.emission: Image = None
x = max(x,image.size[0]) self.ambient_occlusion: Image = None
y = max(y,image.size[1]) self.height: Image = None
print(x,y) 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:
x: int = 0
y: int = 0
for image in images: # 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
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)) image.scale(width=int(x), height=int(y))
return x,y return x, y
def MaterialImageList_to_Image_list(classitem: MaterialImageList) -> list[Image]: def MaterialImageList_to_Image_list(classitem: MaterialImageList) -> list[Image]:
list_of_images: list[Image] = [] list_of_images: list[Image] = []
@@ -38,62 +57,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:
try: if obj.type == 'MESH':
new_mat_image_item.albedo = bpy.data.images[mat.mat.texture_atlas_albedo] for mat_slot in obj.material_slots:
except Exception as e: # Only process materials that are selected for atlas
name: str = mat.mat.name+"_albedo_replacement" if mat_slot.material and mat_slot.material.include_in_atlas is True:
if name in bpy.data.images: new_mat_image_item = MaterialImageList()
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) try:
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[mat_slot.material.texture_atlas_albedo]
new_mat_image_item.albedo.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32) except Exception:
try: name = mat_slot.material.name + "_albedo_replacement"
new_mat_image_item.normal = bpy.data.images[mat.mat.texture_atlas_normal] if name in bpy.data.images:
except Exception: bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
name: str = mat.mat.name+"_normal_replacement" new_mat_image_item.albedo = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
if name in bpy.data.images: new_mat_image_item.albedo.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) try:
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[mat_slot.material.texture_atlas_normal]
new_mat_image_item.normal.pixels[:] = numpy.tile(numpy.array([0.5,0.5,1.0,1.0]), 32*32) except Exception:
try: name = mat_slot.material.name + "_normal_replacement"
new_mat_image_item.emission = bpy.data.images[mat.mat.texture_atlas_emission] if name in bpy.data.images:
except Exception: bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
name: str = mat.mat.name+"_emission_replacement" new_mat_image_item.normal = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
if name in bpy.data.images: new_mat_image_item.normal.pixels[:] = numpy.tile(numpy.array([0.5,0.5,1.0,1.0]), 32*32)
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) try:
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[mat_slot.material.texture_atlas_emission]
new_mat_image_item.emission.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32) except Exception:
name = mat_slot.material.name + "_emission_replacement"
try: if name in bpy.data.images:
new_mat_image_item.ambient_occlusion = bpy.data.images[mat.mat.texture_atlas_ambient_occlusion] bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
except Exception: new_mat_image_item.emission = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
name: str = mat.mat.name+"_ambient_occlusion_replacement" new_mat_image_item.emission.pixels[:] = numpy.tile(numpy.array([0.0,0.0,0.0,1.0]), 32*32)
if name in bpy.data.images: try:
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) new_mat_image_item.ambient_occlusion = bpy.data.images[mat_slot.material.texture_atlas_ambient_occlusion]
new_mat_image_item.ambient_occlusion = bpy.data.images.new(name=name,width=32,height=32, alpha=True) except Exception:
new_mat_image_item.ambient_occlusion.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,1.0]), 32*32) name = mat_slot.material.name + "_ambient_occlusion_replacement"
try: if name in bpy.data.images:
new_mat_image_item.height = bpy.data.images[mat.mat.texture_atlas_height] bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
except Exception: new_mat_image_item.ambient_occlusion = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
name: str = mat.mat.name+"_height_replacement" new_mat_image_item.ambient_occlusion.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,1.0]), 32*32)
if name in bpy.data.images: try:
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) new_mat_image_item.height = bpy.data.images[mat_slot.material.texture_atlas_height]
new_mat_image_item.height = bpy.data.images.new(name=name,width=32,height=32, alpha=True) except Exception:
new_mat_image_item.height.pixels[:] = numpy.tile(numpy.array([0.5,0.5,0.5,1.0]), 32*32) name = mat_slot.material.name + "_height_replacement"
if name in bpy.data.images:
try: bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
new_mat_image_item.roughness = bpy.data.images[mat.mat.texture_atlas_roughness] new_mat_image_item.height = bpy.data.images.new(name=name, width=32, height=32, alpha=True)
except Exception: new_mat_image_item.height.pixels[:] = numpy.tile(numpy.array([0.5,0.5,0.5,1.0]), 32*32)
name: str = mat.mat.name+"_roughness_replacement" try:
if name in bpy.data.images: new_mat_image_item.roughness = bpy.data.images[mat_slot.material.texture_atlas_roughness]
bpy.data.images.remove(image=bpy.data.images[name],do_unlink=True) except Exception:
new_mat_image_item.roughness = bpy.data.images.new(name=name,width=32,height=32, alpha=True) name = mat_slot.material.name + "_roughness_replacement"
new_mat_image_item.roughness.pixels[:] = numpy.tile(numpy.array([1.0,1.0,1.0,0.0]), 32*32) if name in bpy.data.images:
new_mat_image_item.material = mat.mat bpy.data.images.remove(image=bpy.data.images[name], do_unlink=True)
material_image_list.append(new_mat_image_item) 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.material = mat_slot.material
new_mat_image_item.parent_mesh = obj
material_image_list.append(new_mat_image_item)
return material_image_list return material_image_list
@@ -124,13 +148,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) # 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]
packer: BinPacker = BinPacker(mat_images)
if not selected_materials:
self.report({'WARNING'}, t("TextureAtlas.no_materials_selected"))
return {'CANCELLED'}
packer: BinPacker = BinPacker(selected_materials)
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,18 +171,17 @@ 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:
mesh: Mesh = obj.data if obj.type == 'MESH':
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: for loop_idx in layer.loop_indices:
for loop_idx in layer.loop_indices: layer_loops: MeshUVLoopLayer
layer_loops: MeshUVLoopLayer for layer_loops in mesh.uv_layers:
for layer_loops in mesh.uv_layers: uv_item: Float2AttributeValue = layer_loops.uv[loop_idx]
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.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])
uv_item.vector.y = (uv_item.vector.y*(h/size[1]))+(y/size[1])
for type in ["albedo","normal", "emission","ambient_occlusion","height", "roughness"]: for type in ["albedo","normal", "emission","ambient_occlusion","height", "roughness"]:
new_image_name: str= "Atlas_"+type+"_"+context.scene.name+"_"+Path(bpy.data.filepath).stem new_image_name: str= "Atlas_"+type+"_"+context.scene.name+"_"+Path(bpy.data.filepath).stem
@@ -167,7 +193,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 +224,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,12 +281,13 @@ 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 selected materials for meshes
for obj in context.scene.objects: for obj in context.scene.objects:
mesh: Mesh = obj.data if obj.type == 'MESH':
mesh.materials.clear() mesh: Mesh = obj.data
for i, mat_slot in enumerate(obj.material_slots):
mesh.materials.append(atlased_mat.material) if mat_slot.material and mat_slot.material.include_in_atlas is True:
mesh.materials[i] = atlased_mat.material
self.report({'INFO'}, t("TextureAtlas.atlas_completed")) self.report({'INFO'}, t("TextureAtlas.atlas_completed"))
return {"FINISHED"} return {"FINISHED"}
@@ -271,5 +295,4 @@ class AvatarToolKit_OT_AtlasMaterials(Operator):
self.report({'ERROR'}, t("TextureAtlas.atlas_error")) self.report({'ERROR'}, t("TextureAtlas.atlas_error"))
raise e raise e
return {"FINISHED"} return {"FINISHED"}
+15 -3
View File
@@ -169,6 +169,10 @@
"Tools.remove_zero_weight_bones.threshold.label": "Weight Threshold", "Tools.remove_zero_weight_bones.threshold.label": "Weight Threshold",
"Tools.remove_zero_weight_bones.threshold.desc": "If a bone is not weighted to any part of any mesh under the armature with a threshold greater than this, it is removed", "Tools.remove_zero_weight_bones.threshold.desc": "If a bone is not weighted to any part of any mesh under the armature with a threshold greater than this, it is removed",
"Tools.connect_bones.label": "Connect Bones", "Tools.connect_bones.label": "Connect Bones",
"Tools.bone_tools.label": "Bone Tools",
"Tools.additional_tools.label": "Additional Tools",
"Tools.merge_twist_bones.label": "Merge Twist Bones",
"Tools.merge_twist_bones.desc": "Merge twist bones into their parent bones",
"Tools.connect_bones.desc": "Connect bones with their respective children", "Tools.connect_bones.desc": "Connect bones with their respective children",
"Tools.connect_bones.invalid_armature": "Invalid armature selected", "Tools.connect_bones.invalid_armature": "Invalid armature selected",
"Tools.connect_bones.min_distance.label": "Minimum Distance", "Tools.connect_bones.min_distance.label": "Minimum Distance",
@@ -223,6 +227,9 @@
"VisemePanel.start_viseme_creation": "Starting viseme creation...", "VisemePanel.start_viseme_creation": "Starting viseme creation...",
"VisemePanel.viseme_created_successfully": "Viseme {viseme_name} created successfully", "VisemePanel.viseme_created_successfully": "Viseme {viseme_name} created successfully",
"VisemePanel.viseme_creation_completed": "Viseme creation completed.", "VisemePanel.viseme_creation_completed": "Viseme creation completed.",
"MMDOptions.title": "MMD Options",
"MMDOptions.no_armature_selected": "No armature selected",
"MMDOptions.label": "MMD Options",
"MMDOptions.cleanup_mesh.label": "Cleanup Mesh", "MMDOptions.cleanup_mesh.label": "Cleanup Mesh",
"MMDOptions.cleanup_mesh.desc": "Clean up the mesh by removing empty objects, unused vertex groups, unused vertices, and empty shape keys", "MMDOptions.cleanup_mesh.desc": "Clean up the mesh by removing empty objects, unused vertex groups, unused vertices, and empty shape keys",
"MMDOptions.removing_empty_objects": "Removing empty objects", "MMDOptions.removing_empty_objects": "Removing empty objects",
@@ -249,7 +256,8 @@
"MMDOptions.renaming_bones": "Renaming bones", "MMDOptions.renaming_bones": "Renaming bones",
"MMDOptions.armature_optimization_complete": "Armature optimization complete", "MMDOptions.armature_optimization_complete": "Armature optimization complete",
"MMDOptions.convert_materials.label": "Convert Materials", "MMDOptions.convert_materials.label": "Convert Materials",
"MMDOptions.convert_materials.desc": "Convert materials to use Principled BSDF shader and fix MMD and VRM shaders", "MMDOptions.converting_materials": "Converting materials for {name}", "MMDOptions.convert_materials.desc": "Convert materials to use Principled BSDF shader and fix MMD and VRM shaders",
"MMDOptions.converting_materials": "Converting materials for {name}",
"Updater.label": "Updater", "Updater.label": "Updater",
"Updater.CheckForUpdateButton.label": "Check for Updates", "Updater.CheckForUpdateButton.label": "Check for Updates",
"Updater.CheckForUpdateButton.label_alt": "No Updates Available", "Updater.CheckForUpdateButton.label_alt": "No Updates Available",
@@ -282,7 +290,11 @@
"CreditsSupport.help_text1": "Check out our wiki first, we HIGHLY encourage", "CreditsSupport.help_text1": "Check out our wiki first, we HIGHLY encourage",
"CreditsSupport.help_text2": "that you read it before seeking further support.", "CreditsSupport.help_text2": "that you read it before seeking further support.",
"CreditsSupport.wiki_button": "Wiki", "CreditsSupport.wiki_button": "Wiki",
"CreditsSupport.discord_button": "Join Discord" "CreditsSupport.discord_button": "Join Discord",
"TextureAtlas.include_in_atlas": "Include in Atlas",
"TextureAtlas.include_in_atlas_desc": "Include this material in the texture atlas",
"Scene.avatar_toolkit_updater_version_list.name": "Version List",
"Scene.avatar_toolkit_updater_version_list.description": "List of available versions to update to",
"TextureAtlas.no_materials_selected": "No materials selected for atlas"
} }
} }
+224 -76
View File
@@ -1,150 +1,298 @@
{ {
"authors": ["Avatar Toolkit Team"], "authors": ["Avatar Toolkit Team"],
"messages": { "messages": {
"AutoVisemeButton.desc": "シェイプキーに基づいて自動的にビセムを作成する", "AutoVisemeButton.desc": "シェイプキーに基づいて自動的にビセムを作成",
"AutoVisemeButton.error.noShapekeys": "シェイプキーが見つかりません", "AutoVisemeButton.error.noShapekeys": "シェイプキーが見つかりません",
"AutoVisemeButton.error.selectShapekeys": "シェイプキーを選択してください", "AutoVisemeButton.error.selectShapekeys": "シェイプキーを選択してください",
"AutoVisemeButton.label": "ビセムを作成", "AutoVisemeButton.label": "ビセムを作成",
"AutoVisemeButton.success": "ビセームが正常に作成されました", "AutoVisemeButton.success": "ビセムの作成に成功しました",
"AvatarToolkit.alpha_warning": "これは早期アルファ版であり、バグや問題が発生する可能性があります。", "AvatarToolkit.label": "Avatar Toolkit (アルファ版)",
"AvatarToolkit.description": "Blenderでアバターを作成および編集するための", "AvatarToolkit.desc1": "Avatar Toolkitは早期アクセス段階です",
"AvatarToolkit.label": "Avatar Toolkit", "AvatarToolkit.desc2": "問題が発生する可能性があります。",
"AvatarToolkit.welcome": "Avatar Toolkitへようこそ、", "AvatarToolkit.desc3": "問題を見つけた場合はGithubで報告してください。",
"Export.resonite.desc": "すべてのアニメーションとマテリアルを含むGLBをエクスポートします。アニメーションデータについては以下を参照してください", "Export.resonite.desc": "アニメーションとマテリアルを含むGLBをエクスポート。アニメーションデータについては:",
"Export.resonite.label": "Resoniteにエクスポート", "Export.resonite.label": "Resoniteにエクスポート",
"Importer.export_resonite.desc": "ResoniteにGLTFとしてエクスポートします。モデルがBlenderで正しいスケールであることを確認し、Resoniteでメートル単位でインポートしてください。", "Importer.export_resonite.desc": "GLTFとしてResoniteにエクスポート。Blenderでモデルのスケールを確認し、Resoniteでメートル単位でインポートしてください。",
"Importer.export_resonite.label": "Resoniteにエクスポート", "Importer.export_resonite.label": "Resoniteにエクスポート",
"Importer.export_vrchat.desc": "VRChatにエクスポートします。ChilloutVRでも動作する可能性あります。Catsのエクスポートに似ています。", "Importer.export_vrchat.desc": "VRChatにエクスポートChilloutVRでも動作する可能性あり。Catsのエクスポートに似ています。",
"Importer.export_vrchat.label": "VRChatにエクスポート", "Importer.export_vrchat.label": "VRChatにエクスポート",
"Importer.mmd_anim_importer.desc": "MMDアニメーション(.vmd)をインポート", "Importer.mmd_anim_importer.desc": "MMDアニメーション(.vmd)をインポート",
"Importer.mmd_anim_importer.label": "MMDアニメーション", "Importer.mmd_anim_importer.label": "MMDアニメーション",
"Importing.importer_search_term": "https://search.brave.com/search?q=Blender+{extension}+インポーターアドオン&source=web", "Importing.importer_search_term": "https://search.brave.com/search?q=blender+{extension}+importer+addon&source=web",
"Importing.need_importer": "{extension}タイプに必要なインポーターがありません!インポーター検索用ウェブブラウザを開きます...", "Importing.need_importer": "{extension}タイプに必要なインポーターがありません!インポーター検索用ウェブブラウザを開きます...",
"Language.auto": "自動", "Language.auto": "自動",
"Language.en_US": "English", "Language.en_US": "English",
"Language.ja_JP": "日本語", "Language.ja_JP": "日本語",
"Optimization.applying_transforms": "トランスフォームを適用中...", "Optimization.applying_transforms": "トランスフォームを適用中...",
"Optimization.cleaning_material_names": "マテリアル名をクリーニング中...", "Optimization.cleaning_material_names": "マテリアル名を整理中...",
"Optimization.cleaning_material_slots": "マテリアルスロットをクリーニング中...", "Optimization.cleaning_material_slots": "マテリアルスロットを整理中...",
"Optimization.clearing_unused_data": "未使用データをクリア中...", "Optimization.clearing_unused_data": "未使用データを削除中...",
"Optimization.combine_materials.desc": "ドローコールを減らしパフォーマンスを向上させるために類似したマテリアルを結合する", "Optimization.materials_optimization_report": "マテリアル最適化完了:{num_combined}個のマテリアルを結合、{num_cleaned_slots}個のマテリアルスロットを整理、{num_cleaned_names}個のマテリアル名を整理、{num_removed_data_blocks}個の未使用データブロックを削除しました",
"Optimization.combine_materials.desc": "描画コールを減らしパフォーマンスを向上させるため、類似したマテリアルを結合",
"Optimization.combine_materials.label": "マテリアルを結合", "Optimization.combine_materials.label": "マテリアルを結合",
"Optimization.consolidating_materials": "マテリアルを統合中...", "Optimization.consolidating_materials": "マテリアルを統合中...",
"Optimization.finalizing": "最終処理中...", "Optimization.finalizing": "最終処理中...",
"Optimization.fixing_uv_coordinates": "UV座標を修正中...", "Optimization.fixing_uv_coordinates": "UV座標を修正中...",
"Optimization.join_all_meshes.desc": "ドローコールを減らすためすべてのメッシュを1つのオブジェクトにマージする", "Optimization.join_all_meshes.desc": "描画コールを減らすためすべてのメッシュを1つのオブジェクトに結合",
"Optimization.join_all_meshes.label": "すべてのメッシュを結合", "Optimization.join_all_meshes.label": "すべてのメッシュを結合",
"Optimization.join_error": "メッシュ結合中にエラーが発生しました", "Optimization.join_error": "メッシュ結合中にエラーが発生",
"Optimization.join_operation_failed": "結合操作に失敗しました", "Optimization.join_operation_failed": "結合操作に失敗しました",
"Optimization.join_selected_meshes.desc": "選択したメッシュのみを1つのオブジェクトにマージする", "Optimization.join_selected_meshes.desc": "選択したメッシュのみを1つのオブジェクトに結合",
"Optimization.join_selected_meshes.label": "選択したメッシュを結合", "Optimization.join_selected_meshes.label": "選択したメッシュを結合",
"Optimization.joinmeshes.label": "メッシュ結合:", "Optimization.joinmeshes.label": "メッシュ結合:",
"Optimization.joining_meshes": "メッシュを結合中...", "Optimization.joining_meshes": "メッシュを結合中...",
"Optimization.label": "最適化", "Optimization.label": "最適化",
"Optimization.material_attribute_mismatch": "マテリアル{material_name}の属性が一致しません。スキップします", "Optimization.material_attribute_mismatch": "マテリアル{material_name}の属性が一致しません。スキップします",
"Optimization.materials_combined": "{num_combined}個のマテリアルを結合しました", "Optimization.materials_combined": "{num_combined}個のマテリアルを結合しました",
"Optimization.meshes_joined": "メッシュが正常に結合されました", "Optimization.meshes_joined": "メッシュの結合に成功しました",
"Optimization.no_armature_selected": "アーマチュアが選択されていません", "Optimization.no_armature_selected": "アーマチュアが選択されていません",
"Optimization.no_mesh_selected": "メッシュオブジェクトが選択されていません", "Optimization.no_mesh_selected": "メッシュオブジェクトが選択されていません",
"Optimization.no_meshes_found": "選択されたアーマチュアに対応するメッシュが見つかりません", "Optimization.no_meshes_found": "選択たアーマチュアにメッシュが見つかりません",
"Optimization.options.label": "最適化:", "Optimization.options.label": "最適化:",
"Optimization.preparing_meshes": "メッシュを準備中...", "Optimization.preparing_meshes": "メッシュを準備中...",
"Optimization.processing_mesh_no_shapekeys": "シェイプキーのないメッシュ「{mesh_name}」を処理中", "Optimization.processing_mesh_no_shapekeys": "シェイプキーのないメッシュ「{mesh_name}」を処理中",
"Optimization.processing_shapekey": "メッシュ「{mesh_name}」のシェイプキー「{shapekeyname}」を処理中", "Optimization.processing_shapekey": "メッシュ「{mesh_name}」のシェイプキー「{shapekeyname}」を処理中",
"Optimization.remove_doubles_completed": "重複頂点の削除が完了しました", "Optimization.remove_doubles_completed": "重複頂点の削除が完了しました",
"Optimization.remove_doubles_safely.desc": "口の形状などの重要な特徴を保持しながら重複頂点を削除する", "Optimization.remove_doubles_safely.desc": "口の形状などの重要な特徴を保持しながら重複頂点を削除します。\n素早い解決策ですが、動く頂点は結合しません。",
"Optimization.remove_doubles_safely.label": "安全に重複頂点を削除", "Optimization.remove_doubles_safely.label": "安全に重複頂点を削除",
"Optimization.select_armature": "アーマチュアを選択してください", "Optimization.remove_doubles_safely_advanced.label": "高度な安全重複頂点削除",
"Optimization.select_at_least_two_meshes": "少なくとも2つのメッシュオブジェクトを選択してください", "Optimization.remove_doubles_safely_advanced.desc": "口の形状などの重要な特徴を保持しながら重複頂点を削除します。\n基本版と異なり、動く頂点も結合しますがシェイプキーは保持します。\n例:唇を閉じることはありませんが、唇を構成する分割されたポリゴンは修正します。",
"Optimization.selected_meshes_joined": "選択したメッシュが正常に結合されました", "UVTools.align_uv_to_target.warning.too_much": "エラー!選択が多すぎます。2つのエッジを選択していますか?",
"Optimization.selecting_meshes": "メッシュを選択中...", "UVTools.align_uv_to_target.warning.need_a_line": "各選択オブジェクトにUVポイントの1行が必要です。オブジェクト「{obj}」がこの要件を満たしていません!",
"Optimization.transform_apply_failed": "トランスフォームの適用に失敗しました", "avatar_toolkit.align_uv_edges_to_target.label": "UVエッジをターゲットに合わせる",
"Optimization.vertex_excluded": "シェイプキーのインデックス「{index}」に移動した頂点があります。重複マージから除外します", "avatar_toolkit.align_uv_edges_to_target.desc": "選択された各メッシュのUVポイントの線をアクティブメッシュの選択されたUVポイントの線に合わせます。\nあるモデルのテクスチャを別のモデルに適用する際に便利です。\n2Dカーソルからの距離を使用して各メッシュのUVポイントの線の開始点を識別します",
"Quick_Access.selected_armature.label": "選択されたアーマチュア",
"Quick_Access.selected_armature.desc": "Avatar Toolkitの操作対象となる現在の「ターゲット」アーマチュア",
"Quick_Access.export": "エクスポート", "Quick_Access.export": "エクスポート",
"Quick_Access.export_fbx.desc": "モデルをFBXとしてエクスポート", "Quick_Access.export_fbx.desc": "モデルをFBXとしてエクスポート",
"Quick_Access.export_fbx.label": "FBXエクスポート", "Quick_Access.export_fbx.label": "FBXエクスポート",
"Quick_Access.export_menu.desc": "サポートされているフォーマットにエクスポート", "Quick_Access.export_menu.desc": "サポートされている形式にエクスポート",
"Quick_Access.export_menu.label": "エクスポートメニュー", "Quick_Access.export_menu.label": "エクスポートメニュー",
"Quick_Access.import": "インポート", "Quick_Access.import": "インポート",
"Quick_Access.import_export.label": "インポート/エクスポート:", "Quick_Access.import_export.label": "インポート/エクスポート:",
"Quick_Access.import_menu.desc": "モデルをインポート", "Quick_Access.import_menu.desc": "モデルをインポート",
"Quick_Access.import_menu.label": "インポートメニュー", "Quick_Access.import_menu.label": "インポートメニュー",
"Quick_Access.import_pmd": "PMDインポート", "Quick_Access.import_pmd": "PMDインポート",
"Quick_Access.import_pmd.desc": "MMD PMDモデルをインポート", "Quick_Access.import_pmd.desc": "MMD PMDモデルをインポート",
"Quick_Access.import_pmx": "PMXインポート", "Quick_Access.import_pmx": "PMXインポート",
"Quick_Access.import_pmx.desc": "MMD PMXモデルをインポート", "Quick_Access.import_pmx.desc": "MMD PMXモデルをインポート",
"Quick_Access.import_success": "モデルが正常にインポートされました", "Quick_Access.import_success": "モデルインポートに成功しました",
"Quick_Access.label": "クイックアクセス", "Quick_Access.label": "クイックアクセス",
"Quick_Access.options": "クイックアクセス:", "Quick_Access.options": "クイックアクセス:",
"Quick_Access.select_armature": "アーマチュアを選択:", "Quick_Access.select_armature": "アーマチュアを選択:",
"Quick_Access.apply_armature_failed": "シェイプキーの結合段階でポーズをアーマチュアに適用できませんでした!",
"Quick_Access.apply_pose_as_rest.desc": "現在のポーズをデフォルトの休止ポーズにします。",
"Quick_Access.stop_pose_mode.desc": "ポーズモードを終了し、ポーズモードの全ての表示ボーンのポーズをクリアします。",
"Quick_Access.apply_pose_as_rest.label": "ポーズを休止ポーズとして適用",
"Quick_Access.apply_pose_as_shapekey.desc": "現在のポーズを後で有効化できるシェイプキーとして作成します。\n顎の開閉位置を顔の動きのシェイプキーとして適用する際に便利です。",
"Quick_Access.apply_pose_as_shapekey.label": "ポーズをシェイプキーとして適用",
"Quick_Access.stop_pose_mode.label": "ポーズモードを終了",
"Quick_Access.start_pose_mode.desc": "Avatar Toolkitのターゲットアーマチュアのポーズモードを開始します。",
"Quick_Access.start_pose_mode.label": "ポーズモードを開始",
"Quick_Access.select_export.label": "エクスポート方法を選択", "Quick_Access.select_export.label": "エクスポート方法を選択",
"Quick_Access.select_export_resonite.label": "Resonite", "Quick_Access.select_export_resonite.label": "Resonite",
"Settings.label": "設定", "Settings.label": "設定",
"Settings.language.desc": "アドオンのUI言語を選択", "Settings.language.desc": "アドオンのUI言語を選択",
"Settings.language.label": "言語:", "Settings.language.label": "言語:",
"Settings.translation_restart_popup.description": "翻訳の更新に関する情報", "Settings.translation_restart_popup.description": "翻訳の更新について",
"Settings.translation_restart_popup.label": "翻訳の更新", "Settings.translation_restart_popup.label": "翻訳の更新",
"Settings.translation_restart_popup.message1": "一部の翻訳は適用されない場合があります", "Settings.translation_restart_popup.message1": "一部の翻訳はBlenderを再起動するまで",
"Settings.translation_restart_popup.message2": "Blenderを再起動するまで。", "Settings.translation_restart_popup.message2": "適用されない場合があります。",
"TextureAtlas.atlas_completed": "テクスチャアトラスの作成が完了しました", "TextureAtlas.atlas_completed": "テクスチャアトラスの作成が完了しました",
"TextureAtlas.atlas_error": "テクスチャアトラスの作成中にエラーが発生しました", "TextureAtlas.atlas_error": "テクスチャアトラスの作成中にエラーが発生しました",
"TextureAtlas.atlas_materials": "マテリアルをアトラス化", "TextureAtlas.atlas_materials": "マテリアルをアトラス化",
"TextureAtlas.atlas_materials_desc": "モデルを最適化するためにマテリアルをアトラス化する", "TextureAtlas.atlas_materials_desc": "モデルを最適化するためにマテリアルをアトラス化",
"TextureAtlas.label": "テクスチャアトラス", "TextureAtlas.label": "テクスチャアトラス",
"TextureAtlas.loaded_list": "テクスチャアトラスマテリアルリストを読み込みました", "TextureAtlas.loaded_list": "テクスチャアトラスマテリアルリストを読み込みました",
"TextureAtlas.material_list_label": "テクスチャアトラスマテリアルリストのマテリアル", "TextureAtlas.material_list_label": "テクスチャアトラスマテリアルリストのマテリアル",
"TextureAtlas.reload_list": "テクスチャアトラスマテリアルリストを再読み込み", "TextureAtlas.reload_list": "テクスチャアトラスマテリアルリストを再読み込み",
"Tools.bones_translated_success": "すべての骨をヒューマノイド名に正常に変換しました", "Tools.bones_translated_success": "すべてのボーンを正常にヒューマノイド名に変換しました",
"Tools.bones_translated_with_fails": "{translate_bone_fails}個のをヒューマノイド名に変換できませんでした。それらの名前に「<noik>」を追加します。", "Tools.bones_translated_with_fails": "{translate_bone_fails}個のボーンをヒューマノイド名に変換できませんでした。名前に「<noik>」を追加します。",
"Tools.convert_to_resonite.desc": "モデルの骨の名前をResonite互換性のある名前に変換します", "Tools.convert_to_resonite.desc": "モデルのボーン名をResonite互換名前に変換",
"Tools.convert_to_resonite.label": "Resoniteに変換", "Tools.convert_to_resonite.label": "Resoniteに変換",
"Tools.create_digitigrade_legs.desc": "選択した骨のチェーンからデジティグレード脚を作成する", "Tools.create_digitigrade_legs.desc": "選択したボーンチェーンから脚を作成",
"Tools.create_digitigrade_legs.label": "デジティグレード脚を作成", "Tools.create_digitigrade_legs.label": "脚を作成",
"Tools.digitigrade_legs.error.bone_format": "骨のフォーマットが正しくありません!4つの連続したのチェーンを選択してください!", "Tools.digitigrade_legs.error.bone_format": "ボーンの形式が正しくありません!4つの連続したボーンのチェーンを選択してください!",
"Tools.digitigrade_legs.success": "デジティグレード脚が正常に作成されました", "Tools.digitigrade_legs.success": "獣脚の作成に成功しました",
"Tools.import_any_model.desc": "サポートされているモデル(FBX、SMD、DMX、GLTF、PMD、PMXなどをインポートします。", "Tools.import_any_model.desc": "FBX、SMD、DMX、GLTF、PMD、PMXなど、サポートされているモデルをインポート",
"Tools.import_any_model.label": "モデルをインポート", "Tools.import_any_model.label": "モデルをインポート",
"Tools.label": "ツール", "Tools.label": "ツール",
"Tools.no_armature_selected": "アーマチュアが選択されていません", "Tools.no_armature_selected": "アーマチュアが選択されていません",
"Tools.select_armature": "アーマチュアを選択してください", "Tools.select_armature": "アーマチュアを選択してください",
"Tools.tools_title.label": "ツール:", "Tools.tools_title.label": "ツール:",
"Tools.separate_by.label": "別:", "Tools.separate_by.label": "分離方法:",
"Tools.separate_by_materials.label": "マテリアルで分離", "Tools.separate_by_materials.label": "マテリアルで分離",
"Tools.separate_by_materials.desc": "選択されたメッシュをマテリアルごとに分離します", "Tools.separate_by_materials.desc": "選択たメッシュをマテリアルで分離",
"Tools.separate_by_materials.success": "メッシュマテリアルごとに正常に分離されました", "Tools.separate_by_materials.success": "メッシュマテリアルで分離しました",
"Tools.separate_by_loose_parts.label": "バラバラの部分で分離", "Tools.separate_by_loose_parts.label": "分離パーツで分離",
"Tools.separate_by_loose_parts.desc": "選択されたメッシュをバラバラの部分ごとに分離します", "Tools.separate_by_loose_parts.desc": "選択たメッシュを分離パーツで分離",
"Tools.separate_by_loose_parts.success": "メッシュがバラバラの部分ごとに正常に分離されました", "Tools.separate_by_loose_parts.success": "メッシュを分離パーツで分離しました",
"Tools.apply_transforms.label": "トランスフォームを適用", "Tools.apply_transforms.label": "トランスフォームを適用",
"Tools.apply_transforms.desc": "アーマチュアとそのメッシュに位置、回転、スケールを適用します", "Tools.apply_transforms.desc": "アーマチュアとそのメッシュに位置、回転、スケールを適用",
"Tools.apply_transforms.invalid_armature": "無効なアーマチュアが選択されています", "Tools.apply_transforms.invalid_armature": "無効なアーマチュアが選択されています",
"Tools.apply_transforms.success": "アーマチュアとメッシュにトランスフォームが正常に適用されました", "Tools.apply_transforms.success": "アーマチュアとメッシュにトランスフォームを適用しました",
"VisemePanel.create_visemes": "ビセームを作成", "Tools.remove_unused_shapekeys.label": "未使用のシェイプキーを削除",
"VisemePanel.creating_viseme": "ビセームを作成中:{viseme_name}", "Tools.remove_unused_shapekeys.tolerance.desc": "シェイプキーを保持する最小の頂点移動量\n(任意の座標での位置)",
"VisemePanel.creating_viseme_detail": "ビセームを作成中:{viseme_name}", "Tools.remove_unused_shapekeys.desc": "何も動かさないシェイプキーを削除します。\nカテゴリーシェイプキーは削除しません。\n(例:名前に「~」「-」「=」を含むもの)",
"VisemePanel.creating_visemes": "ビセームを作成中...", "Tools.remove_unused_shapekeys.tolerance.label": "位置の許容値",
"Tools.apply_shape_key.label": "シェイプキーをベースに適用",
"Tools.apply_shape_key.desc": "選択したシェイプキーをベースに適用し、デフォルトでオンにします。",
"Tools.apply_shape_key.error": "シェイプキーが何らかの理由でマージされませんでした!",
"Tools.remove_zero_weight_bones.success": "ウェイトのないボーンを削除しました",
"Tools.remove_zero_weight_bones.label": "ウェイトのないボーンを削除",
"Tools.remove_zero_weight_bones.desc": "閾値以下のウェイトを持つボーンをアーマチュアから削除します。",
"Tools.merge_bones_to_active.delete_old.desc": "マージ時に古いボーンを削除します。",
"Tools.merge_bones_to_active.delete_old.label": "古いボーンを削除",
"Tools.merge_bones_to_active.desc": "選択したボーンをアクティブなボーン(青または橙色で選択)にマージします。",
"Tools.merge_bones_to_active.label": "ボーンをアクティブなものにマージ",
"Tools.merge_bones_to_parents.delete_old.desc": "マージ時に古いボーンを削除します。",
"Tools.merge_bones_to_parents.delete_old.label": "古いボーンを削除",
"Tools.merge_bones_to_parents.desc": "選択した各ボーンをそれぞれの親ボーンにマージします。",
"Tools.merge_bones_to_parents.label": "ボーンを個別の親にマージ",
"Tools.remove_zero_weight_bones.threshold.label": "ウェイトの閾値",
"Tools.remove_zero_weight_bones.threshold.desc": "アーマチュア下のメッシュのどの部分にもこの閾値以上のウェイトがないボーンは削除されます",
"Tools.connect_bones.label": "ボーンを接続",
"Tools.bone_tools.label": "ボーンツール",
"Tools.additional_tools.label": "追加ツール",
"Tools.merge_twist_bones.label": "ツイストボーンをマージ",
"Tools.merge_twist_bones.desc": "ツイストボーンを親ボーンにマージ",
"Tools.connect_bones.desc": "ボーンをそれぞれの子ボーンと接続",
"Tools.connect_bones.invalid_armature": "無効なアーマチュアが選択されています",
"Tools.connect_bones.min_distance.label": "最小距離",
"Tools.connect_bones.min_distance.desc": "ボーンを接続する最小距離",
"Tools.connect_bones.success": "{bones_connected}個のボーンを接続しました",
"Tools.delete_bone_constraints.label": "ボーンの制約を削除",
"Tools.delete_bone_constraints.desc": "アーマチュアのボーンから全ての制約を削除",
"Tools.delete_bone_constraints.invalid_armature": "無効なアーマチュアが選択されています",
"Tools.delete_bone_constraints.success": "ボーンから{constraints_removed}個の制約を削除しました",
"Tools.convert_rigify_to_unity.label": "RigifyをUnityに変換",
"Tools.convert_rigify_to_unity.desc": "RigifyアーマチュアをUnityで使用できるように準備",
"Tools.convert_rigify_to_unity.success": "RigifyアーマチュアをUnity用に変換しました",
"VisemePanel.create_visemes": "ビセムを作成",
"VisemePanel.creating_viseme": "ビセムを作成中:{viseme_name}",
"VisemePanel.creating_viseme_detail": "ビセムを作成中:{viseme_name}",
"VisemePanel.creating_visemes": "ビセムを作成中...",
"VisemePanel.error.noArmature": "アーマチュアが選択されていません", "VisemePanel.error.noArmature": "アーマチュアが選択されていません",
"VisemePanel.error.noMesh": "メッシュが選択されていません", "VisemePanel.error.noMesh": "メッシュが選択されていません",
"VisemePanel.error.noShapekeys": "選択されたメッシュにシェイプキーがありません", "VisemePanel.error.noShapekeys": "選択たメッシュにシェイプキーがありません",
"VisemePanel.error.selectMesh": "ビセムを作成するメッシュを選択してください", "VisemePanel.error.selectMesh": "ビセムを作成するメッシュを選択してください",
"VisemePanel.info.selectMesh": "ビセムを作成するメッシュを選択してください", "VisemePanel.info.selectMesh": "ビセムを作成するメッシュを選択してください",
"VisemePanel.label": "ビセム", "VisemePanel.label": "ビセム",
"VisemePanel.mixing_shape": "シェイプをミックス中:{shape_name}値:{value}", "VisemePanel.mixing_shape": "シェイプを混合中:{shape_name} 値:{value}",
"VisemePanel.mouth_a.desc": "'A'の口の形のシェイプキー", "VisemePanel.mouth_a.desc": "'A'の口の形のシェイプキー",
"VisemePanel.mouth_a.label": "口 A", "VisemePanel.mouth_a.label": "口 A",
"VisemePanel.mouth_ch.desc": "'CH'の口の形のシェイプキー", "VisemePanel.mouth_ch.desc": "'CH'の口の形のシェイプキー",
"VisemePanel.mouth_ch.label": "口 CH", "VisemePanel.mouth_ch.label": "口 CH",
"VisemePanel.mouth_o.desc": "'O'の口の形のシェイプキー", "VisemePanel.mouth_o.desc": "'O'の口の形のシェイプキー",
"VisemePanel.mouth_o.label": "口 O", "VisemePanel.mouth_o.label": "口 O",
"VisemePanel.removing_existing_viseme": "既存のビセムを削除中:{viseme_name}", "VisemePanel.removing_existing_viseme": "既存のビセムを削除中:{viseme_name}",
"VisemePanel.removing_existing_visemes": "既存のビセムを削除中...", "VisemePanel.removing_existing_visemes": "既存のビセムを削除中...",
"VisemePanel.select_mesh": "メッシュを選択", "VisemePanel.select_mesh": "メッシュを選択",
"VisemePanel.selected_shapes": "選択されたシェイプ:A={shape_a}、O={shape_o}、CH={shape_ch}", "VisemePanel.selected_mesh.label": "選択されたメッシュ",
"VisemePanel.selected_mesh.desc": "ビセム操作用に現在選択されているメッシュ",
"VisemePanel.selected_shapes": "選択されたシェイプ:A={shape_a}, O={shape_o}, CH={shape_ch}",
"VisemePanel.shape_intensity": "シェイプの強度", "VisemePanel.shape_intensity": "シェイプの強度",
"VisemePanel.shape_intensity_desc": "ビセムシェイプキーの強度", "VisemePanel.shape_intensity_desc": "ビセムシェイプキーの強度",
"VisemePanel.sorting_shapekeys": "シェイプキーをソート中...", "VisemePanel.sorting_shapekeys": "シェイプキーを並べ替え中...",
"VisemePanel.start_viseme_creation": "ビセム作成を開始...", "VisemePanel.start_viseme_creation": "ビセム作成を開始...",
"VisemePanel.viseme_created_successfully": "ビセム{viseme_name}が正常に作成されました", "VisemePanel.viseme_created_successfully": "ビセム{viseme_name}の作成に成功しました",
"VisemePanel.viseme_creation_completed": "ビセム作成が完了しました。" "VisemePanel.viseme_creation_completed": "ビセム作成が完了しました。",
"MergeArmatures.select_armature": "アーマチュアを選択してください",
"MergeArmatures.title.label": "アーマチュアのマージ:",
"MergeArmatures.label": "アーマチュアをマージ",
"MergeArmatures.selected_armature.label": "マージ元のアーマチュア",
"MergeArmatures.selected_armature.desc": "Avatar Toolkitのターゲットアーマチュアにマージされるアーマチュア",
"MergeArmatures.target_armature.label": "マージ先のアーマチュア",
"MergeArmatures.target_armature.desc": "アーマチュアのマージ先となるターゲットアーマチュア",
"MergeArmature.merge_armatures.label": "アーマチュアをマージ",
"MergeArmature.merge_armatures.desc": "{selected_armature_label}をAvatar Toolkitのターゲットアーマチュアにマージ",
"MergeArmature.merge_armatures.align_bones.label": "ボーンを整列",
"MergeArmature.merge_armatures.align_bones.desc": "マージ前にソースアーマチュアのボーンをターゲットアーマチュアに合わせて\nボーンを伸縮させます。",
"MergeArmature.merge_armatures.apply_transforms.label": "トランスフォームを適用",
"MergeArmature.merge_armatures.apply_transforms.desc": "マージ前にアーマチュアとそのメッシュにトランスフォームを適用します。",
"MMDOptions.optimize_armature.label": "アーマチュアを最適化",
"MMDOptions.optimize_armature.desc": "ボーンのロールの修正、ボーンの整列、ボーンの接続などでアーマチュアを最適化",
"MMDOptions.fixing_bone_rolls": "ボーンのロールを修正中",
"MMDOptions.aligning_bones": "ボーンを整列中",
"MMDOptions.connecting_bones": "ボーンを接続中",
"MMDOptions.deleting_bone_constraints": "ボーンの制約を削除中",
"MMDOptions.merging_bones_to_parents": "ボーンを親にマージ中",
"MMDOptions.reordering_bones": "ボーンを並べ替え中",
"MMDOptions.fixing_armature_names": "アーマチュア名を修正中",
"MMDOptions.renaming_bones": "ボーン名を変更中",
"MMDOptions.armature_optimization_complete": "アーマチュアの最適化が完了しました",
"MMDOptions.convert_materials.label": "マテリアルを変換",
"MMDOptions.convert_materials.desc": "マテリアルをPrincipled BSDFシェーダーを使用するように変換し、MMDとVRMシェーダーを修正",
"MMDOptions.converting_materials": "{name}のマテリアルを変換中",
"MMDOptions.title": "MMDオプション",
"MMDOptions.no_armature_selected": "アーマチュアが選択されていません",
"MMDOptions.label": "MMDオプション",
"MMDOptions.cleanup_mesh.label": "メッシュのクリーンアップ",
"MMDOptions.cleanup_mesh.desc": "空のオブジェクト、未使用の頂点グループ、未使用の頂点、空のシェイプキーを削除してメッシュをクリーンアップ",
"MMDOptions.removing_empty_objects": "空のオブジェクトを削除中",
"MMDOptions.removing_unused_vertex_groups": "未使用の頂点グループを削除中",
"MMDOptions.removing_unused_vertices": "未使用の頂点を削除中",
"MMDOptions.removing_empty_shape_keys": "空のシェイプキーを削除中",
"MMDOptions.optimize_weights.label": "ウェイトを最適化",
"MMDOptions.optimize_weights.desc": "頂点あたりのウェイト数を制限してウェイトを最適化",
"MMDOptions.max_weights.label": "最大ウェイト数",
"MMDOptions.max_weights.desc": "頂点あたりの最大ウェイト数",
"MMDOptions.merging_weights": "ウェイトを結合中",
"MMDOptions.removing_zero_weight_bones": "ウェイトのないボーンを削除中",
"MMDOptions.limiting_vertex_weights": "頂点ウェイトを制限中",
"MMDOptions.weight_optimization_complete": "ウェイトの最適化が完了しました",
"Updater.label": "アップデーター",
"Updater.CheckForUpdateButton.label": "アップデートを確認",
"Updater.CheckForUpdateButton.label_alt": "利用可能なアップデートはありません",
"Updater.UpdateToLatestButton.label": "{name}にアップデート",
"Updater.UpdateToSelectedButton.label": "アップデート",
"Updater.currentVersion": "現在のバージョン:{name}",
"Updater.CheckForUpdateButton.desc": "利用可能なアップデートを確認",
"UpdateToLatestButton.desc": "最新バージョンにアップデート",
"UpdateNotificationPopup.label": "アップデート通知",
"UpdateNotificationPopup.desc": "利用可能なアップデートについての通知",
"UpdateNotificationPopup.newUpdate": "新しいアップデートが利用可能:{version}",
"RestartBlenderPopup.label": "Blenderを再起動",
"RestartBlenderPopup.desc": "アップデートを完了するためにBlenderを再起動",
"RestartBlenderPopup.message": "アップデートが成功しました!Blenderを再起動してください。",
"check_for_update.cantCheck": "アップデートを確認できません",
"download_file.cantConnect": "アップデートサーバーに接続できません",
"download_file.cantFindZip": "アップデートファイルが見つかりません",
"download_file.cantFindAvatarToolkit": "アップデートパッケージ内にAvatar Toolkitファイルが見つかりません",
"CreditsSupport.label": "クレジット&サポート",
"CreditsSupport.credits_title": "クレジット",
"CreditsSupport.credits_text1": "Avatar Toolkitは以下のNeonekoチームによって作成されました:",
"CreditsSupport.credits_text2": "YusarinaとOnan989",
"CreditsSupport.credits_text3": "一部のコードはCats Blender Pluginを参考にしています。",
"CreditsSupport.credits_text4": "元のプラグインの貢献者に感謝します。",
"CreditsSupport.support_text1": "私たちの活動を支援したい場合は、",
"CreditsSupport.support_text2": "pally.ggページで寄付/投げ銭ができます。",
"CreditsSupport.support_title": "サポートする",
"CreditsSupport.support_button": "サポートする",
"CreditsSupport.help_title": "ヘルプが必要ですか?",
"CreditsSupport.help_text1": "まずはWikiをご確認ください。さらなるサポートを",
"CreditsSupport.help_text2": "求める前にWikiを読むことを強くお勧めします。",
"CreditsSupport.wiki_button": "Wiki",
"CreditsSupport.discord_button": "Discordに参加",
"TextureAtlas.include_in_atlas": "アトラスに含める",
"TextureAtlas.include_in_atlas_desc": "このマテリアルをテクスチャアトラスに含める",
"Scene.avatar_toolkit_updater_version_list.name": "バージョンリスト",
"Scene.avatar_toolkit_updater_version_list.description": "アップデート可能なバージョンのリスト",
"TextureAtlas.albedo": "アルベド",
"TextureAtlas.normal": "法線",
"TextureAtlas.emission": "発光",
"TextureAtlas.ambient_occlusion": "アンビエントオクルージョン",
"TextureAtlas.height": "ハイト",
"TextureAtlas.roughness": "ラフネス",
"TextureAtlas.error.label": "エラー",
"TextureAtlas.none.label": "なし",
"TextureAtlas.no_nodes_error.desc": "このマテリアルはノードを使用していません!",
"TextureAtlas.no_images_error.desc": "このマテリアルには画像がありません!",
"TextureAtlas.texture_use_atlas.desc": "{name}マップアトラスに使用するテクスチャ",
"TextureAtlas.no_materials_selected": "アトラス用のマテリアルが選択されていません",
"Optimization.select_armature": "アーマチュアを選択してください",
"CheckForUpdateButton.label": "アップデートを確認",
"CheckForUpdateButton.desc": "利用可能なアップデートを確認",
"UpdateToLatestButton.label": "最新バージョンにアップデート"
} }
} }
+119 -23
View File
@@ -1,11 +1,56 @@
from bpy.types import UIList, Panel, UILayout, Object, Context,Material, Operator from bpy.types import UIList, Panel, UILayout, Object, Context,Material, Operator
import bpy import bpy
from math import sqrt
from ..core.register import register_wrap from ..core.register import register_wrap
from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME
from ..core.common import SceneMatClass, MaterialListBool, get_selected_armature from ..core.common import SceneMatClass, MaterialListBool, get_selected_armature
from ..functions.atlas_materials import AvatarToolKit_OT_AtlasMaterials from ..functions.atlas_materials import AvatarToolKit_OT_AtlasMaterials
from ..functions.translations import t from ..functions.translations import t
@register_wrap
class AvatarToolKit_OT_SelectAllMaterials(Operator):
bl_idname = 'avatar_toolkit.select_all_materials'
bl_label = "Select All"
bl_description = "Select all materials for atlas"
def execute(self, context):
for item in context.scene.materials:
item.mat.include_in_atlas = True
return {'FINISHED'}
@register_wrap
class AvatarToolKit_OT_SelectNoneMaterials(Operator):
bl_idname = 'avatar_toolkit.select_none_materials'
bl_label = "Select None"
bl_description = "Deselect all materials"
def execute(self, context):
for item in context.scene.materials:
item.mat.include_in_atlas = False
return {'FINISHED'}
@register_wrap
class AvatarToolKit_OT_ExpandAllMaterials(Operator):
bl_idname = 'avatar_toolkit.expand_all_materials'
bl_label = "Expand All"
bl_description = "Expand all material settings"
def execute(self, context):
for item in context.scene.materials:
item.mat.material_expanded = True
return {'FINISHED'}
@register_wrap
class AvatarToolKit_OT_CollapseAllMaterials(Operator):
bl_idname = 'avatar_toolkit.collapse_all_materials'
bl_label = "Collapse All"
bl_description = "Collapse all material settings"
def execute(self, context):
for item in context.scene.materials:
item.mat.material_expanded = False
return {'FINISHED'}
@register_wrap @register_wrap
class AvatarToolKit_OT_ExpandSectionMaterials(Operator): class AvatarToolKit_OT_ExpandSectionMaterials(Operator):
bl_idname = 'avatar_toolkit.expand_section_materials' bl_idname = 'avatar_toolkit.expand_section_materials'
@@ -40,23 +85,70 @@ 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_header(self, context):
layout = self.layout
row = layout.row(align=True)
row.operator("avatar_toolkit.select_all_materials", text="", icon='CHECKBOX_HLT')
row.operator("avatar_toolkit.select_none_materials", text="", icon='CHECKBOX_DEHLT')
row.operator("avatar_toolkit.expand_all_materials", text="", icon='DISCLOSURE_TRI_DOWN')
row.operator("avatar_toolkit.collapse_all_materials", text="", icon='DISCLOSURE_TRI_RIGHT')
row.prop(context.scene, "material_search_filter", text="", icon='VIEWZOOM')
box = layout.box()
row = box.row()
row.label(text=f"Estimated Atlas Size: {self.calculate_atlas_size(context)}px")
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() if context.scene.material_search_filter and context.scene.material_search_filter.lower() not in item.mat.name.lower():
row = box.row() return
row.label(text=item.mat.name, icon = "MATERIAL")
col = box.row() row = layout.row()
col.prop(item.mat, "texture_atlas_albedo")
col = box.row() # Add a clear checkbox for material selection
col.prop(item.mat, "texture_atlas_normal") row.prop(item.mat, "include_in_atlas", text="", icon='CHECKBOX_HLT' if item.mat.include_in_atlas else 'CHECKBOX_DEHLT')
col = box.row()
col.prop(item.mat, "texture_atlas_emission") # Material name and expansion toggle
col = box.row() row.prop(item.mat, "material_expanded",
col.prop(item.mat, "texture_atlas_ambient_occlusion") text=item.mat.name,
col = box.row() icon='DOWNARROW_HLT' if item.mat.material_expanded else 'RIGHTARROW',
col.prop(item.mat, "texture_atlas_height") emboss=False)
col = box.row()
col.prop(item.mat, "texture_atlas_roughness") # Show texture settings if expanded
if item.mat.material_expanded and item.mat.include_in_atlas:
box = layout.box()
col = box.column(align=True)
self.draw_texture_row(col, item.mat, "texture_atlas_albedo", "IMAGE_RGB")
self.draw_texture_row(col, item.mat, "texture_atlas_normal", "NORMALS_FACE")
self.draw_texture_row(col, item.mat, "texture_atlas_emission", "LIGHT")
self.draw_texture_row(col, item.mat, "texture_atlas_ambient_occlusion", "SHADING_SOLID")
self.draw_texture_row(col, item.mat, "texture_atlas_height", "IMAGE_ZDEPTH")
self.draw_texture_row(col, item.mat, "texture_atlas_roughness", "MATERIAL")
col.separator(factor=0.5)
def draw_texture_row(self, layout, material, prop_name, icon):
row = layout.row()
row.prop(material, prop_name, icon=icon)
if getattr(material, prop_name):
row.label(text="", icon='CHECKMARK')
else:
row.label(text="", icon='X')
def is_material_ready(self, material):
return bool(material.texture_atlas_albedo or
material.texture_atlas_normal or
material.texture_atlas_emission)
def calculate_atlas_size(self, context):
total_size = 0
for mat in context.scene.materials:
if mat.mat.include_in_atlas:
if mat.mat.texture_atlas_albedo:
img = bpy.data.images[mat.mat.texture_atlas_albedo]
total_size += img.size[0] * img.size[1]
return f"{int(sqrt(total_size))}x{int(sqrt(total_size))}"
@register_wrap @register_wrap
class AvatarToolKit_PT_TextureAtlasPanel(Panel): class AvatarToolKit_PT_TextureAtlasPanel(Panel):
@@ -74,7 +166,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 +177,21 @@ 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')