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
This commit is contained in:
2025-12-06 15:26:19 +08:00
commit 5d9e09e9c3
122 changed files with 22683 additions and 0 deletions

View File

@@ -0,0 +1,131 @@
from collections import OrderedDict
from collections import defaultdict
from typing import List
from typing import Union
from typing import ValuesView
from typing import cast
import bpy
import numpy as np
from .images import get_image
from .images import get_packed_file
from .textures import get_texture
from . import globs
from .type_annotations import Diffuse
from .type_annotations import MatDict
from .type_annotations import MatDictItem
shader_types = OrderedDict([
('mmd', {'mmd_shader', 'mmd_base_tex'}),
('mmdCol', {'mmd_shader'}),
('mtoon', {'Mtoon1BaseColorTexture.Image'}),
('mtoonCol', {'Mtoon1Material.Mtoon1Output'}),
('principled', {'Principled BSDF', 'Image Texture'}),
('principledCol', {'Principled BSDF'}),
('diffuse', {'Diffuse BSDF', 'Image Texture'}),
('diffuseCol', {'Diffuse BSDF'}),
('emission', {'Emission', 'Image Texture'}),
('emissionCol', {'Emission'}),
])
shader_image_nodes = {
'mmd': 'mmd_base_tex',
'mtoon': 'Mtoon1BaseColorTexture.Image',
'vrm': 'Image Texture',
'xnalara': 'Image Texture',
'principled': 'Image Texture',
'diffuse': 'Image Texture',
'emission': 'Image Texture',
}
def get_materials(ob: bpy.types.Object) -> List[bpy.types.Material]:
return [mat_slot.material for mat_slot in ob.material_slots if mat_slot.material]
def get_shader_type(mat: bpy.types.Material) -> Union[str, None]:
if not mat.node_tree or not mat.node_tree.nodes:
return
node_tree = mat.node_tree.nodes
if 'Group' in node_tree:
node_tree_name = node_tree['Group'].node_tree.name
if node_tree_name == 'Group':
return 'xnalaraNewCol'
if node_tree_name == 'MToon_unversioned':
return 'vrm' if 'Image Texture' in node_tree else 'vrmCol'
elif node_tree_name == 'XPS Shader' and 'Image Texture' in node_tree:
return 'xnalara'
node_names_set = set(node_tree.keys())
return next(
(
shader_type
for shader_type, node_names in shader_types.items()
if node_names.issubset(node_names_set)
),
None,
)
def sort_materials(mat_list: List[bpy.types.Material]) -> ValuesView[MatDictItem]:
for mat in bpy.data.materials:
mat.kkbp_root_mat = None
mat_dict = cast(MatDict, defaultdict(list))
for mat in mat_list:
node_tree = mat.node_tree if mat else None
packed_file = None
if globs.is_blender_2_79_or_older:
packed_file = get_packed_file(get_image(get_texture(mat)))
elif node_tree:
shader = get_shader_type(mat)
node_name = shader_image_nodes.get(shader)
if node_name:
packed_file = get_packed_file(node_tree.nodes[node_name].image)
if packed_file:
mat_dict[(packed_file, get_diffuse(mat) if mat.kkbp_diffuse else None)].append(mat)
else:
mat_dict[get_diffuse(mat)].append(mat)
return mat_dict.values()
def rgb_to_255_scale(diffuse: Diffuse) -> Diffuse:
rgb = np.empty(shape=(0,), dtype=int)
for c in diffuse:
if c < 0.0:
srgb = 0
elif c < 0.0031308:
srgb = c * 12.92
else:
srgb = 1.055 * pow(c, 1.0 / 2.4) - 0.055
rgb = np.append(rgb, np.clip(round(srgb * 255), 0, 255))
return tuple(rgb)
def get_diffuse(mat: bpy.types.Material) -> Diffuse:
if globs.is_blender_2_79_or_older:
return rgb_to_255_scale(mat.diffuse_color)
shader = get_shader_type(mat) if mat else False
if shader == 'mmdCol':
return rgb_to_255_scale(mat.node_tree.nodes['mmd_shader'].inputs['Diffuse Color'].default_value)
elif shader == 'mtoonCol':
return rgb_to_255_scale(mat.node_tree.nodes['Mtoon1PbrMetallicRoughness.BaseColorFactor'].color)
elif shader == 'vrm':
return rgb_to_255_scale(mat.node_tree.nodes['RGB'].outputs[0].default_value)
elif shader == 'vrmCol':
return rgb_to_255_scale(mat.node_tree.nodes['Group'].inputs[10].default_value)
elif shader == 'diffuseCol':
return rgb_to_255_scale(mat.node_tree.nodes['Diffuse BSDF'].inputs['Color'].default_value)
elif shader == 'xnalaraNewCol':
return rgb_to_255_scale(mat.node_tree.nodes['Group'].inputs['Diffuse'].default_value)
elif shader in ['principledCol', 'xnalaraCol']:
return rgb_to_255_scale(mat.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value)
return 255, 255, 255