# MIT License # Copyright (c) 2017 GiveMeAllYourCats # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the 'Software'), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. # Code author: GiveMeAllYourCats # Repo: https://github.com/michaeldegroot/cats-blender-plugin # Edits by: GiveMeAllYourCats, Hotox import re import bpy import time import bmesh import platform import numpy as np from math import degrees from mathutils import Vector from datetime import datetime from html.parser import HTMLParser from html.entities import name2codepoint from . import common as Common #from . import supporter as Supporter #from . import decimation as Decimation #from . import translate as Translate #from . import armature_bones as Bones #from . import settings as Settings #from .register import register_wrap #from .translations import t #from mmd_tools_local import utils #from mmd_tools_local.panels import tool as mmd_tool #from mmd_tools_local.panels import util_tools as mmd_util_tools #from mmd_tools_local.panels import view_prop as mmd_view_prop def version_2_79_or_older(): return bpy.app.version < (2, 80) def version_2_93_or_older(): return bpy.app.version < (2, 90) def get_objects(): return bpy.context.scene.objects if version_2_79_or_older() else bpy.context.view_layer.objects class SavedData: __object_properties = {} __active_object = None def __init__(self): # initialize as instance attributes rather than class attributes self.__object_properties = {} self.__active_object = None for obj in get_objects(): mode = obj.mode selected = is_selected(obj) hidden = is_hidden(obj) pose = None if obj.type == 'ARMATURE': pose = obj.data.pose_position self.__object_properties[obj.name] = [mode, selected, hidden, pose] active = get_active() if active: self.__active_object = active.name def load(self, ignore=None, load_mode=True, load_select=True, load_hide=True, load_active=True, hide_only=False): if not ignore: ignore = [] if hide_only: load_mode = False load_select = False load_active = False for obj_name, values in self.__object_properties.items(): # print(obj_name, ignore) if obj_name in ignore: continue obj = get_objects().get(obj_name) if not obj: continue mode, selected, hidden, pose = values # print(obj_name, mode, selected, hidden) print(obj_name, pose) if load_mode and obj.mode != mode: set_active(obj, skip_sel=True) switch(mode, check_mode=False) if pose: obj.data.pose_position = pose if load_select: select(obj, selected) if load_hide: hide(obj, hidden) # Set the active object if load_active and self.__active_object and get_objects().get(self.__active_object): if self.__active_object not in ignore and self.__active_object != get_active(): set_active(get_objects().get(self.__active_object), skip_sel=True) def get_armature(armature_name=None): if not armature_name: armature_name = bpy.context.scene.armature for obj in get_objects(): if obj.type == 'ARMATURE': if (armature_name and obj.name == armature_name) or not armature_name: return obj return None def get_armature_objects(): armatures = [] for obj in get_objects(): if obj.type == 'ARMATURE': armatures.append(obj) return armatures def get_top_parent(child): if child.parent: return get_top_parent(child.parent) return child def unhide_all_unnecessary(): try: bpy.ops.object.hide_view_clear() except RuntimeError: pass for collection in bpy.data.collections: collection.hide_select = False collection.hide_viewport = False def unhide_all(): for obj in get_objects(): hide(obj, False) set_unselectable(obj, False) if not version_2_79_or_older(): unhide_all_unnecessary() def unhide_children(parent): for child in parent.children: hide(child, False) set_unselectable(child, False) unhide_children(child) def unhide_all_of(obj_to_unhide=None): if not obj_to_unhide: return top_parent = get_top_parent(obj_to_unhide) hide(top_parent, False) set_unselectable(top_parent, False) unhide_children(top_parent) def unselect_all(): for obj in get_objects(): select(obj, False) def set_active(obj, skip_sel=False): if not skip_sel: select(obj) if version_2_79_or_older(): bpy.context.scene.objects.active = obj else: bpy.context.view_layer.objects.active = obj def get_active(): if version_2_79_or_older(): return bpy.context.scene.objects.active return bpy.context.view_layer.objects.active def select(obj, sel=True): if sel: hide(obj, False) obj.select_set(sel) def is_selected(obj): if version_2_79_or_older(): return obj.select return obj.select_get() def hide(obj, val=True): obj.hide_set(val) def is_hidden(obj): return obj.hide_get() def set_unselectable(obj, val=True): obj.hide_select = val def switch(new_mode, check_mode=True): if check_mode and get_active() and get_active().mode == new_mode: return if bpy.ops.object.mode_set.poll(): bpy.ops.object.mode_set(mode=new_mode, toggle=False) def set_default_stage_old(): switch('OBJECT') unhide_all() unselect_all() armature = get_armature() set_active(armature) return armature def set_default_stage(): """ Selects the armature, unhides everything and sets the modes of every object to object mode :return: the armature """ # Remove rigidbody collections, as they cause issues if they are not in the view_layer if not version_2_79_or_older() and bpy.context.scene.remove_rigidbodies_joints: print('Collections:') for collection in bpy.data.collections: print(' ' + collection.name, collection.name.lower()) if 'rigidbody' in collection.name.lower(): print('DELETE') for obj in collection.objects: delete(obj) bpy.data.collections.remove(collection) if not bpy.context.scene.cats_is_unittest: unhide_all() unselect_all() for obj in get_objects(): set_active(obj) switch('OBJECT') if obj.type == 'ARMATURE': # obj.data.pose_position = 'REST' pass select(obj, False) armature = get_armature() if armature: set_active(armature) # if version_2_79_or_older(): # armature.layers[0] = True # Fix broken armatures if not bpy.context.scene.armature: bpy.context.scene.armature = armature.name return armature def apply_modifier(mod, as_shapekey=False): if bpy.app.version < (2, 90): bpy.ops.object.modifier_apply(apply_as='SHAPE' if as_shapekey else 'DATA', modifier=mod.name) return if as_shapekey: bpy.ops.object.modifier_apply_as_shapekey(keep_modifier=False, modifier=mod.name) else: bpy.ops.object.modifier_apply(modifier=mod.name) def remove_bone(find_bone): armature = get_armature() switch('EDIT') for bone in armature.data.edit_bones: if bone.name == find_bone: armature.data.edit_bones.remove(bone) def remove_empty(): armature = set_default_stage() if armature.parent and armature.parent.type == 'EMPTY': unselect_all() set_active(armature.parent) bpy.ops.object.delete(use_global=False) unselect_all() def get_bone_angle(p1, p2): try: ret = degrees((p1.head - p1.tail).angle(p2.head - p2.tail)) except ValueError: ret = 0 return ret def remove_unused_vertex_groups(ignore_main_bones=False): remove_count = 0 unselect_all() for mesh in get_meshes_objects(mode=2): mesh.update_from_editmode() vgroup_used = {i: False for i, k in enumerate(mesh.vertex_groups)} for v in mesh.data.vertices: for g in v.groups: if g.weight > 0.0: vgroup_used[g.group] = True for i, used in sorted(vgroup_used.items(), reverse=True): if not used: if ignore_main_bones and mesh.vertex_groups[i].name in Bones.dont_delete_these_main_bones: continue mesh.vertex_groups.remove(mesh.vertex_groups[i]) remove_count += 1 return remove_count def remove_unused_vertex_groups_of_mesh(mesh): remove_count = 0 unselect_all() mesh.update_from_editmode() vgroup_used = {i: False for i, k in enumerate(mesh.vertex_groups)} for v in mesh.data.vertices: for g in v.groups: if g.weight > 0.0: vgroup_used[g.group] = True for i, used in sorted(vgroup_used.items(), reverse=True): if not used: mesh.vertex_groups.remove(mesh.vertex_groups[i]) remove_count += 1 return remove_count def find_center_vector_of_vertex_group(mesh, vertex_group): data = mesh.data verts = data.vertices verts_in_group = [] for vert in verts: i = vert.index try: if mesh.vertex_groups[vertex_group].weight(i) > 0: verts_in_group.append(vert) except RuntimeError: # vertex is not in the group pass # Find the average vector point of the vertex cluster divide_by = len(verts_in_group) total = Vector() if divide_by == 0: return False for vert in verts_in_group: total += vert.co average = total / divide_by return average def vertex_group_exists(mesh_name, bone_name): mesh = get_objects()[mesh_name] data = mesh.data verts = data.vertices for vert in verts: i = vert.index try: mesh.vertex_groups[bone_name].weight(i) return True except: pass return False def get_meshes(self, context): # Modes: # 0 = With Armature only # 1 = Without armature only # 2 = All meshes choices = [] for mesh in get_meshes_objects(mode=0, check=False): choices.append((mesh.name, mesh.name, mesh.name)) bpy.types.Object.Enum = sorted(choices, key=lambda x: tuple(x[0].lower())) return bpy.types.Object.Enum def get_top_meshes(self, context): choices = [] for mesh in get_meshes_objects(mode=1, check=False): choices.append((mesh.name, mesh.name, mesh.name)) bpy.types.Object.Enum = sorted(choices, key=lambda x: tuple(x[0].lower())) return bpy.types.Object.Enum def get_all_meshes(self, context): choices = [] for mesh in get_meshes_objects(mode=2, check=False): choices.append((mesh.name, mesh.name, mesh.name)) bpy.types.Object.Enum = sorted(choices, key=lambda x: tuple(x[0].lower())) return bpy.types.Object.Enum def get_armature_list(self, context): choices = [] for armature in get_armature_objects(): # Set name displayed in list name = armature.data.name if name.startswith('Armature ('): name = armature.name + ' (' + name.replace('Armature (', '')[:-1] + ')' # 1. Will be returned by context.scene # 2. Will be shown in lists # 3. will be shown in the hover description (below description) choices.append((armature.name, name, armature.name)) if len(choices) == 0: choices.append(('None', 'None', 'None')) bpy.types.Object.Enum = sorted(choices, key=lambda x: tuple(x[0].lower())) return bpy.types.Object.Enum def get_armature_merge_list(self, context): choices = [] current_armature = context.scene.merge_armature_into for armature in get_armature_objects(): if armature.name != current_armature: # Set name displayed in list name = armature.data.name if name.startswith('Armature ('): name = armature.name + ' (' + name.replace('Armature (', '')[:-1] + ')' # 1. Will be returned by context.scene # 2. Will be shown in lists # 3. will be shown in the hover description (below description) choices.append((armature.name, name, armature.name)) if len(choices) == 0: choices.append(('None', 'None', 'None')) bpy.types.Object.Enum = sorted(choices, key=lambda x: tuple(x[0].lower())) return bpy.types.Object.Enum def get_meshes_decimation(self, context): choices = [] for object in bpy.context.scene.objects: if object.type == 'MESH': if object.parent and object.parent.type == 'ARMATURE' and object.parent.name == bpy.context.scene.armature: if object.name in Decimation.ignore_meshes: continue # 1. Will be returned by context.scene # 2. Will be shown in lists # 3. will be shown in the hover description (below description) choices.append((object.name, object.name, object.name)) bpy.types.Object.Enum = sorted(choices, key=lambda x: tuple(x[0].lower())) return bpy.types.Object.Enum def get_bones_head(self, context): return get_bones(names=['Head']) def get_bones_eye_l(self, context): return get_bones(names=['Eye_L', 'EyeReturn_L']) def get_bones_eye_r(self, context): return get_bones(names=['Eye_R', 'EyeReturn_R']) def get_bones_merge(self, context): return get_bones(armature_name=bpy.context.scene.merge_armature_into) # names - The first object will be the first one in the list. So the first one has to be the one that exists in the most models def get_bones(names=None, armature_name=None, check_list=False): if not names: names = [] if not armature_name: armature_name = bpy.context.scene.armature choices = [] armature = get_armature(armature_name=armature_name) if not armature: bpy.types.Object.Enum = choices return bpy.types.Object.Enum # print("") # print("START DEBUG UNICODE") # print("") for bone in armature.data.bones: # print(bone.name) try: # 1. Will be returned by context.scene # 2. Will be shown in lists # 3. will be shown in the hover description (below description) choices.append((bone.name, bone.name, bone.name)) except UnicodeDecodeError: print("ERROR", bone.name) choices.sort(key=lambda x: tuple(x[0].lower())) choices2 = [] for name in names: if name in armature.data.bones and choices[0][0] != name: choices2.append((name, name, name)) if not check_list: for choice in choices: choices2.append(choice) bpy.types.Object.Enum = choices2 return bpy.types.Object.Enum def get_shapekeys_mouth_ah(self, context): return get_shapekeys(context, ['MTH A', 'Ah', 'A'], True, False, False, False) def get_shapekeys_mouth_oh(self, context): return get_shapekeys(context, ['MTH U', 'Oh', 'O', 'Your'], True, False, False, False) def get_shapekeys_mouth_ch(self, context): return get_shapekeys(context, ['MTH I', 'Glue', 'Ch', 'I', 'There'], True, False, False, False) def get_shapekeys_eye_blink_l(self, context): return get_shapekeys(context, ['EYE Close L', 'Wink 2', 'Wink', 'Wink left', 'Wink Left', 'Blink (Left)', 'Blink', 'Basis'], False, False, False, False) def get_shapekeys_eye_blink_r(self, context): return get_shapekeys(context, ['EYE Close R', 'Wink 2 right', 'Wink 2 Right', 'Wink right 2', 'Wink Right 2', 'Wink right', 'Wink Right', 'Blink (Right)', 'Basis'], False, False, False, False) def get_shapekeys_eye_low_l(self, context): return get_shapekeys(context, ['Basis'], False, False, False, False) def get_shapekeys_eye_low_r(self, context): return get_shapekeys(context, ['Basis'], False, False, False, False) def get_shapekeys_decimation(self, context): return get_shapekeys(context, ['MTH A', 'Ah', 'A', 'MTH U', 'Oh', 'O', 'Your', 'MTH I', 'Glue', 'Ch', 'I', 'There', 'Wink 2', 'Wink', 'Wink left', 'Wink Left', 'Blink (Left)', 'Wink 2 right', 'EYE Close R', 'EYE Close L', 'Wink 2 Right', 'Wink right 2', 'Wink Right 2', 'Wink right', 'Wink Right', 'Blink (Right)', 'Blink'], False, True, True, False) def get_shapekeys_decimation_list(self, context): return get_shapekeys(context, ['MTH A', 'Ah', 'A', 'MTH U', 'Oh', 'O', 'Your', 'MTH I', 'Glue', 'Ch', 'I', 'There', 'Wink 2', 'Wink', 'Wink left', 'Wink Left', 'Blink (Left)', 'Wink 2 right', 'EYE Close R', 'EYE Close L', 'Wink 2 Right', 'Wink right 2', 'Wink Right 2', 'Wink right', 'Wink Right', 'Blink (Right)', 'Blink'], False, True, True, True) # names - The first object will be the first one in the list. So the first one has to be the one that exists in the most models # no_basis - If this is true the Basis will not be available in the list def get_shapekeys(context, names, is_mouth, no_basis, decimation, return_list): choices = [] choices_simple = [] meshes_list = get_meshes_objects(check=False) if decimation: meshes = meshes_list elif meshes_list: if is_mouth: meshes = [get_objects().get(context.scene.mesh_name_viseme)] else: meshes = [get_objects().get(context.scene.mesh_name_eye)] else: bpy.types.Object.Enum = choices return bpy.types.Object.Enum for mesh in meshes: if not mesh or not has_shapekeys(mesh): bpy.types.Object.Enum = choices return bpy.types.Object.Enum for shapekey in mesh.data.shape_keys.key_blocks: name = shapekey.name if name in choices_simple: continue if no_basis and name == 'Basis': continue if decimation and name in Decimation.ignore_shapes: continue # 1. Will be returned by context.scene # 2. Will be shown in lists # 3. will be shown in the hover description (below description) choices.append((name, name, name)) choices_simple.append(name) choices.sort(key=lambda x: tuple(x[0].lower())) choices2 = [] for name in names: if name in choices_simple and len(choices) > 1 and choices[0][0] != name: if decimation and name in Decimation.ignore_shapes: continue choices2.append((name, name, name)) for choice in choices: choices2.append(choice) bpy.types.Object.Enum = choices2 if return_list: shape_list = [] for choice in choices2: shape_list.append(choice[0]) return shape_list return bpy.types.Object.Enum def fix_armature_names(armature_name=None): if not armature_name: armature_name = bpy.context.scene.armature base_armature = get_armature(armature_name=bpy.context.scene.merge_armature_into) merge_armature = get_armature(armature_name=bpy.context.scene.merge_armature) # Armature should be named correctly (has to be at the end because of multiple armatures) armature = get_armature(armature_name=armature_name) armature.name = 'Armature' if not armature.data.name.startswith('Armature'): Translate.update_dictionary(armature.data.name) armature.data.name = 'Armature (' + Translate.translate(armature.data.name, add_space=True)[0] + ')' # Reset the armature lists try: bpy.context.scene.armature = armature.name except TypeError: pass try: if base_armature: bpy.context.scene.merge_armature_into = base_armature.name except TypeError: pass try: if merge_armature: bpy.context.scene.merge_armature = merge_armature.name except TypeError: pass def get_texture_sizes(self, context): bpy.types.Object.Enum = [ ("1024", "1024 (low)", "1024"), ("2048", "2048 (medium)", "2048"), ("4096", "4096 (high)", "4096") ] return bpy.types.Object.Enum def get_meshes_objects(armature_name=None, mode=0, check=True, visible_only=False): # Modes: # 0 = With armatures only # 1 = Top level only # 2 = All meshes # 3 = Selected only if not armature_name: armature = get_armature() if armature: armature_name = armature.name meshes = [] for ob in get_objects(): if ob.type == 'MESH': if mode == 0 or mode == 5: if ob.parent: if ob.parent.type == 'ARMATURE' and ob.parent.name == armature_name: meshes.append(ob) elif ob.parent.parent and ob.parent.parent.type == 'ARMATURE' and ob.parent.parent.name == armature_name: meshes.append(ob) elif mode == 1: if not ob.parent: meshes.append(ob) elif mode == 2: meshes.append(ob) elif mode == 3: if is_selected(ob): meshes.append(ob) if visible_only: for mesh in meshes: if is_hidden(mesh): meshes.remove(mesh) # Check for broken meshes and delete them if check: current_active = get_active() to_remove = [] for mesh in meshes: selected = is_selected(mesh) # print(mesh.name, mesh.users) set_active(mesh) if not get_active(): to_remove.append(mesh) if not selected: select(mesh, False) for mesh in to_remove: print('DELETED CORRUPTED MESH:', mesh.name, mesh.users) meshes.remove(mesh) delete(mesh) if current_active: set_active(current_active) return meshes def join_meshes(armature_name=None, mode=0, apply_transformations=True, repair_shape_keys=True): # Modes: # 0 - Join all meshes # 1 - Join selected only if not armature_name: armature_name = bpy.context.scene.armature # Get meshes to join meshes_to_join = get_meshes_objects(armature_name=armature_name, mode=3 if mode == 1 else 0) if not meshes_to_join: reset_context_scenes() return None set_default_stage() unselect_all() if apply_transformations: apply_transforms(armature_name=armature_name) unselect_all() # Apply existing decimation modifiers and select the meshes for joining for mesh in meshes_to_join: set_active(mesh) # Apply decimation modifiers for mod in mesh.modifiers: if mod.type == 'DECIMATE': if mod.decimate_type == 'COLLAPSE' and mod.ratio == 1: mesh.modifiers.remove(mod) continue if mod.decimate_type == 'UNSUBDIV' and mod.iterations == 0: mesh.modifiers.remove(mod) continue if has_shapekeys(mesh): bpy.ops.object.shape_key_remove(all=True) apply_modifier(mod) elif mod.type == 'SUBSURF': mesh.modifiers.remove(mod) elif mod.type == 'MIRROR': if not has_shapekeys(mesh): apply_modifier(mod) # Standardize UV maps name if version_2_79_or_older(): if mesh.data.uv_textures: mesh.data.uv_textures[0].name = 'UVMap' for mat_slot in mesh.material_slots: if mat_slot and mat_slot.material: for tex_slot in mat_slot.material.texture_slots: if tex_slot and tex_slot.texture and tex_slot.texture_coords == 'UV': tex_slot.uv_layer = 'UVMap' else: if mesh.data.uv_layers: mesh.data.uv_layers[0].name = 'UVMap' # Get the name of the active mesh in order to check if it was deleted later active_mesh_name = get_active().name # Join the meshes if bpy.ops.object.join.poll(): bpy.ops.object.join() else: print('NO MESH COMBINED!') # Delete meshes that somehow weren't deleted. Both pre and post join mesh deletion methods are needed! for mesh in get_meshes_objects(armature_name=armature_name): if mesh.name == active_mesh_name: set_active(mesh) elif mesh.name in meshes_to_join: delete(mesh) print('DELETED', mesh.name, mesh.users) # Rename result to Body and correct modifiers mesh = get_active() if mesh: # If its the only mesh in the armature left, rename it to Body if len(get_meshes_objects(armature_name=armature_name)) == 1: mesh.name = 'Body' mesh.parent_type = 'OBJECT' repair_mesh(mesh, armature_name) if repair_shape_keys: repair_shapekey_order(mesh.name) reset_context_scenes() # Update the material list of the Material Combiner update_material_list() return mesh def repair_mesh(mesh, armature_name): mesh.parent_type = 'OBJECT' # Remove duplicate armature modifiers mod_count = 0 for mod in mesh.modifiers: mod.show_expanded = False if mod.type == 'ARMATURE': mod_count += 1 if mod_count > 1: bpy.ops.object.modifier_remove(modifier=mod.name) continue mod.object = get_armature(armature_name=armature_name) mod.show_viewport = True # Add armature mod if there is none if mod_count == 0: mod = mesh.modifiers.new("Armature", 'ARMATURE') mod.object = get_armature(armature_name=armature_name) def apply_transforms(armature_name=None): if not armature_name: armature_name = bpy.context.scene.armature armature = get_armature(armature_name=armature_name) # Apply transforms on armature unselect_all() set_active(armature) switch('OBJECT') bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) # Apply transforms of meshes for mesh in get_meshes_objects(armature_name=armature_name): unselect_all() set_active(mesh) bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) def apply_all_transforms(): def apply_transforms_with_children(parent): unselect_all() set_active(parent) switch('OBJECT') bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) for child in parent.children: apply_transforms_with_children(child) for obj in get_objects(): if not obj.parent: apply_transforms_with_children(obj) def reset_transforms(armature_name=None): if not armature_name: armature_name = bpy.context.scene.armature armature = get_armature(armature_name=armature_name) # Reset transforms on armature for i in range(0, 3): armature.location[i] = 0 armature.rotation_euler[i] = 0 armature.scale[i] = 1 # Apply transforms of meshes for mesh in get_meshes_objects(armature_name=armature_name): for i in range(0, 3): mesh.location[i] = 0 mesh.rotation_euler[i] = 0 mesh.scale[i] = 1 def separate_by_materials(context, mesh): prepare_separation(mesh) utils.separateByMaterials(mesh) for ob in context.selected_objects: if ob.type == 'MESH': hide(ob, False) clean_shapekeys(ob) utils.clearUnusedMeshes() # Update the material list of the Material Combiner update_material_list() def separate_by_loose_parts(context, mesh): prepare_separation(mesh) # Correctly put mesh together. This is done to prevent extremely small pieces. # This essentially does nothing but merges the extremely small parts together. remove_doubles(mesh, 0, save_shapes=True) utils.separateByMaterials(mesh) meshes = [] for ob in context.selected_objects: if ob.type == 'MESH': hide(ob, False) meshes.append(ob) wm = bpy.context.window_manager current_step = 0 wm.progress_begin(current_step, len(meshes)) for mesh in meshes: unselect_all() set_active(mesh) bpy.ops.mesh.separate(type='LOOSE') meshes2 = [] for ob in context.selected_objects: if ob.type == 'MESH': meshes2.append(ob) ## This crashes blender, but would be better # unselect_all() # for mesh2 in meshes2: # if len(mesh2.data.vertices) <= 3: # select(mesh2) # elif bpy.ops.object.join.poll(): # bpy.ops.object.join() # unselect_all() for mesh2 in meshes2: clean_shapekeys(mesh2) current_step += 1 wm.progress_update(current_step) wm.progress_end() utils.clearUnusedMeshes() # Update the material list of the Material Combiner update_material_list() def separate_by_shape_keys(context, mesh): prepare_separation(mesh) switch('EDIT') bpy.ops.mesh.select_mode(type="VERT") bpy.ops.mesh.select_all(action='DESELECT') switch('OBJECT') selected_count = 0 max_count = 0 if has_shapekeys(mesh): for kb in mesh.data.shape_keys.key_blocks: for i, (v0, v1) in enumerate(zip(kb.relative_key.data, kb.data)): max_count += 1 if v0.co != v1.co: mesh.data.vertices[i].select = True selected_count += 1 if not selected_count or selected_count == max_count: return False switch('EDIT') bpy.ops.mesh.select_all(action='INVERT') bpy.ops.mesh.separate(type='SELECTED') for ob in context.selected_objects: if ob.type == 'MESH': if ob != get_active(): print('not active', ob.name) active_tmp = get_active() ob.name = ob.name.replace('.001', '') + '.no_shapes' set_active(ob) bpy.ops.object.shape_key_remove(all=True) set_active(active_tmp) select(ob, False) else: print('active', ob.name) clean_shapekeys(ob) switch('OBJECT') utils.clearUnusedMeshes() # Update the material list of the Material Combiner update_material_list() return True def separate_by_cats_protection(context, mesh): prepare_separation(mesh) switch('EDIT') bpy.ops.mesh.select_mode(type="VERT") bpy.ops.mesh.select_all(action='DESELECT') switch('OBJECT') selected_count = 0 max_count = 0 if has_shapekeys(mesh): for kb in mesh.data.shape_keys.key_blocks: if kb.name == 'Basis Original': for i, (v0, v1) in enumerate(zip(kb.relative_key.data, kb.data)): max_count += 1 if v0.co != v1.co: mesh.data.vertices[i].select = True selected_count += 1 if not selected_count or selected_count == max_count: return False switch('EDIT') bpy.ops.mesh.select_all(action='INVERT') bpy.ops.mesh.separate(type='SELECTED') for ob in context.selected_objects: if ob.type == 'MESH': if ob != get_active(): print('not active', ob.name) active_tmp = get_active() ob.name = ob.name.replace('.001', '') + '.no_shapes' set_active(ob) bpy.ops.object.shape_key_remove(all=True) set_active(active_tmp) select(ob, False) else: print('active', ob.name) clean_shapekeys(ob) switch('OBJECT') utils.clearUnusedMeshes() # Update the material list of the Material Combiner update_material_list() return True def prepare_separation(mesh): set_default_stage() unselect_all() # Remove Rigidbodies and joints if bpy.context.scene.remove_rigidbodies_joints: for obj in get_objects(): if 'rigidbodies' in obj.name or 'joints' in obj.name: delete_hierarchy(obj) save_shapekey_order(mesh.name) set_active(mesh) for mod in mesh.modifiers: if mod.type == 'DECIMATE': mesh.modifiers.remove(mod) else: mod.show_expanded = False clean_material_names(mesh) def clean_shapekeys(mesh): # Remove empty shapekeys if has_shapekeys(mesh): for kb in mesh.data.shape_keys.key_blocks: if can_remove_shapekey(kb): mesh.shape_key_remove(kb) if len(mesh.data.shape_keys.key_blocks) == 1: mesh.shape_key_remove(mesh.data.shape_keys.key_blocks[0]) def can_remove_shapekey(key_block): if 'mmd_' in key_block.name: return True if key_block.relative_key == key_block: return False # Basis for v0, v1 in zip(key_block.relative_key.data, key_block.data): if v0.co != v1.co: return False return True def separate_by_verts(): for obj in bpy.context.selected_objects: if obj.type == 'MESH' and len(obj.vertex_groups) > 0: Common.set_active(obj) bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_mode(type='VERT') for vgroup in obj.vertex_groups: bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.object.vertex_group_set_active(group=vgroup.name) bpy.ops.object.vertex_group_select() bpy.ops.mesh.separate(type='SELECTED') bpy.ops.object.mode_set(mode='OBJECT') def reset_context_scenes(): head_bones = get_bones_head(None, bpy.context) if len(head_bones) > 0: bpy.context.scene.head = head_bones[0][0] bpy.context.scene.eye_left = get_bones_eye_l(None, bpy.context)[0][0] bpy.context.scene.eye_right = get_bones_eye_r(None, bpy.context)[0][0] meshes = get_meshes(None, bpy.context) if len(meshes) > 0: mesh = meshes[0][0] if not bpy.context.scene.mesh_name_eye: bpy.context.scene.mesh_name_eye = mesh if not bpy.context.scene.mesh_name_viseme: bpy.context.scene.mesh_name_viseme = mesh if not bpy.context.scene.merge_mesh: bpy.context.scene.merge_mesh = mesh def save_shapekey_order(mesh_name): mesh = get_objects()[mesh_name] armature = get_armature() if not armature: return # Get current custom data custom_data = armature.get('CUSTOM') if not custom_data: # print('NEW DATA!') custom_data = {} # Create shapekey order shape_key_order = [] if has_shapekeys(mesh): for index, shapekey in enumerate(mesh.data.shape_keys.key_blocks): shape_key_order.append(shapekey.name) # Check if there is already a shapekey order if custom_data.get('shape_key_order'): # print('SHAPEKEY ORDER ALREADY EXISTS!') # print(custom_data['shape_key_order']) old_len = len(custom_data.get('shape_key_order')) if type(shape_key_order) is str: old_len = len(shape_key_order.split(',,,')) if len(shape_key_order) <= old_len: # print('ABORT') return # Save order to custom data # print('SAVE NEW ORDER') custom_data['shape_key_order'] = shape_key_order # Save custom data in armature armature['CUSTOM'] = custom_data # print(armature.get('CUSTOM').get('shape_key_order')) def repair_shapekey_order(mesh_name): # Get current custom data armature = get_armature() custom_data = armature.get('CUSTOM') if not custom_data: custom_data = {} # Extract shape keys from string shape_key_order = custom_data.get('shape_key_order') if not shape_key_order: custom_data['shape_key_order'] = [] armature['CUSTOM'] = custom_data if type(shape_key_order) is str: shape_key_order_temp = [] for shape_name in shape_key_order.split(',,,'): shape_key_order_temp.append(shape_name) custom_data['shape_key_order'] = shape_key_order_temp armature['CUSTOM'] = custom_data sort_shape_keys(mesh_name, custom_data['shape_key_order']) def update_shapekey_orders(): for armature in get_armature_objects(): shape_key_order_translated = [] # Get current custom data custom_data = armature.get('CUSTOM') if not custom_data: continue order = custom_data.get('shape_key_order') if not order: continue if type(order) is str: shape_key_order_temp = order.split(',,,') order = [] for shape_name in shape_key_order_temp: order.append(shape_name) # Get shape keys and translate them for shape_name in order: shape_key_order_translated.append(Translate.translate(shape_name, add_space=True, translating_shapes=True)[0]) # print(armature.name, shape_key_order_translated) custom_data['shape_key_order'] = shape_key_order_translated armature['CUSTOM'] = custom_data def sort_shape_keys(mesh_name, shape_key_order=None): mesh = get_objects()[mesh_name] if not has_shapekeys(mesh): return set_active(mesh) if not shape_key_order: shape_key_order = [] order = [ 'Basis', 'vrc.blink_left', 'vrc.blink_right', 'vrc.lowerlid_left', 'vrc.lowerlid_right', 'vrc.v_aa', 'vrc.v_ch', 'vrc.v_dd', 'vrc.v_e', 'vrc.v_ff', 'vrc.v_ih', 'vrc.v_kk', 'vrc.v_nn', 'vrc.v_oh', 'vrc.v_ou', 'vrc.v_pp', 'vrc.v_rr', 'vrc.v_sil', 'vrc.v_ss', 'vrc.v_th', 'Basis Original' ] for shape in shape_key_order: if shape not in order: order.append(shape) wm = bpy.context.window_manager current_step = 0 wm.progress_begin(current_step, len(order)) i = 0 for name in order: if name == 'Basis' and 'Basis' not in mesh.data.shape_keys.key_blocks: i += 1 current_step += 1 wm.progress_update(current_step) continue for index, shapekey in enumerate(mesh.data.shape_keys.key_blocks): if shapekey.name == name: mesh.active_shape_key_index = index new_index = i index_diff = (index - new_index) if new_index >= len(mesh.data.shape_keys.key_blocks): bpy.ops.object.shape_key_move(type='BOTTOM') break position_correct = False if 0 <= index_diff <= (new_index - 1): while position_correct is False: if mesh.active_shape_key_index != new_index: bpy.ops.object.shape_key_move(type='UP') else: position_correct = True else: if mesh.active_shape_key_index > new_index: bpy.ops.object.shape_key_move(type='TOP') position_correct = False while position_correct is False: if mesh.active_shape_key_index != new_index: bpy.ops.object.shape_key_move(type='DOWN') else: position_correct = True i += 1 break current_step += 1 wm.progress_update(current_step) mesh.active_shape_key_index = 0 wm.progress_end() def isEmptyGroup(group_name): mesh = get_objects().get('Body') if mesh is None: return True vgroup = mesh.vertex_groups.get(group_name) if vgroup is None: return True for vert in mesh.data.vertices: for group in vert.groups: if group.group == vgroup.index: if group.weight > 0: return False return True def removeEmptyGroups(obj, thres=0): z = [] for v in obj.data.vertices: for g in v.groups: if g.weight > thres: if g not in z: z.append(obj.vertex_groups[g.group]) for r in obj.vertex_groups: if r not in z: obj.vertex_groups.remove(r) def removeZeroVerts(obj, thres=0): for v in obj.data.vertices: z = [] for g in v.groups: if not g.weight > thres: z.append(g) for r in z: obj.vertex_groups[g.group].remove([v.index]) def delete_hierarchy(parent): unselect_all() to_delete = [] def get_child_names(obj): for child in obj.children: to_delete.append(child) if child.children: get_child_names(child) get_child_names(parent) to_delete.append(parent) objs = bpy.data.objects for obj in to_delete: objs.remove(objs[obj.name], do_unlink=True) def delete(obj): if obj.parent: for child in obj.children: child.parent = obj.parent objs = bpy.data.objects objs.remove(objs[obj.name], do_unlink=True) def days_between(d1, d2, time_format): d1 = datetime.strptime(d1, time_format) d2 = datetime.strptime(d2, time_format) return abs((d2 - d1).days) def delete_bone_constraints(armature_name=None): if not armature_name: armature_name = bpy.context.scene.armature armature = get_armature(armature_name=armature_name) switch('POSE') for bone in armature.pose.bones: if len(bone.constraints) > 0: for constraint in bone.constraints: bone.constraints.remove(constraint) switch('EDIT') def delete_zero_weight(armature_name=None, ignore=''): if not armature_name: armature_name = bpy.context.scene.armature armature = get_armature(armature_name=armature_name) switch('EDIT') bone_names_to_work_on = set([bone.name for bone in armature.data.edit_bones]) bone_name_to_edit_bone = dict() for edit_bone in armature.data.edit_bones: bone_name_to_edit_bone[edit_bone.name] = edit_bone vertex_group_names_used = set() vertex_group_name_to_objects_having_same_named_vertex_group = dict() for objects in get_meshes_objects(armature_name=armature_name): vertex_group_id_to_vertex_group_name = dict() for vertex_group in objects.vertex_groups: vertex_group_id_to_vertex_group_name[vertex_group.index] = vertex_group.name if vertex_group.name not in vertex_group_name_to_objects_having_same_named_vertex_group: vertex_group_name_to_objects_having_same_named_vertex_group[vertex_group.name] = set() vertex_group_name_to_objects_having_same_named_vertex_group[vertex_group.name].add(objects) for vertex in objects.data.vertices: for group in vertex.groups: if group.weight > 0: vertex_group_names_used.add(vertex_group_id_to_vertex_group_name.get(group.group)) not_used_bone_names = bone_names_to_work_on - vertex_group_names_used count = 0 for bone_name in not_used_bone_names: if not bpy.context.scene.keep_end_bones or not is_end_bone(bone_name, armature_name): if bone_name not in Bones.dont_delete_these_bones and 'Root_' not in bone_name and bone_name != ignore: armature.data.edit_bones.remove(bone_name_to_edit_bone[bone_name]) # delete bone count += 1 if bone_name in vertex_group_name_to_objects_having_same_named_vertex_group: for objects in vertex_group_name_to_objects_having_same_named_vertex_group[bone_name]: # delete vertex groups vertex_group = objects.vertex_groups.get(bone_name) if vertex_group is not None: objects.vertex_groups.remove(vertex_group) return count def remove_unused_objects(): default_scene_objects = [] for obj in get_objects(): if (obj.type == 'CAMERA' and obj.name == 'Camera') \ or (obj.type == 'LAMP' and obj.name == 'Lamp') \ or (obj.type == 'LIGHT' and obj.name == 'Light') \ or (obj.type == 'MESH' and obj.name == 'Cube'): default_scene_objects.append(obj) if len(default_scene_objects) == 3: for obj in default_scene_objects: delete_hierarchy(obj) def remove_no_user_objects(): # print('\nREMOVE OBJECTS') for block in get_objects(): # print(block.name, block.users) if block.users == 0: print('Removing obj ', block.name) delete(block) # print('\nREMOVE MESHES') for block in bpy.data.meshes: # print(block.name, block.users) if block.users == 0: print('Removing mesh ', block.name) bpy.data.meshes.remove(block) # print('\nREMOVE MATERIALS') for block in bpy.data.materials: # print(block.name, block.users) if block.users == 0: print('Removing material ', block.name) bpy.data.materials.remove(block) # print('\nREMOVE MATS') # for block in bpy.data.materials: # print(block.name, block.users) # if block.users == 0: # bpy.data.materials.remove(block) def is_end_bone(name, armature_name): armature = get_armature(armature_name=armature_name) end_bone = armature.data.edit_bones.get(name) if end_bone and end_bone.parent and len(end_bone.parent.children) == 1: return True return False def correct_bone_positions(armature_name=None): if not armature_name: armature_name = bpy.context.scene.armature armature = get_armature(armature_name=armature_name) upper_chest = armature.data.edit_bones.get('Upper Chest') chest = armature.data.edit_bones.get('Chest') neck = armature.data.edit_bones.get('Neck') head = armature.data.edit_bones.get('Head') if chest and neck: if upper_chest and bpy.context.scene.keep_upper_chest: chest.tail = upper_chest.head upper_chest.tail = neck.head else: chest.tail = neck.head if neck and head: neck.tail = head.head if 'Left shoulder' in armature.data.edit_bones: if 'Left arm' in armature.data.edit_bones: if 'Left elbow' in armature.data.edit_bones: if 'Left wrist' in armature.data.edit_bones: shoulder = armature.data.edit_bones.get('Left shoulder') arm = armature.data.edit_bones.get('Left arm') elbow = armature.data.edit_bones.get('Left elbow') wrist = armature.data.edit_bones.get('Left wrist') shoulder.tail = arm.head arm.tail = elbow.head elbow.tail = wrist.head if 'Right shoulder' in armature.data.edit_bones: if 'Right arm' in armature.data.edit_bones: if 'Right elbow' in armature.data.edit_bones: if 'Right wrist' in armature.data.edit_bones: shoulder = armature.data.edit_bones.get('Right shoulder') arm = armature.data.edit_bones.get('Right arm') elbow = armature.data.edit_bones.get('Right elbow') wrist = armature.data.edit_bones.get('Right wrist') shoulder.tail = arm.head arm.tail = elbow.head elbow.tail = wrist.head if 'Left leg' in armature.data.edit_bones: if 'Left knee' in armature.data.edit_bones: if 'Left ankle' in armature.data.edit_bones: leg = armature.data.edit_bones.get('Left leg') knee = armature.data.edit_bones.get('Left knee') ankle = armature.data.edit_bones.get('Left ankle') if 'Left leg 2' in armature.data.edit_bones: leg = armature.data.edit_bones.get('Left leg 2') leg.tail = knee.head knee.tail = ankle.head if 'Right leg' in armature.data.edit_bones: if 'Right knee' in armature.data.edit_bones: if 'Right ankle' in armature.data.edit_bones: leg = armature.data.edit_bones.get('Right leg') knee = armature.data.edit_bones.get('Right knee') ankle = armature.data.edit_bones.get('Right ankle') if 'Right leg 2' in armature.data.edit_bones: leg = armature.data.edit_bones.get('Right leg 2') leg.tail = knee.head knee.tail = ankle.head dpi_scale = 3 error = [] override = False def show_error(scale, error_list, override_header=False): global override, dpi_scale, error override = override_header dpi_scale = scale if type(error_list) is str: error_list = error_list.split('\n') error = error_list header = 'error' if override: header = error_list[0] print('') print('Report: Error') for line in error: print(' ' + line) def remove_doubles(mesh, threshold, save_shapes=True): if not mesh: return 0 # If the mesh has no shapekeys, don't remove doubles if not has_shapekeys(mesh) or len(mesh.data.shape_keys.key_blocks) == 1: return 0 pre_tris = len(mesh.data.polygons) set_active(mesh) switch('EDIT') bpy.ops.mesh.select_mode(type="VERT") bpy.ops.mesh.select_all(action='DESELECT') if save_shapes and has_shapekeys(mesh): switch('OBJECT') for kb in mesh.data.shape_keys.key_blocks: i = 0 for v0, v1 in zip(kb.relative_key.data, kb.data): if v0.co != v1.co: mesh.data.vertices[i].select = True i += 1 switch('EDIT') bpy.ops.mesh.select_all(action='INVERT') else: bpy.ops.mesh.select_all(action='SELECT') bpy.ops.mesh.remove_doubles(threshold=threshold) bpy.ops.mesh.select_all(action='DESELECT') switch('OBJECT') return pre_tris - len(mesh.data.polygons) def get_tricount(obj): # Triangulates with Bmesh to avoid messing with the original geometry bmesh_mesh = bmesh.new() bmesh_mesh.from_mesh(obj.data) bmesh.ops.triangulate(bmesh_mesh, faces=bmesh_mesh.faces[:]) return len(bmesh_mesh.faces) def get_bone_orientations(armature): x_cord = 0 y_cord = 1 z_cord = 2 fbx = False # armature = get_armature() # # for index, bone in enumerate(armature.pose.bones): # if 'Head' in bone.name: # #if index == 5: # bone_pos = bone.matrix # print(bone_pos) # world_pos = armature.matrix_world * bone.matrix # print(world_pos) # print(bone_pos[0][0], world_pos[0][0]) # if round(abs(bone_pos[0][0]), 4) != round(abs(world_pos[0][0]), 4): # z_cord = 1 # y_cord = 2 # fbx = True # break return x_cord, y_cord, z_cord, fbx def clean_material_names(mesh): for j, mat in enumerate(mesh.material_slots): if mat.name.endswith('.001'): mesh.active_material_index = j mesh.active_material.name = mat.name[:-4] if mat.name.endswith(('. 001', ' .001')): mesh.active_material_index = j mesh.active_material.name = mat.name[:-5] def mix_weights(mesh, vg_from, vg_to, mix_strength=1.0, mix_mode='ADD', delete_old_vg=True): mesh.active_shape_key_index = 0 mod = mesh.modifiers.new("VertexWeightMix", 'VERTEX_WEIGHT_MIX') mod.vertex_group_a = vg_to mod.vertex_group_b = vg_from mod.mix_mode = mix_mode mod.mix_set = 'B' mod.mask_constant = mix_strength apply_modifier(mod) if delete_old_vg: mesh.vertex_groups.remove(mesh.vertex_groups.get(vg_from)) mesh.active_shape_key_index = 0 # This line fixes a visual bug in 2.90 which causes random weights to be stuck after being merged def get_user_preferences(): return bpy.context.user_preferences if hasattr(bpy.context, 'user_preferences') else bpy.context.preferences def has_shapekeys(mesh): if not hasattr(mesh.data, 'shape_keys'): return False return hasattr(mesh.data.shape_keys, 'key_blocks') def matmul(a, b): if version_2_79_or_older(): return a * b return a @ b def ui_refresh(): # A way to refresh the ui refreshed = False while not refreshed: if hasattr(bpy.data, 'window_managers'): for windowManager in bpy.data.window_managers: for window in windowManager.windows: for area in window.screen.areas: area.tag_redraw() refreshed = True # print('Refreshed UI') else: time.sleep(0.5) def fix_zero_length_bones(armature, x_cord, y_cord, z_cord): pre_mode = armature.mode set_active(armature) switch('EDIT') for bone in armature.data.edit_bones: if round(bone.head[x_cord], 4) == round(bone.tail[x_cord], 4) \ and round(bone.head[y_cord], 4) == round(bone.tail[y_cord], 4) \ and round(bone.head[z_cord], 4) == round(bone.tail[z_cord], 4): bone.tail[z_cord] += 0.1 switch(pre_mode) def fix_bone_orientations(armature): # Connect all bones with their children if they have exactly one for bone in armature.data.edit_bones: if len(bone.children) == 1 and bone.name not in ['LeftEye', 'RightEye', 'Head', 'Hips']: p1 = bone.head p2 = bone.children[0].head dist = ((p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2 + (p2[2] - p1[2]) ** 2) ** (1/2) # Only connect them if the other bone is a certain distance away, otherwise blender will delete them if dist > 0.005: bone.tail = bone.children[0].head if bone.parent: if len(bone.parent.children) == 1: # if the bone's parent bone only has one child, connect the bones (Don't connect them all because that would mess up hand/finger bones) bone.use_connect = True def update_material_list(self=None, context=None): try: if hasattr(bpy.context.scene, 'kkbp_ob_data') and bpy.context.scene.kkbp_ob_data: bpy.ops.kkbp.refresh_ob_data() except AttributeError: print('Material Combiner not found') def unify_materials(): textures = [] for ob in get_objects(): if ob.type == "MESH": for mat_slot in ob.material_slots: if mat_slot.material: mat_slot.material.blend_method = 'HASHED' # mat_slot.material.blend_method = 'BLEND' # Use this for transparent textures only print('MAT: ', mat_slot.material.name) if mat_slot.material.node_tree: nodes = mat_slot.material.node_tree.nodes image = None for node in nodes: # print(' ' + node.name + ', ' + node.type + ', ' + node.label) if node.type == 'TEX_IMAGE' and 'toon' not in node.name and 'sphere' not in node.name: image = node.image # textures.append(node.image.name) mat_slot.material.node_tree.nodes.remove(node) # Create Image node node_texture = nodes.new(type='ShaderNodeTexImage') node_texture.location = 0, 0 node_texture.image = image node_texture.label = 'Cats Texture' # Create Principled BSDF node node_prinipled = nodes.new(type='ShaderNodeBsdfPrincipled') node_prinipled.location = 300, -220 node_prinipled.label = 'Cats Emission' node_prinipled.inputs['Specular'].default_value = 0 node_prinipled.inputs['Roughness'].default_value = 0 node_prinipled.inputs['Sheen Tint'].default_value = 0 node_prinipled.inputs['Clearcoat Roughness'].default_value = 0 node_prinipled.inputs['IOR'].default_value = 0 # Create Transparency BSDF node node_transparent = nodes.new(type='ShaderNodeBsdfTransparent') node_transparent.location = 325, -100 node_transparent.label = 'Cats Transparency' # Create Mix Shader node node_mix = nodes.new(type='ShaderNodeMixShader') node_mix.location = 600, 0 node_mix.label = 'Cats Mix' # Create Output node node_output = nodes.new(type='ShaderNodeOutputMaterial') node_output.location = 800, 0 node_output.label = 'Cats Output' # Create 2nd Output node node_output2 = nodes.new(type='ShaderNodeOutputMaterial') node_output2.location = 800, -200 node_output2.label = 'Cats Export' # Link nodes together mat_slot.material.node_tree.links.new(node_texture.outputs['Color'], node_prinipled.inputs['Base Color']) mat_slot.material.node_tree.links.new(node_texture.outputs['Alpha'], node_mix.inputs['Fac']) mat_slot.material.node_tree.links.new(node_prinipled.outputs['BSDF'], node_mix.inputs[2]) mat_slot.material.node_tree.links.new(node_transparent.outputs['BSDF'], node_mix.inputs[1]) mat_slot.material.node_tree.links.new(node_mix.outputs['Shader'], node_output.inputs['Surface']) mat_slot.material.node_tree.links.new(node_prinipled.outputs['BSDF'], node_output2.inputs['Surface']) # break print(textures, len(textures)) return {'FINISHED'} def add_principled_shader(mesh): # This adds a principled shader and material output node in order for # Unity to automatically detect exported materials principled_shader_pos = (501, -500) output_shader_pos = (801, -500) mmd_texture_bake_pos = (1101, -500) principled_shader_label = 'Cats Export Shader' output_shader_label = 'Cats Export' for mat_slot in mesh.material_slots: if mat_slot.material and mat_slot.material.node_tree: nodes = mat_slot.material.node_tree.nodes node_image = None node_image_count = 0 node_mmd_shader = None needsmmdcolor = False # Check if the new nodes should be added and to which image node they should be attached to for node in nodes: # Cancel if the cats nodes are already found if node.type == 'BSDF_PRINCIPLED' and node.label == principled_shader_label: node_image = None break elif node.type == 'OUTPUT_MATERIAL' and node.label == output_shader_label: node_image = None break elif node.type == 'OUTPUT_MATERIAL': #So that blender doesn't get confused on which to output nodes.remove(node) continue if node.name == "mmd_shader": node_mmd_shader = node needsmmdcolor = True continue # Skip if this node is not an image node if node.type != 'TEX_IMAGE': continue node_image_count += 1 # If an mmd_texture is found, link it to the principled shader later if node.name == 'mmd_base_tex' or node.label == 'MainTexture': node_image = node node_image_count = 0 break # This is an image node, so link it to the principled shader later node_image = node #this material doesn't have a texture and doesn't have a MMD AO+Diffuse so skip if (not node_image or node_image_count > 1) and not needsmmdcolor: continue elif needsmmdcolor and node_mmd_shader: #this needs to implement mmd color and has a shader node #bake AO and Diffuse color into pixels for MMD texture. if texture exists, multiply over #Thank this guy for pixel manipulation: https://blender.stackexchange.com/a/652 basecolor = [x*0.6 for x in node_mmd_shader.inputs[1].default_value[:]] #multply color of diffuse by .6 which is MMD's addition factor for rgba,num in enumerate(basecolor): basecolor[rgba] = max(0,min(1,basecolor[rgba]+node_mmd_shader.inputs[0].default_value[rgba])) #add AO to diffuse and clamp between 0-1 for each channel if not node_image: node_image = mat_slot.material.node_tree.nodes.new(type="ShaderNodeTexImage") node_image.location = mmd_texture_bake_pos node_image.label = "Mmd Base Tex" node_image.name = "mmd_base_tex" node_image.image = bpy.data.images.new("MMDCatsBaked", width=8, height=8, alpha=True) #make pixels using AO color #assign to image so it's baked node_image.image.generated_color = basecolor node_image.image.filepath = bpy.path.abspath("//"+node_image.image.name+".png") node_image.image.file_format = 'PNG' if bpy.data.is_saved: node_image.image.save() elif node_image: #multiply color on top of default color. pixels = np.array(node_image.image.pixels[:]) multiply_image = np.tile(np.array(basecolor),int(len(pixels)/4)) new_pixels = pixels*multiply_image #create new image as to not touch old one node_image.image = bpy.data.images.new(node_image.image.name+"MMDCatsBaked", width=node_image.image.size[0], height=node_image.image.size[1], alpha=True) node_image.image.filepath = bpy.path.abspath("//"+node_image.image.name+".png") node_image.image.file_format = 'PNG' node_image.image.pixels = new_pixels if bpy.data.is_saved: node_image.image.save() # Create Principled BSDF node node_prinipled = nodes.new(type='ShaderNodeBsdfPrincipled') node_prinipled.label = 'Cats Export Shader' node_prinipled.location = principled_shader_pos node_prinipled.inputs['Specular'].default_value = 0 node_prinipled.inputs['Roughness'].default_value = 0 node_prinipled.inputs['Sheen Tint'].default_value = 0 node_prinipled.inputs['Clearcoat Roughness'].default_value = 0 node_prinipled.inputs['IOR'].default_value = 0 # Create Output node for correct image exports node_output = nodes.new(type='ShaderNodeOutputMaterial') node_output.label = 'Cats Export' node_output.location = output_shader_pos # Link nodes together mat_slot.material.node_tree.links.new(node_image.outputs['Color'], node_prinipled.inputs['Base Color']) mat_slot.material.node_tree.links.new(node_prinipled.outputs['BSDF'], node_output.inputs['Surface']) def remove_toon_shader(mesh): for mat_slot in mesh.material_slots: if mat_slot.material and mat_slot.material.node_tree: nodes = mat_slot.material.node_tree.nodes for node in nodes: if node.name == 'mmd_toon_tex': print('Toon tex removed from material', mat_slot.material.name) nodes.remove(node) # if not node.image or not node.image.filepath: # print('Toon tex removed: Empty, from material', mat_slot.material.name) # nodes.remove(node) # continue # # image_filepath = bpy.path.abspath(node.image.filepath) # if not os.path.isfile(image_filepath): # print('Toon tex removed:', node.image.name, 'from material', mat_slot.material.name) # nodes.remove(node) def fix_mmd_shader(mesh): for mat_slot in mesh.material_slots: if mat_slot.material and mat_slot.material.node_tree: nodes = mat_slot.material.node_tree.nodes for node in nodes: if node.name == 'mmd_shader': node.inputs['Reflect'].default_value = 1 def fix_vrm_shader(mesh): for mat_slot in mesh.material_slots: if mat_slot.material and mat_slot.material.node_tree: is_vrm_mat = False nodes = mat_slot.material.node_tree.nodes for node in nodes: if hasattr(node, 'node_tree') and 'MToon_unversioned' in node.node_tree.name: node.location[0] = 200 node.inputs['ReceiveShadow_Texture_alpha'].default_value = -10000 node.inputs['ShadeTexture'].default_value = (1.0, 1.0, 1.0, 1.0) node.inputs['Emission_Texture'].default_value = (0.0, 0.0, 0.0, 0.0) node.inputs['SphereAddTexture'].default_value = (0.0, 0.0, 0.0, 0.0) # Support typo in old vrm importer node_input = node.inputs.get('NomalmapTexture') if not node_input: node_input = node.inputs.get('NormalmapTexture') node_input.default_value = (1.0, 1.0, 1.0, 1.0) is_vrm_mat = True break if not is_vrm_mat: continue nodes_to_keep = ['DiffuseColor', 'MainTexture', 'Emission_Texture'] if 'HAIR' in mat_slot.material.name: nodes_to_keep = ['DiffuseColor', 'MainTexture', 'Emission_Texture', 'SphereAddTexture'] for node in nodes: # Delete all unneccessary nodes if 'RGB' in node.name \ or 'Value' in node.name \ or 'Image Texture' in node.name \ or 'UV Map' in node.name \ or 'Mapping' in node.name: if node.label not in nodes_to_keep: for output in node.outputs: for link in output.links: mat_slot.material.node_tree.links.remove(link) continue # if hasattr(node, 'node_tree') and 'matcap_vector' in node.node_tree.name: # for output in node.outputs: # for link in output.links: # mat_slot.material.node_tree.links.remove(link) # continue def fix_twist_bones(mesh, bones_to_delete): # This will fix MMD twist bones for bone_type in ['Hand', 'Arm']: for suffix in ['L', 'R']: prefix = 'Left' if suffix == 'L' else 'Right' bone_parent_name = prefix + ' ' + ('elbow' if bone_type == 'Hand' else 'arm') vg_twist = mesh.vertex_groups.get(bone_type + 'Twist_' + suffix) vg_parent = mesh.vertex_groups.get(bone_parent_name) if not vg_twist: print('1. no ' + bone_type + 'Twist_' + suffix) continue if not vg_parent: print('2. no ' + bone_parent_name) vg_parent = mesh.vertex_groups.new(name=bone_parent_name) vg_twist1 = mesh.vertex_groups.get(bone_type + 'Twist1_' + suffix) vg_twist2 = mesh.vertex_groups.get(bone_type + 'Twist2_' + suffix) vg_twist3 = mesh.vertex_groups.get(bone_type + 'Twist3_' + suffix) mix_weights(mesh, vg_twist.name, vg_parent.name, mix_strength=0.2, delete_old_vg=False) mix_weights(mesh, vg_twist.name, vg_twist.name, mix_strength=0.2, mix_mode='SUB', delete_old_vg=False) if vg_twist1: twistname = bone_type + 'Twist1_' + suffix bones_to_delete.append(twistname) mix_weights(mesh, twistname, vg_twist.name, mix_strength=0.25, delete_old_vg=False) mix_weights(mesh, twistname, vg_parent.name, mix_strength=0.75) #if we are adding to bones to delete, then don't delete prematurely please (added don't delete argument) - @989onan if vg_twist2: twistname = bone_type + 'Twist2_' + suffix bones_to_delete.append(twistname) mix_weights(mesh, twistname, vg_twist.name, mix_strength=0.5, delete_old_vg=False) mix_weights(mesh, twistname, vg_parent.name, mix_strength=0.5, delete_old_vg=False) #if we are adding to bones to delete, then don't delete prematurely please (added don't delete argument) - @989onan if vg_twist3: twistname = bone_type + 'Twist3_' + suffix bones_to_delete.append(twistname) mix_weights(mesh, twistname, vg_twist.name, mix_strength=0.75, delete_old_vg=False) mix_weights(mesh, twistname, vg_parent.name, mix_strength=0.25, delete_old_vg=False) #if we are adding to bones to delete, then don't delete prematurely please. (added don't delete argument) - @989onan def fix_twist_bone_names(armature): # This will fix MMD twist bone names after the vertex groups have been fixed for bone_type in ['Hand', 'Arm']: for suffix in ['L', 'R']: bone_twist = armature.data.edit_bones.get(bone_type + 'Twist_' + suffix) if bone_twist: bone_twist.name = 'z' + bone_twist.name def toggle_mmd_tabs_update(self, context): toggle_mmd_tabs() def toggle_mmd_tabs(shutdown_plugin=False): mmd_cls = [ mmd_tool.MMDToolsObjectPanel, mmd_tool.MMDDisplayItemsPanel, mmd_tool.MMDMorphToolsPanel, mmd_tool.MMDRigidbodySelectorPanel, mmd_tool.MMDJointSelectorPanel, mmd_util_tools.MMDMaterialSorter, mmd_util_tools.MMDMeshSorter, mmd_util_tools.MMDBoneOrder, ] mmd_cls_shading = [ mmd_view_prop.MMDViewPanel, mmd_view_prop.MMDSDEFPanel, ] if not version_2_79_or_older(): mmd_cls = mmd_cls + mmd_cls_shading # If the plugin is shutting down, load the mmd_tools tabs before that, to avoid issues when unregistering mmd_tools if bpy.context.scene.show_mmd_tabs or shutdown_plugin: for cls in mmd_cls: try: bpy.utils.register_class(cls) except: pass else: for cls in reversed(mmd_cls): try: bpy.utils.unregister_class(cls) except: pass if not shutdown_plugin: Settings.update_settings(None, None) """ HTML <-> text conversions. http://stackoverflow.com/questions/328356/extracting-text-from-html-file-using-python """ class _HTMLToText(HTMLParser): def __init__(self): HTMLParser.__init__(self) self._buf = [] self.hide_output = False def handle_starttag(self, tag, attrs): if tag in ('p', 'br') and not self.hide_output: self._buf.append('\n') elif tag in ('script', 'style'): self.hide_output = True def handle_startendtag(self, tag, attrs): if tag == 'br': self._buf.append('\n') def handle_endtag(self, tag): if tag == 'p': self._buf.append('\n') elif tag in ('script', 'style'): self.hide_output = False def handle_data(self, text): if text and not self.hide_output: self._buf.append(re.sub(r'\s+', ' ', text)) def handle_entityref(self, name): if name in name2codepoint and not self.hide_output: c = chr(name2codepoint[name]) self._buf.append(c) def handle_charref(self, name): if not self.hide_output: n = int(name[1:], 16) if name.startswith('x') else int(name) self._buf.append(chr(n)) def get_text(self): return re.sub(r' +', ' ', ''.join(self._buf)) def html_to_text(html): """ Given a piece of HTML, return the plain text it contains. This handles entities and char refs, but not javascript and stylesheets. """ parser = _HTMLToText() try: parser.feed(html) parser.close() except: # HTMLParseError: No good replacement? pass return parser.get_text() """ === THIS CODE COULD BE USEFUL === """ # def addvertex(meshname, shapekey_name): # mesh = get_objects()[meshname].data # bm = bmesh.new() # bm.from_mesh(mesh) # bm.verts.ensure_lookup_table() # # print(" ") # if shapekey_name in bm.verts.layers.shape.keys(): # val = bm.verts.layers.shape.get(shapekey_name) # print("%s = %s" % (shapekey_name, val)) # sk = mesh.shape_keys.key_blocks[shapekey_name] # print("v=%f, f=%f" % (sk.value, sk.frame)) # for i in range(len(bm.verts)): # v = bm.verts[i] # delta = v[val] - v.co # if (delta.length > 0): # print("v[%d]+%s" % (i, delta)) # # print(" ") # === THIS CODE COULD BE USEFUL === # Check which shape keys will be deleted on export by Blender # def checkshapekeys(): # for ob in get_objects(): # if ob.type == 'MESH': # mesh = ob # bm = bmesh.new() # bm.from_mesh(mesh.data) # bm.verts.ensure_lookup_table() # # deleted_shapes = [] # for key in bm.verts.layers.shape.keys(): # if key == 'Basis': # continue # val = bm.verts.layers.shape.get(key) # delete = True # for vert in bm.verts: # delta = vert[val] - vert.co # if delta.length > 0: # delete = False # break # if delete: # deleted_shapes.append(key) # # return deleted_shapes # # Repair vrc shape keys old # def repair_shapekeys(): # for ob in get_objects(): # if ob.type == 'MESH': # mesh = ob # bm = bmesh.new() # bm.from_mesh(mesh.data) # bm.verts.ensure_lookup_table() # # for key in bm.verts.layers.shape.keys(): # if not key.startswith('vrc'): # continue # # value = bm.verts.layers.shape.get(key) # for vert in bm.verts: # shapekey = vert # shapekey_coords = mesh.matrix_world * shapekey[value] # shapekey_coords[2] -= 0.00001 # shapekey[value] = mesh.matrix_world.inverted() * shapekey_coords # break # # bm.to_mesh(mesh.data) # === THIS CODE COULD BE USEFUL ===