Files
KKBP_Importer/extras/catsscripts/common.py
小煜 5d9e09e9c3 feat: Add KoikatsuBlenderPipeline addon with import/export functionality
- Add core addon files (__init__.py, KKPanel.py, preferences.py, common.py)
- Add import pipeline with armature, mesh, and material modification modules
- Add export pipeline with material baking and FBX preparation utilities
- Add material combiner tool for texture atlas generation and optimization
- Add extras utilities for animation, rigging, and asset management
- Add bone orientation data from better_fbx for accurate skeletal structure
- Add comprehensive documentation and wiki with multi-language support (EN, JP, ZH)
- Add animation library retargeting lists for ARP and Rokoko motion capture
- Add Rigify integration scripts for advanced rigging workflows
- Add shader file (KK Shader V8.0.blend) for material rendering
- Add manifest and license files for addon distribution
- Add changelog documenting version history and improvements
- Initialize complete Blender addon project for Koikatsu character import/export
2025-12-06 15:26:19 +08:00

2291 lines
76 KiB
Python

# 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 ===