Files
KKBP_Importer/KKPanel.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

429 lines
18 KiB
Python

import bpy, os, textwrap
from bpy.types import PropertyGroup
from bpy.props import (
IntProperty,
FloatProperty,
EnumProperty,
BoolProperty,
StringProperty
)
from .interface.dictionary_en import t
from .exporting.material_combiner import globs
class PlaceholderProperties(PropertyGroup):
#this will let the plugin know where to look for texture / json data
import_dir: StringProperty(default='')
#this will let the plugin know where the user is in the import / export process
plugin_state:StringProperty(default='')
#This will let the plugin track what objects belong to what character
character_name: StringProperty(default='')
#this lets the plugin time various actions
total_timer : FloatProperty(default=0)
timer : FloatProperty(default=0)
bake_mult: IntProperty(
min=1, max = 6,
default=1,
description=t('bake_mult_tt'))
sfw_mode : BoolProperty(
description=t('sfw_mode_tt'),
default = bpy.context.preferences.addons[__package__].preferences.sfw_mode)
fix_seams : BoolProperty(
description=t('seams_tt'),
default = bpy.context.preferences.addons[__package__].preferences.fix_seams)
use_single_outline : BoolProperty(
description= t('outline_tt'),
default = bpy.context.preferences.addons[__package__].preferences.use_single_outline)
use_material_fake_user : BoolProperty(
description=t('keep_templates_tt'),
default = bpy.context.preferences.addons[__package__].preferences.use_material_fake_user)
old_bake_bool : BoolProperty(
description=t('old_bake_tt'),
default = bpy.context.preferences.addons[__package__].preferences.old_bake_bool)
armature_dropdown : EnumProperty(
items=(
("A", t('arm_drop_A'), t('arm_drop_A_tt')),
("B", t('arm_drop_B'), t('arm_drop_B_tt')),
("C", t('arm_drop_C'), t('arm_drop_C_tt')),
("D", t('arm_drop_D'), t('arm_drop_D_tt')),
), name="", default=bpy.context.preferences.addons[__package__].preferences.armature_dropdown, description=t('arm_drop'))
categorize_dropdown : EnumProperty(
items=(
("A", t('cat_drop_A'), t('cat_drop_A_tt')),
("B", t('cat_drop_B'), t('cat_drop_B_tt')),
), name="", default=bpy.context.preferences.addons[__package__].preferences.categorize_dropdown, description=t('cat_drop'))
colors_dropdown : BoolProperty(
description = t('dark'),
default=bpy.context.preferences.addons[__package__].preferences.colors_dropdown)
prep_dropdown : EnumProperty(
items=(
("A", t('prep_drop_A'), t('prep_drop_A_tt')),
#("C", "MikuMikuDance - PMX compatible", " "),
("D", t('prep_drop_D'), t('prep_drop_D_tt')),
("E", t('prep_drop_E'), t('prep_drop_E_tt')),
("B", t('prep_drop_B'), t('prep_drop_B_tt')),
), name="", default=bpy.context.preferences.addons[__package__].preferences.prep_dropdown, description=t('prep_drop'))
simp_dropdown : EnumProperty(
items=(
("A", t('simp_drop_A'), t('simp_drop_A_tt')),
("B", t('simp_drop_B'), t('simp_drop_B_tt')),
("C", t('simp_drop_C'), t('simp_drop_C_tt')),
), name="", default=bpy.context.preferences.addons[__package__].preferences.simp_dropdown, description=t('simp_drop'))
bake_light_bool : BoolProperty(
description=t('bake_light_tt'),
default = bpy.context.preferences.addons[__package__].preferences.bake_light_bool)
bake_dark_bool : BoolProperty(
description=t('bake_dark_tt'),
default = bpy.context.preferences.addons[__package__].preferences.bake_dark_bool)
bake_norm_bool : BoolProperty(
description=t('bake_norm_tt'),
default = bpy.context.preferences.addons[__package__].preferences.bake_norm_bool)
delete_cache : BoolProperty(
description=t('delete_cache_tt'),
default = bpy.context.preferences.addons[__package__].preferences.delete_cache)
use_atlas : BoolProperty(
description=t('use_atlas_tt'),
default = bpy.context.preferences.addons[__package__].preferences.use_atlas)
animation_library_scale : BoolProperty(
description=t('animation_library_scale_tt'),
default = True)
shapekeys_dropdown : EnumProperty(
items=(
("A", t('shape_A'), t('shape_A_tt')),
("B", t('shape_B'), t('shape_B_tt')),
("C", t('shape_C'), t('shape_C_tt')),
), name="", default=bpy.context.preferences.addons[__package__].preferences.shapekeys_dropdown, description="")
shader_dropdown : EnumProperty(
items=(
("A", t('shader_A'), ''),
("B", t('shader_B'), ''),
("D", t('shader_D'), ''),
("C", t('shader_C'), t('shader_C_tt')),
), name="", default=bpy.context.preferences.addons[__package__].preferences.shader_dropdown, description="Shader")
atlas_dropdown : EnumProperty(
items=(
("A", t('bake_light'), ""),
("B", t('bake_dark'), ""),
("C", t('bake_norm'), ""),
), name="", default="A", description='')
dropdown_box : EnumProperty(
items=(
("A", "Principled BSDF", "Default shading"),
("B", "Emission", "Flat shading"),
("C", "KK Shader", "Anime cel shading"),
("D", "Custom", "Custom shading")
), name="", default="A", description="Shader type")
shadows_dropdown : EnumProperty(
items=(
("A", "None", "No shadows"),
("B", "Opaque", ""),
("C", "Alpha Clip", ""),
("D", "Alpha Hashed", "")
), name="", default="A", description="Shadow Mode")
blend_dropdown : EnumProperty(
items=(
("A", "Opaque", ""),
("B", "Alpha Clip", ""),
("C", "Alpha Hashed", ""),
("D", "Alpha Blend", ""),
), name="", default="B", description="Blend Mode")
studio_lut_bool : BoolProperty(
name="Enable or Disable",
description=t('convert_texture_tt'),
default = True)
rokoko_bool : BoolProperty(
name="Enable or Disable",
description="""Enable this if you don't want KKBP to process the fbx animation, and instead want to use the rokoko plugin to transfer the fbx animation to your character.
Stock / unmodified armatures only!""",
default = False)
animation_import_type : BoolProperty(
name="Enable or Disable",
description=t('animation_type_tt'),
default = False)
image_dropdown : EnumProperty(
items=(
("A", t('dark_C'), "Use Day LUT to saturate image"),
("B", t('dark_A'), "Use Night LUT to saturate image"),
("C", t('dark_B'), "Use Sunset LUT to saturate image")
), name="", default="A", description="LUT Choice")
#The main panel
class IMPORTINGHEADER_PT_panel(bpy.types.Panel):
bl_label = t('import_export')
bl_category = "KKBP"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
def draw(self,context):
layout = self.layout
class IMPORTING_PT_panel(bpy.types.Panel):
bl_parent_id = "IMPORTINGHEADER_PT_panel"
bl_label = t('import_export')
bl_category = "KKBP"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_options = {'HIDE_HEADER'}
def draw(self,context):
scene = context.scene.kkbp
layout = self.layout
splitfac = 0.5
box = layout.box()
col = box.column(align=True)
# row = col.row(align=True)
# row.operator('kkbp.debug', text = 'Debug', icon='FILE_FOLDER')
row = col.row(align=True)
row.operator('kkbp.kkbpimport', text = t('import_model'), icon='FILE_FOLDER')
row.enabled = scene.plugin_state not in ['imported', 'prepped']
row = col.row(align = True)
box = row.box()
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align = True, factor=splitfac)
split.prop(context.scene.kkbp, "categorize_dropdown")
split.prop(context.scene.kkbp, "armature_dropdown")
split.enabled = scene.plugin_state not in ['imported', 'prepped']
row = col.row(align=True)
split = row.split(align = True, factor=splitfac)
split.prop(context.scene.kkbp, "shapekeys_dropdown")
split.prop(context.scene.kkbp, "shader_dropdown")
row.enabled = scene.plugin_state not in ['imported', 'prepped']
row = col.row(align=True)
split = row.split(align = True, factor=splitfac)
split.prop(context.scene.kkbp, "colors_dropdown", toggle=True, text = t('dark_F') if scene.colors_dropdown else t('dark_C'))
split.prop(context.scene.kkbp, "delete_cache", toggle=True, text = t('delete_cache'))
row.enabled = scene.plugin_state not in ['imported', 'prepped']
row = col.row(align=True)
split = row.split(align = True, factor=splitfac)
split.prop(context.scene.kkbp, "fix_seams", toggle=True, text = t('seams'))
split.prop(context.scene.kkbp, "use_material_fake_user", toggle=True, text = t('keep_templates'))
row.enabled = scene.plugin_state not in ['imported', 'prepped']
row = col.row(align=True)
split = row.split(align = True, factor=splitfac)
split.prop(context.scene.kkbp, "use_single_outline", toggle=True, text = t('outline'))
split.prop(context.scene.kkbp, "sfw_mode", toggle=True, text = t('sfw_mode'))
row.enabled = scene.plugin_state not in ['imported', 'prepped']
col = box.column(align=True)
row = col.row(align=True)
row.operator('kkbp.bakematerials', text = t('bake'), icon='OUTPUT')
row.enabled = scene.plugin_state in ['imported', 'prepped']
row = col.row(align=True)
split = row.split(align=True, factor=0.33)
split.prop(context.scene.kkbp, "bake_light_bool", toggle=True, text = t('bake_light'))
split.prop(context.scene.kkbp, "bake_dark_bool", toggle=True, text = t('bake_dark'))
split.prop(context.scene.kkbp, "bake_norm_bool", toggle=True, text = t('bake_norm'))
row.enabled = scene.plugin_state in ['imported', 'prepped']
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.prop(context.scene.kkbp, 'old_bake_bool', toggle=True, text = t('old_bake'))
split.prop(context.scene.kkbp, "bake_mult", text = t('bake_mult'))
row.enabled = scene.plugin_state in ['imported', 'prepped']
row = col.row(align = True)
split = row.split(align=True, factor=splitfac)
if globs.pil_exist == 'no':
split.operator('kkbp.get_pillow', text = t('pillow'), icon='FILE_REFRESH')
split.operator('kkbp.resetmaterials', text = t('reset_mats'), icon='RECOVER_LAST')
elif globs.pil_exist == 'restart':
col = col.box().column()
col.label(text='Installation complete')
col.label(text='Please restart Blender')
else:
split.prop(context.scene.kkbp, "use_atlas", toggle=True, text = t('use_atlas') if scene.use_atlas else t('dont_use_atlas'))
split.operator('kkbp.resetmaterials', text = t('reset_mats'), icon='RECOVER_LAST')
row.enabled = scene.plugin_state in ['imported', 'prepped']
class EXPORTING_PT_panel(bpy.types.Panel):
bl_parent_id = "IMPORTING_PT_panel"
bl_label = 'Exporting'
bl_options = {'HIDE_HEADER'}
bl_category = "KKBP"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
def draw(self,context):
scene = context.scene.kkbp
layout = self.layout
splitfac = 0.5
box = layout.box()
col = box.column(align=True)
row = col.row(align=True)
row.operator('kkbp.exportprep', text = t('prep'), icon = 'MODIFIER')
row.enabled = scene.plugin_state in ['imported'] and bpy.context.scene.kkbp.armature_dropdown in ['A', 'C', 'D']
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.prop(context.scene.kkbp, "simp_dropdown")
split.prop(context.scene.kkbp, "prep_dropdown")
row.enabled = scene.plugin_state in ['imported'] and bpy.context.scene.kkbp.armature_dropdown in ['A', 'C', 'D']
class EXTRAS_PT_panel(bpy.types.Panel):
bl_label = t('extras')
bl_category = "KKBP"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_options = {'DEFAULT_CLOSED'}
def draw(self,context):
layout = self.layout
scene = context.scene.kkbp
splitfac = 0.6
box = layout.box()
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text=t('studio_object'))
split.operator('kkbp.importstudio', text = '', icon = 'PACKAGE')
row = col.row(align=True)
split = row.split(align=True)
split.label(text="Shader")
split.label(text="Shadow Mode")
split.label(text="Blend Mode")
split.label(text="")
row = col.row(align=True)
split = row.split(align=True)
split.prop(context.scene.kkbp, "dropdown_box")
split.prop(context.scene.kkbp, "shadows_dropdown")
split.prop(context.scene.kkbp, "blend_dropdown")
split.prop(context.scene.kkbp, "studio_lut_bool", toggle=True, text = t('convert_texture'))
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text = t('single_animation'))
split.operator('kkbp.importanimation', text = '', icon = 'ARMATURE_DATA')
row.enabled = scene.plugin_state in ['imported'] and bpy.context.scene.kkbp.armature_dropdown == 'B'
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text="")
split.prop(context.scene.kkbp, "animation_import_type", toggle=True, text = t('animation_mix') if scene.animation_import_type else t('animation_koi'))
row.enabled = scene.plugin_state in ['imported'] and bpy.context.scene.kkbp.armature_dropdown == 'B'
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text=t('animation_library'))
split.operator('kkbp.createanimassetlib', text = '', icon = 'ARMATURE_DATA')
row.enabled = scene.plugin_state in ['imported'] and bpy.context.scene.kkbp.armature_dropdown == 'B'
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text="")
split.prop(context.scene.kkbp, "animation_library_scale", toggle=True, text = t('animation_library_scale'))
row.enabled = scene.plugin_state in ['imported'] and bpy.context.scene.kkbp.armature_dropdown == 'B'
# col = box.column(align=True)
# row = col.row(align=True)
# split = row.split(align=True, factor=splitfac)
# split.label(text=t('map_library'))
# split.operator('kkbp.createmapassetlib', text = '', icon = 'WORLD')
box = layout.box()
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text=t('sep_eye'))
split.operator('kkbp.linkshapekeys', text = '', icon='HIDE_OFF')
box = layout.box()
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text=t('bone_visibility'))
split.operator('kkbp.updatebones', text = '', icon = 'GROUP_BONE')
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text=t('rigify_convert'))
split.operator('kkbp.rigifyconvert', text = '', icon='MOD_ARMATURE')
col = box.column(align=True)
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text=t('matcomb'))
split.operator('kkbp.matcombsetup', text = '', icon='NODETREE')
row = col.row(align=True)
split = row.split(align=True, factor=splitfac)
split.label(text=t('mat_comb_switch'))
split.operator('kkbp.matcombswitch', text = '', icon='FILE_REFRESH')
#check https://ui.blender.org/icons/ for all icon names
#Add a button to the materials tab that lets you update all hair material settings at once
class HAIR_PT_panel(bpy.types.Panel):
#bl_parent_id = "EEVEE_MATERIAL_PT_surface"
bl_label = "kkbp_hair"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "material"
bl_options = {'HIDE_HEADER'}
COMPAT_ENGINES = {'BLENDER_EEVEE', 'BLENDER_EEVEE_NEXT'}
def draw(self, context):
layout = self.layout
mat = context.material
if mat:
if mat.get('hair'):
layout.operator('kkbp.linkhair', text = t('link_hair'), icon='NODETREE')
def register():
bpy.utils.register_class(PlaceholderProperties)
bpy.utils.register_class(IMPORTINGHEADER_PT_panel)
bpy.utils.register_class(IMPORTING_PT_panel)
bpy.utils.register_class(EXPORTING_PT_panel)
bpy.utils.register_class(EXTRAS_PT_panel)
bpy.utils.register_class(HAIR_PT_panel)
def unregister():
bpy.utils.unregister_class(HAIR_PT_panel)
bpy.utils.unregister_class(EXTRAS_PT_panel)
bpy.utils.unregister_class(EXPORTING_PT_panel)
bpy.utils.unregister_class(IMPORTING_PT_panel)
bpy.utils.unregister_class(IMPORTINGHEADER_PT_panel)
bpy.utils.unregister_class(PlaceholderProperties)
if __name__ == "__main__":
#unregister()
register()