diff --git a/core/common.py b/core/common.py index 14e4914..6e79d9c 100644 --- a/core/common.py +++ b/core/common.py @@ -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) 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]]: - 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]: 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" return True -def apply_pose_as_rest(context: Context, armature_obj: bpy.types.Object, meshes: list[bpy.types.Object]) -> bool: - for obj in meshes: - mesh_data: Mesh = obj.data +def apply_pose_as_rest(context: Context, armature_obj: Object, meshes: list[Object]) -> bool: + for mesh_obj in meshes: + if not mesh_obj.data: + continue - if mesh_data.shape_keys: - shape_key_obj_list: list[bpy.types.Object] = [] - modifier_armature_name: str = "" - - 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 - 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) + if mesh_obj.data.shape_keys and mesh_obj.data.shape_keys.key_blocks: + if len(mesh_obj.data.shape_keys.key_blocks) == 1: + basis = mesh_obj.data.shape_keys.key_blocks[0] + basis_name = basis.name + mesh_obj.shape_key_remove(basis) + apply_armature_to_mesh(armature_obj, mesh_obj) + mesh_obj.shape_key_add(name=basis_name) + else: + apply_armature_to_mesh_with_shapekeys(armature_obj, mesh_obj, context) else: - modifier_armature_name: str = "" - - 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") + apply_armature_to_mesh(armature_obj, mesh_obj) + bpy.ops.object.mode_set(mode='POSE') bpy.ops.pose.armature_apply(selected=False) + bpy.ops.object.mode_set(mode='OBJECT') + 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]: armature = get_selected_armature(context) if armature and is_valid_armature(armature): diff --git a/core/properties.py b/core/properties.py index ce0c140..58cd396 100644 --- a/core/properties.py +++ b/core/properties.py @@ -23,10 +23,28 @@ def register() -> None: description=t("VisemePanel.selected_mesh.desc") ))) - 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") + 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.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( @@ -73,7 +91,15 @@ def register() -> None: register_property((bpy.types.Scene, "selected_armature", bpy.props.EnumProperty( items=get_armatures, 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( diff --git a/core/register.py b/core/register.py index 1862e99..915c372 100644 --- a/core/register.py +++ b/core/register.py @@ -26,10 +26,19 @@ def register_properties(): setattr(prop[0], prop[1], prop[2]) else: prop() + +def clear_registration(): + __bl_classes.clear() + __bl_ordered_classes.clear() + __bl_props.clear() def unregister_properties(): 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! # Find order to register to solve dependencies diff --git a/core/updater.py b/core/updater.py index b7486d2..96bc017 100644 --- a/core/updater.py +++ b/core/updater.py @@ -7,6 +7,7 @@ import shutil import pathlib import zipfile import time +from urllib import request, error from threading import Thread from bpy.app.handlers import persistent from ..functions.translations import t @@ -140,9 +141,9 @@ def get_github_releases() -> bool: try: 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()) - except urllib.error.URLError: + except error.URLError: print('URL ERROR') return False @@ -191,6 +192,10 @@ def finish_update_checking(error: str = '') -> None: return None # Important for bpy.app.timers def update_now(latest: bool = False) -> None: + if not version_list: + print("No version list available. Please check for updates first.") + return + if latest: update_link = version_list[latest_version_str][0] else: @@ -210,7 +215,7 @@ def download_file(update_url: str) -> None: try: ssl._create_default_https_context = ssl._create_unverified_context urllib.request.urlretrieve(update_url, update_zip_file) - except urllib.error.URLError: + except error.URLError: finish_update(error=t('download_file.cantConnect')) return diff --git a/functions/armature_modifying.py b/functions/armature_modifying.py index 2797cc7..ddf467a 100644 --- a/functions/armature_modifying.py +++ b/functions/armature_modifying.py @@ -64,25 +64,38 @@ class AvatarToolkit_OT_ApplyPoseAsShapekey(Operator): @classmethod def poll(cls, context): - return get_selected_armature(context) != None 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) + armature = common.get_selected_armature(context) + return armature and context.mode == 'POSE' + 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'} @register_wrap @@ -97,12 +110,14 @@ class AvatarToolkit_OT_ApplyPoseAsRest(Operator): return get_selected_armature(context) != None and context.mode == "POSE" def execute(self, context: Context): - - if common.apply_pose_as_rest(armature_obj=get_selected_armature(context),meshes=get_all_meshes(context), context=context): + if not common.apply_pose_as_rest(armature_obj=get_selected_armature(context), + meshes=get_all_meshes(context), + context=context): self.report({'ERROR'}, t("Quick_Access.apply_armature_failed")) - return {'FINISHED'} + return {'CANCELLED'} return {'FINISHED'} + @register_wrap class AvatarToolkit_OT_RemoveZeroWeightBones(Operator): bl_idname = "avatar_toolkit.remove_zero_weight_bones" diff --git a/functions/atlas_materials.py b/functions/atlas_materials.py index 819d7b5..4b925c1 100644 --- a/functions/atlas_materials.py +++ b/functions/atlas_materials.py @@ -10,19 +10,38 @@ from ..core.common import SceneMatClass, MaterialListBool from ..core.packer.rectangle_packer import MaterialImageList, BinPacker from ..functions.translations import t -def scale_images_to_largest(images:list[Image]) -> set: - print([image.name for image in images]) - x: int=0 - y: int=0 - for image in images: - x = max(x,image.size[0]) - y = max(y,image.size[1]) - print(x,y) +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: + 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)) - return x,y + return x, y def MaterialImageList_to_Image_list(classitem: MaterialImageList) -> 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]: - mat: SceneMatClass = None material_image_list: list[MaterialImageList] = [] - for mat in context.scene.materials: - new_mat_image_item: MaterialImageList = MaterialImageList() - try: - new_mat_image_item.albedo = bpy.data.images[mat.mat.texture_atlas_albedo] - except Exception as e: - name: str = mat.mat.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.mat.texture_atlas_normal] - except Exception: - name: str = mat.mat.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.mat.texture_atlas_emission] - except Exception: - name: str = mat.mat.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.mat.texture_atlas_ambient_occlusion] - except Exception: - name: str = mat.mat.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.mat.texture_atlas_height] - except Exception: - name: str = mat.mat.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.mat.texture_atlas_roughness] - except Exception: - name: str = mat.mat.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) - new_mat_image_item.material = mat.mat - material_image_list.append(new_mat_image_item) + + for obj in context.scene.objects: + 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: + 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) + + 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 @@ -124,13 +148,16 @@ class AvatarToolKit_OT_AtlasMaterials(Operator): def execute(self, context: Context) -> set: try: - mat_images: list[MaterialImageList] = prep_images_in_scene(context) - - packer: BinPacker = BinPacker(mat_images) + # 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] + + if not selected_materials: + self.report({'WARNING'}, t("TextureAtlas.no_materials_selected")) + return {'CANCELLED'} + 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]) @@ -144,18 +171,17 @@ class AvatarToolKit_OT_AtlasMaterials(Operator): h: int = int(mat.albedo.size[1]) for obj in bpy.data.objects: - 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]) + 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 @@ -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) c_w = canvas.size[0] - #c_h = canvas.size[1] canvas_pixels: list[float] = list(canvas.pixels[:]) for mat in mat_images: 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")) 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.use_nodes = True 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.location.x = 7.29706335067749 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(normal_map_node.inputs["Color"], normal_node.outputs["Color"]) - + # Only update selected materials for meshes for obj in context.scene.objects: - mesh: Mesh = obj.data - mesh.materials.clear() - - mesh.materials.append(atlased_mat.material) + 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: + mesh.materials[i] = atlased_mat.material self.report({'INFO'}, t("TextureAtlas.atlas_completed")) return {"FINISHED"} @@ -271,5 +295,4 @@ class AvatarToolKit_OT_AtlasMaterials(Operator): self.report({'ERROR'}, t("TextureAtlas.atlas_error")) raise e return {"FINISHED"} - \ No newline at end of file diff --git a/resources/translations/en_US.json b/resources/translations/en_US.json index 31c1cf1..5d045e2 100644 --- a/resources/translations/en_US.json +++ b/resources/translations/en_US.json @@ -169,6 +169,10 @@ "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.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.invalid_armature": "Invalid armature selected", "Tools.connect_bones.min_distance.label": "Minimum Distance", @@ -223,6 +227,9 @@ "VisemePanel.start_viseme_creation": "Starting viseme creation...", "VisemePanel.viseme_created_successfully": "Viseme {viseme_name} created successfully", "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.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", @@ -249,7 +256,8 @@ "MMDOptions.renaming_bones": "Renaming bones", "MMDOptions.armature_optimization_complete": "Armature optimization complete", "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.CheckForUpdateButton.label": "Check for Updates", "Updater.CheckForUpdateButton.label_alt": "No Updates Available", @@ -282,7 +290,11 @@ "CreditsSupport.help_text1": "Check out our wiki first, we HIGHLY encourage", "CreditsSupport.help_text2": "that you read it before seeking further support.", "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" } } diff --git a/resources/translations/ja_JP.json b/resources/translations/ja_JP.json index 2b525e3..57dc4c2 100644 --- a/resources/translations/ja_JP.json +++ b/resources/translations/ja_JP.json @@ -1,150 +1,298 @@ { "authors": ["Avatar Toolkit Team"], "messages": { - "AutoVisemeButton.desc": "シェイプキーに基づいて自動的にビセームを作成する", + "AutoVisemeButton.desc": "シェイプキーに基づいて自動的にビセムを作成", "AutoVisemeButton.error.noShapekeys": "シェイプキーが見つかりません", "AutoVisemeButton.error.selectShapekeys": "シェイプキーを選択してください", - "AutoVisemeButton.label": "ビセームを作成", - "AutoVisemeButton.success": "ビセームが正常に作成されました", - "AvatarToolkit.alpha_warning": "これは早期アルファ版であり、バグや問題が発生する可能性があります。", - "AvatarToolkit.description": "Blenderでアバターを作成および編集するための", - "AvatarToolkit.label": "Avatar Toolkit", - "AvatarToolkit.welcome": "Avatar Toolkitへようこそ、", - "Export.resonite.desc": "すべてのアニメーションとマテリアルを含むGLBをエクスポートします。アニメーションデータについては以下を参照してください:", + "AutoVisemeButton.label": "ビセムを作成", + "AutoVisemeButton.success": "ビセムの作成に成功しました", + "AvatarToolkit.label": "Avatar Toolkit (アルファ版)", + "AvatarToolkit.desc1": "Avatar Toolkitは早期アクセス段階です", + "AvatarToolkit.desc2": "問題が発生する可能性があります。", + "AvatarToolkit.desc3": "問題を見つけた場合はGithubで報告してください。", + "Export.resonite.desc": "アニメーションとマテリアルを含むGLBをエクスポート。アニメーションデータについては:", "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_vrchat.desc": "VRChatにエクスポートします。ChilloutVRでも動作する可能性があります。Catsのエクスポートに似ています。", + "Importer.export_vrchat.desc": "VRChatにエクスポート(ChilloutVRでも動作する可能性あり)。Catsのエクスポートに似ています。", "Importer.export_vrchat.label": "VRChatにエクスポート", "Importer.mmd_anim_importer.desc": "MMDアニメーション(.vmd)をインポート", "Importer.mmd_anim_importer.label": "MMDアニメーション", - "Importing.importer_search_term": "https://search.brave.com/search?q=Blender+{extension}+インポーターアドオン&source=web", - "Importing.need_importer": "{extension}タイプに必要なインポーターがありません!インポーター検索用のウェブブラウザを開きます...", + "Importing.importer_search_term": "https://search.brave.com/search?q=blender+{extension}+importer+addon&source=web", + "Importing.need_importer": "{extension}タイプに必要なインポーターがありません!インポーター検索用にウェブブラウザを開きます...", "Language.auto": "自動", "Language.en_US": "English", "Language.ja_JP": "日本語", "Optimization.applying_transforms": "トランスフォームを適用中...", - "Optimization.cleaning_material_names": "マテリアル名をクリーニング中...", - "Optimization.cleaning_material_slots": "マテリアルスロットをクリーニング中...", - "Optimization.clearing_unused_data": "未使用データをクリア中...", - "Optimization.combine_materials.desc": "ドローコールを減らしパフォーマンスを向上させるために類似したマテリアルを結合する", + "Optimization.cleaning_material_names": "マテリアル名を整理中...", + "Optimization.cleaning_material_slots": "マテリアルスロットを整理中...", + "Optimization.clearing_unused_data": "未使用データを削除中...", + "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.consolidating_materials": "マテリアルを統合中...", "Optimization.finalizing": "最終処理中...", "Optimization.fixing_uv_coordinates": "UV座標を修正中...", - "Optimization.join_all_meshes.desc": "ドローコールを減らすためにすべてのメッシュを1つのオブジェクトにマージする", + "Optimization.join_all_meshes.desc": "描画コールを減らすため、すべてのメッシュを1つのオブジェクトに結合", "Optimization.join_all_meshes.label": "すべてのメッシュを結合", - "Optimization.join_error": "メッシュ結合中にエラーが発生しました", + "Optimization.join_error": "メッシュ結合中にエラーが発生", "Optimization.join_operation_failed": "結合操作に失敗しました", - "Optimization.join_selected_meshes.desc": "選択したメッシュのみを1つのオブジェクトにマージする", + "Optimization.join_selected_meshes.desc": "選択したメッシュのみを1つのオブジェクトに結合", "Optimization.join_selected_meshes.label": "選択したメッシュを結合", - "Optimization.joinmeshes.label": "メッシュを結合:", + "Optimization.joinmeshes.label": "メッシュの結合:", "Optimization.joining_meshes": "メッシュを結合中...", "Optimization.label": "最適化", "Optimization.material_attribute_mismatch": "マテリアル{material_name}の属性が一致しません。スキップします", "Optimization.materials_combined": "{num_combined}個のマテリアルを結合しました", - "Optimization.meshes_joined": "メッシュが正常に結合されました", + "Optimization.meshes_joined": "メッシュの結合に成功しました", "Optimization.no_armature_selected": "アーマチュアが選択されていません", "Optimization.no_mesh_selected": "メッシュオブジェクトが選択されていません", - "Optimization.no_meshes_found": "選択されたアーマチュアに対応するメッシュが見つかりません", + "Optimization.no_meshes_found": "選択したアーマチュアにメッシュが見つかりません", "Optimization.options.label": "最適化:", "Optimization.preparing_meshes": "メッシュを準備中...", "Optimization.processing_mesh_no_shapekeys": "シェイプキーのないメッシュ「{mesh_name}」を処理中", "Optimization.processing_shapekey": "メッシュ「{mesh_name}」のシェイプキー「{shapekeyname}」を処理中", "Optimization.remove_doubles_completed": "重複頂点の削除が完了しました", - "Optimization.remove_doubles_safely.desc": "口の形状などの重要な特徴を保持しながら重複頂点を削除する", + "Optimization.remove_doubles_safely.desc": "口の形状などの重要な特徴を保持しながら重複頂点を削除します。\n素早い解決策ですが、動く頂点は結合しません。", "Optimization.remove_doubles_safely.label": "安全に重複頂点を削除", - "Optimization.select_armature": "アーマチュアを選択してください", - "Optimization.select_at_least_two_meshes": "少なくとも2つのメッシュオブジェクトを選択してください", - "Optimization.selected_meshes_joined": "選択したメッシュが正常に結合されました", - "Optimization.selecting_meshes": "メッシュを選択中...", - "Optimization.transform_apply_failed": "トランスフォームの適用に失敗しました", - "Optimization.vertex_excluded": "シェイプキーのインデックス「{index}」に移動した頂点があります。重複マージから除外します!", + "Optimization.remove_doubles_safely_advanced.label": "高度な安全重複頂点削除", + "Optimization.remove_doubles_safely_advanced.desc": "口の形状などの重要な特徴を保持しながら重複頂点を削除します。\n基本版と異なり、動く頂点も結合しますがシェイプキーは保持します。\n例:唇を閉じることはありませんが、唇を構成する分割されたポリゴンは修正します。", + "UVTools.align_uv_to_target.warning.too_much": "エラー!選択が多すぎます。2つのエッジを選択していますか?", + "UVTools.align_uv_to_target.warning.need_a_line": "各選択オブジェクトにUVポイントの1行が必要です。オブジェクト「{obj}」がこの要件を満たしていません!", + "avatar_toolkit.align_uv_edges_to_target.label": "UVエッジをターゲットに合わせる", + "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_fbx.desc": "モデルをFBXとしてエクスポート", - "Quick_Access.export_fbx.label": "FBXをエクスポート", - "Quick_Access.export_menu.desc": "サポートされているフォーマットにエクスポート", + "Quick_Access.export_fbx.label": "FBXエクスポート", + "Quick_Access.export_menu.desc": "サポートされている形式にエクスポート", "Quick_Access.export_menu.label": "エクスポートメニュー", "Quick_Access.import": "インポート", "Quick_Access.import_export.label": "インポート/エクスポート:", "Quick_Access.import_menu.desc": "モデルをインポート", "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_pmx": "PMXをインポート", + "Quick_Access.import_pmx": "PMXインポート", "Quick_Access.import_pmx.desc": "MMD PMXモデルをインポート", - "Quick_Access.import_success": "モデルが正常にインポートされました", + "Quick_Access.import_success": "モデルのインポートに成功しました", "Quick_Access.label": "クイックアクセス", "Quick_Access.options": "クイックアクセス:", "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_resonite.label": "Resonite", "Settings.label": "設定", "Settings.language.desc": "アドオンのUI言語を選択", "Settings.language.label": "言語:", - "Settings.translation_restart_popup.description": "翻訳の更新に関する情報", + "Settings.translation_restart_popup.description": "翻訳の更新について", "Settings.translation_restart_popup.label": "翻訳の更新", - "Settings.translation_restart_popup.message1": "一部の翻訳は適用されない場合があります", - "Settings.translation_restart_popup.message2": "Blenderを再起動するまで。", + "Settings.translation_restart_popup.message1": "一部の翻訳はBlenderを再起動するまで", + "Settings.translation_restart_popup.message2": "適用されない場合があります。", "TextureAtlas.atlas_completed": "テクスチャアトラスの作成が完了しました", "TextureAtlas.atlas_error": "テクスチャアトラスの作成中にエラーが発生しました", "TextureAtlas.atlas_materials": "マテリアルをアトラス化", - "TextureAtlas.atlas_materials_desc": "モデルを最適化するためにマテリアルをアトラス化する", - "TextureAtlas.label": "テクスチャアトラス化", + "TextureAtlas.atlas_materials_desc": "モデルを最適化するためにマテリアルをアトラス化", + "TextureAtlas.label": "テクスチャアトラス", "TextureAtlas.loaded_list": "テクスチャアトラスマテリアルリストを読み込みました", "TextureAtlas.material_list_label": "テクスチャアトラスマテリアルリストのマテリアル", "TextureAtlas.reload_list": "テクスチャアトラスマテリアルリストを再読み込み", - "Tools.bones_translated_success": "すべての骨をヒューマノイド名に正常に変換しました", - "Tools.bones_translated_with_fails": "{translate_bone_fails}個の骨をヒューマノイド名に変換できませんでした。それらの名前に「」を追加します。", - "Tools.convert_to_resonite.desc": "モデルの骨の名前をResoniteと互換性のある名前に変換します", + "Tools.bones_translated_success": "すべてのボーンを正常にヒューマノイド名に変換しました", + "Tools.bones_translated_with_fails": "{translate_bone_fails}個のボーンをヒューマノイド名に変換できませんでした。名前に「」を追加します。", + "Tools.convert_to_resonite.desc": "モデルのボーン名をResonite互換の名前に変換", "Tools.convert_to_resonite.label": "Resoniteに変換", - "Tools.create_digitigrade_legs.desc": "選択した骨のチェーンからデジティグレード脚を作成する", - "Tools.create_digitigrade_legs.label": "デジティグレード脚を作成", - "Tools.digitigrade_legs.error.bone_format": "骨のフォーマットが正しくありません!4つの連続した骨のチェーンを選択してください!", - "Tools.digitigrade_legs.success": "デジティグレード脚が正常に作成されました", - "Tools.import_any_model.desc": "サポートされているモデル(FBX、SMD、DMX、GLTF、PMD、PMXなど)をインポートします。", + "Tools.create_digitigrade_legs.desc": "選択したボーンチェーンから獣脚を作成", + "Tools.create_digitigrade_legs.label": "獣脚を作成", + "Tools.digitigrade_legs.error.bone_format": "ボーンの形式が正しくありません!4つの連続したボーンのチェーンを選択してください!", + "Tools.digitigrade_legs.success": "獣脚の作成に成功しました", + "Tools.import_any_model.desc": "FBX、SMD、DMX、GLTF、PMD、PMXなど、サポートされているモデルをインポート", "Tools.import_any_model.label": "モデルをインポート", "Tools.label": "ツール", "Tools.no_armature_selected": "アーマチュアが選択されていません", "Tools.select_armature": "アーマチュアを選択してください", "Tools.tools_title.label": "ツール:", - "Tools.separate_by.label": "別:", + "Tools.separate_by.label": "分離方法:", "Tools.separate_by_materials.label": "マテリアルで分離", - "Tools.separate_by_materials.desc": "選択されたメッシュをマテリアルごとに分離します", - "Tools.separate_by_materials.success": "メッシュがマテリアルごとに正常に分離されました", - "Tools.separate_by_loose_parts.label": "バラバラの部分で分離", - "Tools.separate_by_loose_parts.desc": "選択されたメッシュをバラバラの部分ごとに分離します", - "Tools.separate_by_loose_parts.success": "メッシュがバラバラの部分ごとに正常に分離されました", + "Tools.separate_by_materials.desc": "選択したメッシュをマテリアルで分離", + "Tools.separate_by_materials.success": "メッシュをマテリアルで分離しました", + "Tools.separate_by_loose_parts.label": "分離パーツで分離", + "Tools.separate_by_loose_parts.desc": "選択したメッシュを分離パーツで分離", + "Tools.separate_by_loose_parts.success": "メッシュを分離パーツで分離しました", "Tools.apply_transforms.label": "トランスフォームを適用", - "Tools.apply_transforms.desc": "アーマチュアとそのメッシュに位置、回転、スケールを適用します", + "Tools.apply_transforms.desc": "アーマチュアとそのメッシュに位置、回転、スケールを適用", "Tools.apply_transforms.invalid_armature": "無効なアーマチュアが選択されています", - "Tools.apply_transforms.success": "アーマチュアとメッシュにトランスフォームが正常に適用されました", - "VisemePanel.create_visemes": "ビセームを作成", - "VisemePanel.creating_viseme": "ビセームを作成中:{viseme_name}", - "VisemePanel.creating_viseme_detail": "ビセームを作成中:{viseme_name}", - "VisemePanel.creating_visemes": "ビセームを作成中...", + "Tools.apply_transforms.success": "アーマチュアとメッシュにトランスフォームを適用しました", + "Tools.remove_unused_shapekeys.label": "未使用のシェイプキーを削除", + "Tools.remove_unused_shapekeys.tolerance.desc": "シェイプキーを保持する最小の頂点移動量\n(任意の座標での位置)", + "Tools.remove_unused_shapekeys.desc": "何も動かさないシェイプキーを削除します。\nカテゴリーシェイプキーは削除しません。\n(例:名前に「~」「-」「=」を含むもの)", + "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.noMesh": "メッシュが選択されていません", - "VisemePanel.error.noShapekeys": "選択されたメッシュにシェイプキーがありません", - "VisemePanel.error.selectMesh": "ビセームを作成するメッシュを選択してください", - "VisemePanel.info.selectMesh": "ビセームを作成するメッシュを選択してください", - "VisemePanel.label": "ビセーム", - "VisemePanel.mixing_shape": "シェイプをミックス中:{shape_name}、値:{value}", - "VisemePanel.mouth_a.desc": "'A'の口の形状のシェイプキー", + "VisemePanel.error.noShapekeys": "選択したメッシュにシェイプキーがありません", + "VisemePanel.error.selectMesh": "ビセムを作成するメッシュを選択してください", + "VisemePanel.info.selectMesh": "ビセムを作成するメッシュを選択してください", + "VisemePanel.label": "ビセム", + "VisemePanel.mixing_shape": "シェイプを混合中:{shape_name} 値:{value}", + "VisemePanel.mouth_a.desc": "'A'の口の形のシェイプキー", "VisemePanel.mouth_a.label": "口 A", - "VisemePanel.mouth_ch.desc": "'CH'の口の形状のシェイプキー", + "VisemePanel.mouth_ch.desc": "'CH'の口の形のシェイプキー", "VisemePanel.mouth_ch.label": "口 CH", - "VisemePanel.mouth_o.desc": "'O'の口の形状のシェイプキー", + "VisemePanel.mouth_o.desc": "'O'の口の形のシェイプキー", "VisemePanel.mouth_o.label": "口 O", - "VisemePanel.removing_existing_viseme": "既存のビセームを削除中:{viseme_name}", - "VisemePanel.removing_existing_visemes": "既存のビセームを削除中...", + "VisemePanel.removing_existing_viseme": "既存のビセムを削除中:{viseme_name}", + "VisemePanel.removing_existing_visemes": "既存のビセムを削除中...", "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_desc": "ビセームシェイプキーの強度", - "VisemePanel.sorting_shapekeys": "シェイプキーをソート中...", - "VisemePanel.start_viseme_creation": "ビセーム作成を開始...", - "VisemePanel.viseme_created_successfully": "ビセーム{viseme_name}が正常に作成されました", - "VisemePanel.viseme_creation_completed": "ビセーム作成が完了しました。" + "VisemePanel.shape_intensity_desc": "ビセムシェイプキーの強度", + "VisemePanel.sorting_shapekeys": "シェイプキーを並べ替え中...", + "VisemePanel.start_viseme_creation": "ビセム作成を開始...", + "VisemePanel.viseme_created_successfully": "ビセム{viseme_name}の作成に成功しました", + "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": "最新バージョンにアップデート" } -} +} \ No newline at end of file diff --git a/ui/atlas_materials.py b/ui/atlas_materials.py index 285d3b5..7d5a666 100644 --- a/ui/atlas_materials.py +++ b/ui/atlas_materials.py @@ -1,11 +1,56 @@ from bpy.types import UIList, Panel, UILayout, Object, Context,Material, Operator import bpy +from math import sqrt from ..core.register import register_wrap from .panel import AvatarToolKit_PT_AvatarToolkitPanel, CATEGORY_NAME from ..core.common import SceneMatClass, MaterialListBool, get_selected_armature from ..functions.atlas_materials import AvatarToolKit_OT_AtlasMaterials 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 class AvatarToolKit_OT_ExpandSectionMaterials(Operator): bl_idname = 'avatar_toolkit.expand_section_materials' @@ -40,23 +85,70 @@ class AvatarToolKit_UL_MaterialTextureAtlasProperties(UIList): bl_space_type = 'VIEW_3D' 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: - box = layout.box() - row = box.row() - row.label(text=item.mat.name, icon = "MATERIAL") - col = box.row() - col.prop(item.mat, "texture_atlas_albedo") - col = box.row() - col.prop(item.mat, "texture_atlas_normal") - col = box.row() - col.prop(item.mat, "texture_atlas_emission") - col = box.row() - col.prop(item.mat, "texture_atlas_ambient_occlusion") - col = box.row() - col.prop(item.mat, "texture_atlas_height") - col = box.row() - col.prop(item.mat, "texture_atlas_roughness") + if context.scene.material_search_filter and context.scene.material_search_filter.lower() not in item.mat.name.lower(): + return + + row = layout.row() + + # Add a clear checkbox for material selection + row.prop(item.mat, "include_in_atlas", text="", icon='CHECKBOX_HLT' if item.mat.include_in_atlas else 'CHECKBOX_DEHLT') + + # Material name and expansion toggle + row.prop(item.mat, "material_expanded", + text=item.mat.name, + icon='DOWNARROW_HLT' if item.mat.material_expanded else 'RIGHTARROW', + emboss=False) + + # 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 class AvatarToolKit_PT_TextureAtlasPanel(Panel): @@ -74,7 +166,6 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel): if armature: layout.label(text=t("TextureAtlas.label"), icon='TEXTURE') - layout.separator(factor=0.5) box = layout.box() @@ -86,16 +177,21 @@ class AvatarToolKit_PT_TextureAtlasPanel(Panel): if context.scene.texture_atlas_Has_Mat_List_Shown: row = box.row() - row.template_list(AvatarToolKit_UL_MaterialTextureAtlasProperties.bl_idname, 'material_list', - context.scene, 'materials', context.scene, 'texture_atlas_material_index', - rows=12, type='DEFAULT') + row.template_list(AvatarToolKit_UL_MaterialTextureAtlasProperties.bl_idname, + 'material_list', + context.scene, + 'materials', + context.scene, + 'texture_atlas_material_index', + rows=12, + type='DEFAULT') layout.separator(factor=1.0) row = layout.row() 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: layout.label(text=t("Tools.select_armature"), icon='ERROR') -