- Add new `apply_better_fbx_orientations` operator as standalone UI action for applying bone orientations - Add "Delete Accessory Bones" property and UI toggle in EXTRAS panel for optional bone cleanup - Move bone orientation logic from `modify_armature` execute method to new dedicated operator - Refactor accessory bone deletion into separate method callable from new operator - Update panel UI to show better FBX bones operator with delete accessory bones toggle option - Register new operator in `__init__.py` and export from modifyarmature module - Update interface dictionaries (English, Japanese, Chinese) with new UI strings - Enable better FBX orientations and accessory deletion only for Koikatsu armature (C) selection - Add detailed logging for bone operation counts and status tracking
448 lines
18 KiB
Python
448 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)
|
|
|
|
delete_accessory_bones : BoolProperty(
|
|
name="Delete Accessory Bones",
|
|
description=t('delete_accessory_bones_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('better_fbx_bones'))
|
|
split.operator('kkbp.applybetterfbxorientations', text = '', icon='BONE_DATA')
|
|
row.enabled = scene.plugin_state in ['imported'] and scene.armature_dropdown == 'C'
|
|
row = col.row(align=True)
|
|
split = row.split(align=True, factor=splitfac)
|
|
split.label(text="")
|
|
split.prop(context.scene.kkbp, "delete_accessory_bones", toggle=True, text = t('delete_accessory_bones'))
|
|
row.enabled = scene.plugin_state in ['imported'] and scene.armature_dropdown == 'C'
|
|
|
|
|
|
|
|
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()
|