├── .gitignore ├── KK Shader V6.0.blend ├── plugins ├── KKBP_Exporter.zip └── what is this.txt ├── importing ├── luts │ ├── Lut_TimeDay.png │ ├── Lut_TimeNight.png │ └── Lut_TimeSunset.png ├── importbuttons.py ├── finalizegrey.py ├── importgrey.py ├── darkcolors.py ├── cleanarmature.py └── shapekeys.py ├── .github └── ISSUE_TEMPLATE │ ├── something-else.md │ ├── feature_request.md │ └── bug-report.md ├── extras ├── updatebones.py ├── toggleik.py ├── imageconvert.py ├── catsscripts │ └── armature_manual.py ├── linkshapekeys.py ├── rigifywrapper.py ├── importanimation.py ├── switcharmature.py ├── separatemeshes.py └── rigifyscripts │ └── rigify_after.py ├── exporting ├── exportfbx.py ├── applymaterials.py └── exportprep.py ├── __init__.py ├── README.md ├── interface ├── dictionary_jp.py └── dictionary_en.py └── Changelog.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/settings.json 2 | *.pyc 3 | *.pyc 4 | KK Shader V6.0.blend1 5 | KK Shader V5.0.blend1 6 | -------------------------------------------------------------------------------- /KK Shader V6.0.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaMoots/KK-Blender-Shader-Pack/HEAD/KK Shader V6.0.blend -------------------------------------------------------------------------------- /plugins/KKBP_Exporter.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaMoots/KK-Blender-Shader-Pack/HEAD/plugins/KKBP_Exporter.zip -------------------------------------------------------------------------------- /importing/luts/Lut_TimeDay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaMoots/KK-Blender-Shader-Pack/HEAD/importing/luts/Lut_TimeDay.png -------------------------------------------------------------------------------- /importing/luts/Lut_TimeNight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaMoots/KK-Blender-Shader-Pack/HEAD/importing/luts/Lut_TimeNight.png -------------------------------------------------------------------------------- /importing/luts/Lut_TimeSunset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MediaMoots/KK-Blender-Shader-Pack/HEAD/importing/luts/Lut_TimeSunset.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/something-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Something else 3 | about: A blank issue template 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Report a bug 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please include the card that causes the issue! 11 | It can really help with the debug process. 12 | -------------------------------------------------------------------------------- /plugins/what is this.txt: -------------------------------------------------------------------------------- 1 | KKBP_Exporter v3.89 is a plugin to export characters from KK/KKS for use with KKBP 2 | Place it in your /Koikatsu/BepInEx/plugins/ folder, or load it with KKManager. 3 | 4 | The Plugin dll in "net3.5" is for Koikatsu / Koikatsu Party 5 | The Plugin dll in "net4.6" is for Koikatsu Sunshine 6 | 7 | Requires: 8 | KKAPI or KKSAPI > v1.32 9 | KK_Accessory_States.dll or KKS_Accessory_States.dll 10 | KK_Pushup.dll or KKS_Pushup.dll -------------------------------------------------------------------------------- /extras/updatebones.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class update_bones(bpy.types.Operator): 4 | bl_idname = "kkb.updatebones" 5 | bl_label = "Update bones" 6 | bl_description = "Updates visibility of bones based on which outfits are hidden in the outliner" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | def execute(self, context): 10 | 11 | rigify_armature = [ob for ob in bpy.data.objects if ob.type == 'ARMATURE' and ob.get('rig_ui')] 12 | arm = rigify_armature[0] if len(rigify_armature) else bpy.data.objects['Armature'] 13 | arm = bpy.data.armatures[arm.data.name] 14 | 15 | #check if the outfit linked to accessory bones on the armature is visible or not, then update the bone visibility 16 | for bone in arm.bones: 17 | if bone.get('KKBP outfit ID'): 18 | outfit_detected = False 19 | print("{} for {}".format(bone.name, bone['KKBP outfit ID'])) 20 | for outfit_number in bone['KKBP outfit ID']: 21 | matching_outfit = bpy.data.objects.get('Outfit 0' + str(outfit_number)) 22 | if matching_outfit: 23 | print(matching_outfit.hide) 24 | outfit_detected += not matching_outfit.hide 25 | bone.hide = False if outfit_detected else True 26 | 27 | return {'FINISHED'} 28 | 29 | if __name__ == "__main__": 30 | bpy.utils.register_class(update_bones) -------------------------------------------------------------------------------- /extras/toggleik.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | class toggle_ik(bpy.types.Operator): 4 | bl_idname = "kkb.toggleik" 5 | bl_label = "Toggle IKs" 6 | bl_description = "Click this to toggle the hand and arm IKs" 7 | bl_options = {'REGISTER', 'UNDO'} 8 | 9 | def execute(self, context): 10 | 11 | pose_bones = bpy.data.objects['Armature'].pose.bones 12 | data_bones = bpy.data.objects['Armature'].data.bones 13 | 14 | #if the constaints are active, disable them 15 | if pose_bones['Left wrist'].constraints[0].mute == False: 16 | pose_bones['Left wrist'].constraints[0].mute = True 17 | pose_bones['Right wrist'].constraints[0].mute = True 18 | pose_bones['Left elbow'].constraints[0].mute = True 19 | pose_bones['Right elbow'].constraints[0].mute = True 20 | data_bones['Left wrist'].hide = False 21 | data_bones['Right wrist'].hide = False 22 | 23 | #else enable them 24 | else: 25 | pose_bones['Left wrist'].constraints[0].mute = False 26 | pose_bones['Right wrist'].constraints[0].mute = False 27 | pose_bones['Left elbow'].constraints[0].mute = False 28 | pose_bones['Right elbow'].constraints[0].mute = False 29 | data_bones['Left wrist'].hide = False 30 | data_bones['Right wrist'].hide = False 31 | return {'FINISHED'} 32 | 33 | if __name__ == "__main__": 34 | bpy.utils.register_class(toggle_ik) -------------------------------------------------------------------------------- /extras/imageconvert.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import bpy 3 | from ..importing.finalizepmx import kklog 4 | from ..importing.importcolors import image_to_KK, load_luts 5 | 6 | class image_convert(bpy.types.Operator): 7 | bl_idname = "kkb.imageconvert" 8 | bl_label = "Convert image" 9 | bl_description = "Click this to convert the currently loaded image" 10 | bl_options = {'REGISTER', 'UNDO'} 11 | 12 | def execute(self, context): 13 | kklog("Converting image: ".format(context.space_data.image)) 14 | 15 | scene = context.scene.kkbp 16 | lut_selection = scene.image_dropdown 17 | 18 | if lut_selection == 'A': 19 | lut_choice = 'Lut_TimeDay.png' 20 | elif lut_selection == 'B': 21 | lut_choice = 'Lut_TimeNight.png' 22 | else: 23 | lut_choice = 'Lut_TimeSunset.png' 24 | 25 | image = context.space_data.image 26 | image.reload() 27 | image.colorspace_settings.name = 'sRGB' 28 | 29 | # Use code from importcolors to convert the current image 30 | load_luts(lut_choice, lut_choice) 31 | # Need to run image_to_KK twice for the first image due to a weird bug 32 | image_to_KK(image, lut_choice) 33 | 34 | new_pixels, width, height = image_to_KK(image, lut_choice) 35 | image.pixels = new_pixels 36 | #image.save() 37 | 38 | return {'FINISHED'} 39 | 40 | if __name__ == "__main__": 41 | bpy.utils.register_class(image_convert) 42 | 43 | # test call 44 | print((bpy.ops.kkb.imageconvert('INVOKE_DEFAULT'))) -------------------------------------------------------------------------------- /exporting/exportfbx.py: -------------------------------------------------------------------------------- 1 | #wrapper for blender's File > Export > FBX dialog 2 | 3 | import bpy, traceback, time 4 | from ..importing.importbuttons import kklog 5 | from bpy.props import StringProperty 6 | 7 | #load plugin language 8 | from bpy.app.translations import locale 9 | if locale == 'ja_JP': 10 | from ..interface.dictionary_jp import t 11 | else: 12 | from ..interface.dictionary_en import t 13 | 14 | class export_fbx(bpy.types.Operator): 15 | bl_idname = "kkb.exportfbx" 16 | bl_label = "Export model" 17 | bl_description = t('export_fbx_tt') 18 | bl_options = {'REGISTER', 'UNDO'} 19 | 20 | filepath : StringProperty(maxlen=1024, default='', options={'HIDDEN'}) 21 | filter_glob : StringProperty(default='*.fbx', options={'HIDDEN'}) 22 | 23 | def execute(self, context): 24 | last_step = time.time() 25 | kklog('Finished in ' + str(time.time() - last_step)[0:4] + 's') 26 | try: 27 | kklog('Exporting model to fbx format...') 28 | bpy.ops.export_scene.fbx('EXEC_DEFAULT', 29 | filepath=self.filepath if self.filepath[-4:] == '.fbx' else self.filepath+'.fbx', 30 | object_types={'EMPTY', 'ARMATURE', 'MESH', 'OTHER'}, 31 | use_mesh_modifiers=False, 32 | add_leaf_bones=False, 33 | bake_anim=False, 34 | apply_scale_options='FBX_SCALE_ALL', 35 | path_mode='COPY', 36 | embed_textures=True, 37 | mesh_smooth_type='OFF') 38 | return {'FINISHED'} 39 | except: 40 | kklog('Unknown python error occurred', type = 'error') 41 | kklog(traceback.format_exc()) 42 | self.report({'ERROR'}, traceback.format_exc()) 43 | return {"CANCELLED"} 44 | 45 | def invoke(self, context, event): 46 | context.window_manager.fileselect_add(self) 47 | return {'RUNNING_MODAL'} 48 | 49 | if __name__ == "__main__": 50 | bpy.utils.register_class(export_fbx) 51 | 52 | # test call 53 | print((bpy.ops.kkb.selectbones('INVOKE_DEFAULT'))) 54 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #The init file for the plugin 2 | bl_info = { 3 | "name" : "KK Blender Porter Pack", 4 | "author" : "a blendlet and some blenderchads", 5 | "location" : "View 3D > Tool Shelf > KKBP and Image Editor > Tool Shelf > KKBP", 6 | "description" : "Scripts to automate cleanup of a Koikatsu export", 7 | "version": (6, 0, 0), 8 | "blender" : (3, 1, 0), 9 | "category" : "3D View", 10 | "tracker_url" : "https://github.com/FlailingFog/KK-Blender-Porter-Pack/" 11 | } 12 | 13 | import bpy 14 | from bpy.utils import register_class, unregister_class 15 | 16 | from bpy.types import Scene 17 | from bpy.props import PointerProperty 18 | 19 | from .importing.bonedrivers import bone_drivers 20 | from .importing.cleanarmature import clean_armature 21 | from .importing.finalizegrey import finalize_grey 22 | from .importing.finalizepmx import finalize_pmx 23 | from .importing.importeverything import import_everything 24 | from .importing.importcolors import import_colors 25 | from .importing.importgrey import import_grey 26 | from .importing.importbuttons import quick_import, mat_import 27 | from .importing.separatebody import separate_body 28 | from .importing.shapekeys import shape_keys 29 | 30 | from .exporting.bakematerials import bake_materials 31 | from .exporting.applymaterials import apply_materials 32 | from .exporting.exportprep import export_prep 33 | from .exporting.exportfbx import export_fbx 34 | 35 | from .extras.importstudio import import_studio 36 | from .extras.linkshapekeys import link_shapekeys 37 | from .extras.importanimation import import_animation 38 | from .extras.switcharmature import switch_armature 39 | from .extras.separatemeshes import separate_meshes 40 | from .extras.separatemeshes import export_separate_meshes 41 | from .extras.toggleik import toggle_ik 42 | from .extras.updatebones import update_bones 43 | from .extras.imageconvert import image_convert 44 | from .extras.rigifywrapper import rigify_convert 45 | from .extras.rigifyscripts.rigify_before import rigify_before 46 | from .extras.rigifyscripts.rigify_after import rigify_after 47 | from .extras.catsscripts.armature_manual import MergeWeights 48 | 49 | from . KKPanel import PlaceholderProperties 50 | from . KKPanel import ( 51 | IMPORTINGHEADER_PT_panel, 52 | IMPORTING_PT_panel, 53 | EXPORTING_PT_panel, 54 | EXTRAS_PT_panel, 55 | EDITOR_PT_panel 56 | ) 57 | 58 | classes = ( 59 | apply_materials, 60 | bake_materials, 61 | export_prep, 62 | export_fbx, 63 | image_convert, 64 | 65 | import_animation, 66 | import_studio, 67 | link_shapekeys, 68 | switch_armature, 69 | separate_meshes, 70 | export_separate_meshes, 71 | toggle_ik, 72 | update_bones, 73 | rigify_convert, 74 | rigify_before, 75 | rigify_after, 76 | MergeWeights, 77 | 78 | bone_drivers, 79 | clean_armature, 80 | finalize_grey, 81 | finalize_pmx, 82 | import_everything, 83 | import_colors, 84 | import_grey, 85 | quick_import, 86 | mat_import, 87 | separate_body, 88 | shape_keys, 89 | 90 | PlaceholderProperties, 91 | IMPORTINGHEADER_PT_panel, 92 | IMPORTING_PT_panel, 93 | EXPORTING_PT_panel, 94 | EXTRAS_PT_panel, 95 | EDITOR_PT_panel) 96 | 97 | def register(): 98 | 99 | for cls in classes: 100 | register_class(cls) 101 | 102 | Scene.kkbp = PointerProperty(type=PlaceholderProperties) 103 | 104 | def unregister(): 105 | 106 | for cls in reversed(classes): 107 | unregister_class(cls) 108 | 109 | del Scene.kkbp 110 | 111 | if __name__ == "__main__": 112 | register() 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KK Blender Porter Pack 2 | Plugin pack for exporting and setting up Koikatsu characters in Blender. 3 | 4 | The ```KKBP exporter for Koikatsu``` is used to export the character's mesh, armature and color data. The exported data is then processed by the ```KKBP plugin for Blender```. Once characters are setup in Blender, they can be saved as FBX files for use in other programs. 5 | 6 | The changelog for the pack [can be found here.](https://github.com/FlailingFog/KK-Blender-Shader-Pack/blob/master/Changelog.md) 7 | The pack [also has a barebones wiki here.](https://github.com/FlailingFog/KK-Blender-Shader-Pack/wiki) 8 | 9 | # Download 10 | Stable versions of KKBP are [on the release page](https://github.com/FlailingFog/KK-Blender-Porter-Pack/releases). 11 | The latest version of KKBP can be downloaded by using the green CODE button on the top right of the page and choosing "Download ZIP" 12 | 13 | # Usage Instructions for V6 14 | ### Exporting from Koikatsu and importing to Blender 15 |
Click to expand 16 | 17 | #### Prerequisites: 18 | * Install [HF Patch v3.16 or later](https://github.com/ManlyMarco/KK-HF_Patch) for Koikatsu or [HF Patch v1.7 or later](https://github.com/ManlyMarco/KKS-HF_Patch) for Koikatsu Sunshine 19 | * Install either [CATS](https://github.com/GiveMeAllYourCats/cats-blender-plugin) or [mmd_tools](https://github.com/UuuNyaa/blender_mmd_tools) for Blender 20 | 21 | 1. Install KKBP for Koikatsu by copying the KKBP_Exporter.DLL into the plugins folder: C:/Koikatsu install directory/BepInEx/plugins/ 22 | a. Use the net3.5 exporter for Koikatsu and Koikatsu Party 23 | b. Use the net4.6 exporter for Koikatsu Sunshine 24 | 1. Start the game, go to the character creator and load your character 25 | 1. Click the "Export Model for KKBP" button on the top of the screen. This may take a few minutes depending on your hardware. A folder will popup when the export is finished 26 | ![ ](https://github.com/FlailingFog/KK-Blender-Porter-Pack/blob/assets/readme/exportpanel.PNG) 27 | 1. Copy the entire folder generated by the plugin to your desktop. This folder is located in C:/Koikatsu install directory/Export_PMX. The format of this folder is ######_CharacterName. 28 | 1. Install KKBP for Blender through the addon menu 29 | 1. Click the Import Model button in the KKBP panel and choose the .pmx file from the export folder. This may take a few minutes depending on your hardware. 30 | ![ ](https://github.com/FlailingFog/KK-Blender-Porter-Pack/blob/assets/readme/panelimport.PNG) 31 |
32 | 33 | ### Exporting from Blender 34 | 35 |
Click to expand 36 | 37 | 1. Save a backup file of your finished model 38 | 1. Choose which export type you want in the KKBP panel. There's currently a targeted export type for Unity (VRM), and a generic fbx type for everything else 39 | 1. Click the "Prep for target application" button 40 | 1. Click the "Bake material templates" button and choose the folder you want to store all of your baked images to (warning: there's going to be a lot, so an empty folder is recommended) 41 | 1. Create an altas for the body, clothes and hair objects using the [material combiner](https://github.com/Grim-es/material-combiner-addon) addon 42 | 1. Hit the undo button to return to the state before you created the atlas. Change the menu under the "Apply baked templates" button to "Dark" and click the button to load in the dark textures. Use material combiner again to generate the dark version of the material atlas 43 | 1. Click the export FBX button to invoke the built-in fbx export dialog 44 |
45 | 46 | # Similar Projects 47 | 48 | * [Koikatsu Pmx Exporter (Reverse Engineered & updated)](https://github.com/Snittern/KoikatsuPmxExporterReverseEngineered) 49 | * [KKPMX](https://github.com/CazzoPMX/KKPMX) 50 | * [Grey's mesh exporter for Koikatsu](https://www.google.com/search?q=koikatsu+discord) 51 | -------------------------------------------------------------------------------- /extras/catsscripts/armature_manual.py: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | # Copyright (c) 2017 GiveMeAllYourCats 4 | 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the 'Software'), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | 15 | # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | # Code author: Hotox 24 | # Repo: https://github.com/michaeldegroot/cats-blender-plugin 25 | # Edits by: GiveMeAllYourCats 26 | 27 | import bpy 28 | 29 | from . import common as Common 30 | #from .register import register_wrap 31 | #from .translations import t 32 | 33 | #@register_wrap 34 | class MergeWeights(bpy.types.Operator): 35 | bl_idname = 'kkb.cats_merge_weights' 36 | bl_label = 'cats merge weights' 37 | #bl_description = t('MergeWeights.desc') 38 | bl_options = {'REGISTER', 'UNDO'} 39 | ''' 40 | @classmethod 41 | def poll(cls, context): 42 | active_obj = bpy.context.active_object 43 | if not active_obj or not bpy.context.active_object.type == 'ARMATURE': 44 | return False 45 | if active_obj.mode == 'EDIT' and bpy.context.selected_editable_bones: 46 | return True 47 | if active_obj.mode == 'POSE' and bpy.context.selected_pose_bones: 48 | return True 49 | 50 | return False 51 | ''' 52 | def execute(self, context): 53 | saved_data = Common.SavedData() 54 | 55 | armature = bpy.context.object 56 | 57 | Common.switch('EDIT') 58 | 59 | # Find which bones to work on and put their name and their parent in a list 60 | parenting_list = {} 61 | for bone in bpy.context.selected_editable_bones: 62 | parent = bone.parent 63 | while parent and parent.parent and parent in bpy.context.selected_editable_bones: 64 | parent = parent.parent 65 | if not parent: 66 | continue 67 | parenting_list[bone.name] = parent.name 68 | 69 | # Merge all the bones in the parenting list 70 | merge_weights(armature, parenting_list) 71 | 72 | saved_data.load() 73 | 74 | self.report({'INFO'}, 'cats merge weights success') 75 | return {'FINISHED'} 76 | 77 | def merge_weights(armature, parenting_list): 78 | Common.switch('OBJECT') 79 | # Merge the weights on the meshes 80 | for mesh in Common.get_meshes_objects(armature_name=armature.name, visible_only=bpy.context.scene.merge_visible_meshes_only if bpy.context.scene.get('merge_visible_meshes_only') != None else True): 81 | Common.set_active(mesh) 82 | 83 | for bone, parent in parenting_list.items(): 84 | if not mesh.vertex_groups.get(bone): 85 | continue 86 | if not mesh.vertex_groups.get(parent): 87 | mesh.vertex_groups.new(name=parent) 88 | Common.mix_weights(mesh, bone, parent) 89 | 90 | # Select armature 91 | Common.unselect_all() 92 | Common.set_active(armature) 93 | Common.switch('EDIT') 94 | 95 | # Delete merged bones 96 | if True: 97 | for bone in parenting_list.keys(): 98 | armature.data.edit_bones.remove(armature.data.edit_bones.get(bone)) 99 | -------------------------------------------------------------------------------- /interface/dictionary_jp.py: -------------------------------------------------------------------------------- 1 | from .dictionary_en import translation_dictionary as english_fallback 2 | 3 | translation_dictionary = { 4 | 'bake_mult' : 'ベーク乗数:', 5 | 'bake_mult_tt' : "ベークしたテクスチャーがぼやけている場合は2,3にしてみて", 6 | 7 | 'seams' : "体を縫合する", 8 | 'seams_tt' : '体に近い頂点をマージ。 このマージには首のウエイトが台無しになる可能性がある。 このオプションを無効にしたらウエイトが保存されるけど体に縫い目が見えるかも', 9 | 10 | 'outline' : '一つのアウトラインモード', 11 | 'outline_tt' : "このオプションにしたらシングルのアウトラインを使われる。 このオプションにしたらアウトライン透明の問題が起こるかも", 12 | 13 | 'keep_templates' : "マテリアルテンプレートを保存", 14 | 'keep_templates_tt' : "KKBPマテリアルテンプレートをフェイクユーザーに設定する", 15 | 16 | 'sfw_mode' : 'エロ無しモード', 17 | 'sfw_mode_tt' : 'プラグインがエロの部分を隠してみる', 18 | 19 | 'arm_drop' :"アーマチュアのタイプ", 20 | 'arm_drop_A' : "KKBPアーマチュアにする", 21 | 'arm_drop_A_tt' : "KKBPアーマチュアにする。この設定にはアーマチュアは改造されて、ベーシックなIKがジェネレートされる", 22 | 'arm_drop_B' : "Rigifyアーマチュアにする", 23 | 'arm_drop_B_tt' : "Rigifyアーマチュアにする。この設定にはBlenderでの使用アーマチュアがジェネレートされる", 24 | 'arm_drop_C' : "コイカツのアーマチュアにする", 25 | 'arm_drop_C_tt' : "コイカツのアーマチュアにする。この設定にはボーンの名前、アーマチュアの構造はコイカツのアーマチュアと一致される", 26 | 'arm_drop_D' : "PMXアーマチュアにする", 27 | 'arm_drop_D_tt' : "PMXアーマチュアにする。この設定にはアーマチュアが改造されない", 28 | 29 | 'cat_drop' : '操作タイプ', 30 | 'cat_drop_A' : "カテゴライズのために休止しない", 31 | 'cat_drop_A_tt' : "すべてをインポートして一つの服オブジェクトにする。別の服はひとりでに隠される", 32 | 'cat_drop_B' : "カテゴライズのために休止する", 33 | 'cat_drop_B_tt' : "すべてをインポートしてインポートを休止する。休止の状態に手動で服を別々になれる。髪の毛は別のオブジェクトに別々にされてから新しいオブジェクトは「Hair」か「hair」に名前を変更しないと。服を手動でカテゴライズしたら「カテゴライズしまって」ボタンをクリックして。別の服はひとりでに隠される", 34 | 'cat_drop_C' : "AUTOカテゴライズ", 35 | 'cat_drop_C_tt' : "すべてをインポートして個々の服オブジェクトにする", 36 | 'cat_drop_D' : "SMRデータでカテゴライズ", 37 | 'cat_drop_D_tt' : "すべてをインポートしてSMR(Skinned Mesh Renderer)データで服を別々にする。この設定にはマテリアルテンプレートや色データや使われないからモデルはテクスチャーなしで見える", 38 | 39 | 'dark' : "ダークな色", 40 | 'dark_A' : "LUT 夜", 41 | 'dark_A_tt' : "ダークな色青くする", 42 | 'dark_B' : "LUT 暮れ", 43 | 'dark_B_tt' : "ダークな色赤にする", 44 | 'dark_C' : "LUT 昼", 45 | 'dark_C_tt' : "ダークな色はライトな色と同じにする", 46 | 'dark_D' : "飽和", 47 | 'dark_D_tt' : "ダークな色を飽和する", 48 | 'dark_E' : '明るさ', 49 | 'dark_E_tt' : "ダークな色黒くする", 50 | 'dark_F' : '実験的', 51 | 'dark_F_tt' : "実験的なスクリプトでダークな色をセットする", 52 | 53 | 'prep_drop' : "エクスポートタイプ", 54 | 'prep_drop_A' : "Unity - VRMコンパチ", 55 | 'prep_drop_A_tt' : """アウトラインを削除して... 56 | Eyewhiteの複写を削除して, 57 | Unityがボーンをひとりでに見つけられるためにアーマチュアを改造して""", 58 | 'prep_drop_B' : "汎用FBX - 変更なし", 59 | 'prep_drop_B_tt' : """アウトラインを削除して... 60 | Eyewhiteの複写を削除して""", 61 | 62 | 'simp_drop' : 'アーマチュアの簡略化', 63 | 'simp_drop_A' : '超シンプル (遅い)', 64 | 'simp_drop_A_tt': 'この設定には骨の数が減る。瞳の骨をアーマチュアレイヤー1に移って, アーマチュアレイヤー3,4,5,11,12,17,18,19の骨が簡略化される (約110骨が残る)', 65 | 'simp_drop_B' : 'シンプル', 66 | 'simp_drop_B_tt': 'この設定には骨の数が減る。瞳の骨をアーマチュアレイヤー1に移って, アーマチュアレイヤー11の骨が簡略化される (約1000骨が残る)', 67 | 'simp_drop_C' : '簡略化してない (早い)', 68 | 'simp_drop_C_tt': 'アーマチュアが簡略化されない', 69 | 70 | 'bake' : 'マテリアルテンプレートをベーク', 71 | 'bake_light' : "ライト", 72 | 'bake_light_tt' : "ライトテクスチャーをベーク", 73 | 'bake_dark' : "ダーク", 74 | 'bake_dark_tt' : "ダークテクスチャーをベーク", 75 | 'bake_norm' : "ノーマル", 76 | 'bake_norm_tt' : "ノーマルテクスチャーをベーク", 77 | 78 | 'shape_A' : 'KKBPシェイプキーにする', 79 | 'shape_A_tt' : 'シェイプキーを変更して部分的なシェイプキーを削除する', 80 | 'shape_B' : "部分的なシェイプキーを保存", 81 | 'shape_B_tt' : "シェイプキーを変更して部分的なシェイプキーを保存する", 82 | 'shape_C' : "シェイプキー変更しない", 83 | 'shape_C_tt' : "コイカツのシェイプキーにする。シェイプキーが変更されない", 84 | 85 | 'atlas' : 'アトラスタイプ', 86 | 87 | 'export_fbx' : 'FBXでエクスポート', 88 | 'export_fbx_tt' : 'モデルをエクスポートする。機能性はBlenderのFBXエクスポートと同じ', 89 | 90 | 'import_export' : 'インポート ・ エクスポート', 91 | 'import_model' : 'モデルをインポート', 92 | 'finish_cat' : 'カテゴライズしまって', 93 | 'recalc_dark' : 'ダークな色を再計算する', 94 | 'prep' : 'ターゲットアプリのために準備する', 95 | 'apply_temp' : 'ベークしたテンプレートをチェンジ', 96 | 97 | 'rigify_convert': "Rigifyアーマチュアに変えて", 98 | 'sep_eye' : "EyesやEyebrowsやBodyのオブジェクトから別々になって", 99 | 100 | 'convert_image' : 'KKBPのLUTでイマージをコンヴァート', 101 | 102 | 'quick_import_tt' : "コイカツモデル(.PMXフォーマット)をインポートして改造して", 103 | 'mat_import_tt' : "カテゴライズしまってテクスチャーや色データつけて", 104 | 'export_prep_tt' : "メニューの情報をチェックして", 105 | 'bake_mats_tt' : "フォルダにマテリアルテンプレートをベークして", 106 | 'apply_mats_tt' : "ベークしたマテリアルテンプレートをフォルダから読み取る。メニューからライト・ダーク・ノーマルバージョンを選択できる", 107 | 'import_colors_tt' : ".PMXフォルダを選択したらダークな色を再計算できる", 108 | 109 | } 110 | 111 | def t(text_entry): 112 | try: 113 | return translation_dictionary[text_entry] 114 | except: 115 | return english_fallback[text_entry] 116 | 117 | -------------------------------------------------------------------------------- /extras/linkshapekeys.py: -------------------------------------------------------------------------------- 1 | ''' 2 | LINK EYEBROW SHAPEKEYS TO BODY SCRIPT 3 | Usage: 4 | - Seperate the eyebrow mesh into its own object 5 | - Select the eyebrows object, then shift click the body object and run the script to control the eyebrow shapekeys from the body object 6 | 7 | Script 90% stolen from https://blender.stackexchange.com/questions/86757/python-how-to-connect-shapekeys-via-drivers 8 | ''' 9 | import bpy 10 | 11 | def link_keys(shapekey_holder_object, objects_to_link): 12 | 13 | shapekey_list_string = str(shapekey_holder_object.data.shape_keys.key_blocks.keys()).lower() 14 | for obj in objects_to_link: 15 | bpy.ops.object.select_all(action = 'DESELECT') 16 | bpy.context.view_layer.objects.active = obj 17 | obj.select_set(True) 18 | bpy.ops.object.material_slot_remove_unused() 19 | for key in obj.data.shape_keys.key_blocks: 20 | if key.name.lower() in shapekey_list_string: 21 | if not key.name == obj.data.shape_keys.key_blocks[0]: 22 | skey_driver = key.driver_add('value') 23 | skey_driver.driver.type = 'AVERAGE' 24 | #skey_driver.driver.show_debug_info = True 25 | if skey_driver.driver.variables: 26 | for v in skey_driver.driver.variables: 27 | skey_driver.driver.variables.remove(v) 28 | newVar = skey_driver.driver.variables.new() 29 | newVar.name = "value" 30 | newVar.type = 'SINGLE_PROP' 31 | newVar.targets[0].id_type = 'KEY' 32 | newVar.targets[0].id = shapekey_holder_object.data.shape_keys 33 | newVar.targets[0].data_path = 'key_blocks["' + key.name+ '"].value' 34 | skey_driver = key.driver_add('mute') 35 | skey_driver.driver.type = 'AVERAGE' 36 | #skey_driver.driver.show_debug_info = True 37 | if skey_driver.driver.variables: 38 | for v in skey_driver.driver.variables: 39 | skey_driver.driver.variables.remove(v) 40 | newVar = skey_driver.driver.variables.new() 41 | newVar.name = "hide" 42 | newVar.type = 'SINGLE_PROP' 43 | newVar.targets[0].id_type = 'KEY' 44 | newVar.targets[0].id = shapekey_holder_object.data.shape_keys 45 | newVar.targets[0].data_path = 'key_blocks["' + key.name+ '"].mute' 46 | 47 | 48 | class link_shapekeys(bpy.types.Operator): 49 | bl_idname = "kkb.linkshapekeys" 50 | bl_label = "Link shapekeys" 51 | bl_description = "Separates the Eyes and Eyebrows from the Body object and links the shapekeys to the Body object. Useful for when you want to make eyes or eyebrows appear through the hair using the compositor" 52 | bl_options = {'REGISTER', 'UNDO'} 53 | 54 | def execute(self, context): 55 | #separate the eyes from the body object 56 | body = bpy.data.objects['Body'] 57 | bpy.context.view_layer.objects.active = body 58 | bpy.ops.object.mode_set(mode = 'EDIT') 59 | bpy.ops.mesh.select_all(action = 'DESELECT') 60 | 61 | def separateMaterial(matList): 62 | for mat in matList: 63 | try: 64 | def moveUp(): 65 | return bpy.ops.object.material_slot_move(direction='UP') 66 | while moveUp() != {"CANCELLED"}: 67 | pass 68 | bpy.context.object.active_material_index = body.data.materials.find(mat) 69 | bpy.ops.object.material_slot_select() 70 | #grab the other eye material if there is one 71 | if mat == 'Template Eye (hitomi)' and 'Template Eye' in body.data.materials[body.data.materials.find(mat) + 1].name: 72 | bpy.context.object.active_material_index = body.data.materials.find(mat) + 1 73 | bpy.ops.object.material_slot_select() 74 | except: 75 | print('material wasn\'t found: ' + mat) 76 | bpy.ops.mesh.separate(type='SELECTED') 77 | 78 | eye_list = ['Template Eyeline up','Template Eyewhites (sirome)', 'Template Eyeline down', 'Template Eye (hitomi)'] 79 | separateMaterial(eye_list) 80 | 81 | eyes = bpy.data.objects['Body.001'] 82 | eyes.name = 'Eyes' 83 | 84 | #do the same for the eyebrows 85 | separateMaterial(['Template Eyebrows (mayuge)']) 86 | eyebrows = bpy.data.objects['Body.001'] 87 | eyebrows.name = 'Eyebrows' 88 | 89 | eyes.modifiers[3].show_viewport = False 90 | eyes.modifiers[3].show_render = False 91 | eyebrows.modifiers[3].show_viewport = False 92 | eyebrows.modifiers[3].show_render = False 93 | 94 | bpy.ops.object.mode_set(mode = 'OBJECT') 95 | link_keys(body, [eyes, eyebrows]) 96 | 97 | return {'FINISHED'} 98 | 99 | if __name__ == "__main__": 100 | bpy.utils.register_class(link_shapekeys) 101 | 102 | # test call 103 | print((bpy.ops.kkb.linkshapekeys('INVOKE_DEFAULT'))) 104 | -------------------------------------------------------------------------------- /extras/rigifywrapper.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Wrapper for the rigifyscripts folder 3 | ''' 4 | import bpy 5 | import bmesh 6 | import math 7 | from math import radians 8 | import statistics 9 | from ..importing.importbuttons import kklog 10 | from mathutils import Matrix, Vector, Euler 11 | import traceback 12 | import sys 13 | 14 | from typing import NamedTuple 15 | import mathutils 16 | 17 | from pathlib import Path 18 | 19 | class rigify_convert(bpy.types.Operator): 20 | bl_idname = "kkb.rigifyconvert" 21 | bl_label = "Convert for rigify" 22 | bl_description = """Runs several scripts to convert a KKBP armature to be Rigify compatible""" 23 | bl_options = {'REGISTER', 'UNDO'} 24 | 25 | def execute(self, context): 26 | try: 27 | kklog('\nConverting to Rigify...') 28 | #Make the armature active 29 | bpy.ops.object.mode_set(mode='OBJECT') 30 | bpy.ops.object.select_all(action='DESELECT') 31 | armature = bpy.data.objects['Armature'] 32 | armature.hide_set(False) 33 | armature.select_set(True) 34 | bpy.context.view_layer.objects.active=armature 35 | 36 | bpy.ops.kkb.rigbefore('INVOKE_DEFAULT') 37 | 38 | bpy.ops.pose.rigify_generate() 39 | 40 | bpy.ops.kkb.rigafter('INVOKE_DEFAULT') 41 | 42 | #make sure things are parented correctly and hide original armature 43 | rig = bpy.context.active_object 44 | armature = bpy.data.objects['Armature'] 45 | for child in armature.children: 46 | if child.name in ['Body'] or 'Outfit ' in child.name: 47 | print(child.name) 48 | #do for children first 49 | for ob in child.children: 50 | if ob.name in ['Tears'] or 'Outfit ' in ob.name: 51 | print(ob.name) 52 | hidden = ob.hide 53 | parent = ob.parent 54 | ob.hide = False 55 | bpy.ops.object.select_all(action='DESELECT') 56 | ob.parent = None 57 | ob.select_set(True) 58 | rig.select_set(True) 59 | bpy.context.view_layer.objects.active=rig 60 | bpy.ops.object.parent_set(type='ARMATURE_NAME') 61 | ob.hide = hidden 62 | ob.parent = parent 63 | hidden = child.hide 64 | child.hide = False 65 | bpy.ops.object.select_all(action='DESELECT') 66 | child.parent = None 67 | child.select_set(True) 68 | rig.select_set(True) 69 | bpy.context.view_layer.objects.active=rig 70 | bpy.ops.object.parent_set(type='ARMATURE_NAME') 71 | child.hide = hidden 72 | 73 | #find the last created armature modifier, replace it with the existing one 74 | child.modifiers[0].object = child.modifiers[-1].object 75 | child.modifiers.remove(child.modifiers[-1]) 76 | child.modifiers[0].name = 'Rigify Armature' 77 | 78 | #make sure the new bones on the generated rig retain the KKBP outfit id entry 79 | for bone in rig.data.bones: 80 | if bone.layers[0] == True or bone.layers[2] == True: 81 | if rig.data.bones.get('ORG-' + bone.name): 82 | if rig.data.bones['ORG-' + bone.name].get('KKBP outfit ID'): 83 | bone['KKBP outfit ID'] = rig.data.bones['ORG-' + bone.name]['KKBP outfit ID'] 84 | if rig.data.bones.get('DEF-' + bone.name): 85 | rig.data.bones['DEF-' + bone.name]['KKBP outfit ID'] = rig.data.bones['ORG-' + bone.name]['KKBP outfit ID'] 86 | 87 | #make sure the gfn empty is reparented to the head bone 88 | bpy.ops.object.select_all(action='DESELECT') 89 | bpy.context.view_layer.objects.active = rig 90 | empty = bpy.data.objects['GFN Empty'] 91 | empty.hide = False 92 | empty.select_set(True) 93 | bpy.ops.object.mode_set(mode='POSE') 94 | bpy.ops.pose.select_all(action='DESELECT') 95 | rig.data.bones['head'].select = True 96 | rig.data.bones.active = rig.data.bones['head'] 97 | bpy.ops.object.parent_set(type='BONE') 98 | bpy.ops.pose.select_all(action='DESELECT') 99 | bpy.ops.object.mode_set(mode='OBJECT') 100 | bpy.ops.object.select_all(action='DESELECT') 101 | bpy.data.node_groups['Generated Face Normals'].nodes['GFNEmpty'].object = empty 102 | bpy.context.view_layer.objects.active = empty 103 | empty.select_set(True) 104 | bpy.ops.object.move_to_collection(collection_index=1) 105 | empty.hide = True 106 | empty.hide_render = True 107 | 108 | armature.hide_set(True) 109 | bpy.ops.object.select_all(action='DESELECT') 110 | rig.select_set(True) 111 | bpy.context.view_layer.objects.active=rig 112 | rig.show_in_front = True 113 | bpy.context.scene.tool_settings.transform_pivot_point = 'INDIVIDUAL_ORIGINS' 114 | bpy.context.tool_settings.mesh_select_mode = (False, False, True) 115 | return {'FINISHED'} 116 | 117 | except: 118 | kklog('Unknown python error occurred', type = 'error') 119 | kklog(traceback.format_exc()) 120 | self.report({'ERROR'}, traceback.format_exc()) 121 | return {"CANCELLED"} 122 | 123 | if __name__ == "__main__": 124 | bpy.utils.register_class(rigify_convert) 125 | 126 | # test call 127 | print((bpy.ops.kkb.rigifyconvert('INVOKE_DEFAULT'))) 128 | 129 | -------------------------------------------------------------------------------- /extras/importanimation.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.props import StringProperty 3 | from mathutils import Quaternion 4 | from ..importing.bonedrivers import rename_bones_for_clarity 5 | 6 | def modify_animation_armature(): 7 | #move armature bones that didn't have animation data up a level 8 | bpy.ops.object.mode_set(mode='OBJECT') 9 | bpy.ops.object.select_all(action='DESELECT') 10 | animation_armature = bpy.data.objects['Animation Armature'] 11 | animation_armature.select_set(True) 12 | bpy.context.view_layer.objects.active = animation_armature 13 | 14 | bpy.ops.object.mode_set(mode='EDIT') 15 | animation_armature.data.edit_bones['Hips'].parent = animation_armature.data.edit_bones['Center'] 16 | animation_armature.data.edit_bones['cf_pv_root'].parent = animation_armature.data.edit_bones['Center'] 17 | animation_armature.data.edit_bones['Center'].parent = None 18 | 19 | bpy.ops.object.mode_set(mode='POSE') 20 | bpy.ops.pose.select_all(action='DESELECT') 21 | bpy.ops.object.mode_set(mode='OBJECT') 22 | 23 | def apply_animation(): 24 | #stolen from https://blender.stackexchange.com/questions/27136/ 25 | #this script will copy the animation data from the active Object to the selected object: 26 | armature = bpy.data.objects['Armature'] 27 | animation_armature = bpy.data.objects['Animation Armature'] 28 | 29 | properties = [p.identifier for p in animation_armature.animation_data.bl_rna.properties if not p.is_readonly] 30 | 31 | if armature.animation_data == None : 32 | armature.animation_data_create() 33 | for prop in properties: 34 | setattr(armature.animation_data, prop, getattr(animation_armature.animation_data, prop)) 35 | 36 | def correct_animation(): 37 | 38 | #find each bone rotation 39 | armature = bpy.data.objects['Armature'] 40 | myaction = bpy.context.active_object.animation_data.action 41 | fcurves = myaction.fcurves 42 | 43 | #pmx armatures have swapped x and y rotation channels because the bone has been rotated 44 | # the y and z channels are swapped for fbx armatures 45 | def flip_animation(bone_to_flip): 46 | bone = armature.pose.bones[bone_to_flip] 47 | dpath = bone.path_from_id("rotation_quaternion") 48 | 49 | #get the quaternion rotation channels... 50 | wchan = fcurves.find(dpath, index=0).keyframe_points 51 | xchan = fcurves.find(dpath, index=1).keyframe_points 52 | ychan = fcurves.find(dpath, index=2).keyframe_points 53 | zchan = fcurves.find(dpath, index=3).keyframe_points 54 | 55 | #and flip the channels based on armature origin 56 | index = 0 57 | while index < len(ychan): 58 | quaternion_axis_swapped = Quaternion(( 59 | -wchan[index].co[1], 60 | -ychan[index].co[1], 61 | xchan[index].co[1], 62 | zchan[index].co[1])) 63 | 64 | wchan[index].co[1] = quaternion_axis_swapped.w 65 | xchan[index].co[1] = quaternion_axis_swapped.x 66 | ychan[index].co[1] = quaternion_axis_swapped.y 67 | zchan[index].co[1] = quaternion_axis_swapped.z 68 | #print(quaternion_axis_swapped) 69 | index += 1 70 | 71 | flip_animation('Left elbow') 72 | flip_animation('Right elbow') 73 | 74 | class import_animation(bpy.types.Operator): 75 | bl_idname = "kkb.importanimation" 76 | bl_label = "Import .fbx animation" 77 | bl_description = "Imports a KK animation (.fbx format) and applies it to your character" 78 | bl_options = {'REGISTER', 'UNDO'} 79 | 80 | filepath : StringProperty(maxlen=1024, default='', options={'HIDDEN'}) 81 | filter_glob : StringProperty(default='*.fbx', options={'HIDDEN'}) 82 | 83 | def execute(self, context): 84 | scene = context.scene.kkbp 85 | use_rokoko_plugin = scene.rokoko_bool 86 | 87 | #import the fbx animation from the file dialog 88 | bpy.ops.import_scene.fbx(filepath=str(self.filepath), use_prepost_rot=False, global_scale=96) 89 | animation_armature = bpy.data.objects['Armature.001'] 90 | animation_armature.name = 'Animation Armature' 91 | 92 | # if the character armature has renamed bones, the armature is the modified armature type 93 | armature = bpy.data.objects['Armature'] 94 | if armature.data.bones.get('Left elbow'): 95 | rename_bones_for_clarity('animation') 96 | modify_animation_armature() 97 | apply_animation() 98 | correct_animation() 99 | 100 | #else, the armature is the vanilla armature type 101 | #apply the animation without modifications 102 | else: 103 | #if the user is using the rokoko plugin, use that to apply the animation 104 | #taken from https://github.com/FlailingFog/KK-Blender-Shader-Pack/issues/29 105 | if use_rokoko_plugin: 106 | bpy.data.scenes[0].rsl_retargeting_armature_target = armature 107 | bpy.data.scenes[0].rsl_retargeting_armature_source = animation_armature 108 | bpy.ops.rsl.build_bone_list() 109 | bpy.ops.rsl.retarget_animation() 110 | else: 111 | apply_animation() 112 | 113 | #mute all IKs and rotation locks 114 | armature = bpy.data.objects['Armature'] 115 | for bone in armature.pose.bones: 116 | bonesWithConstraints = [constraint for constraint in bone.constraints if constraint.type == 'IK' or constraint.type == 'COPY_ROTATION'] 117 | for constraint in bonesWithConstraints: 118 | constraint.mute = True 119 | 120 | #delete the animation armature and clean up orphan data 121 | bpy.data.objects.remove(bpy.data.objects['Animation Armature']) 122 | for block in bpy.data.armatures: 123 | if block.users == 0 and not block.use_fake_user: 124 | bpy.data.armatures.remove(block) 125 | 126 | return {'FINISHED'} 127 | 128 | def invoke(self, context, event): 129 | context.window_manager.fileselect_add(self) 130 | return {'RUNNING_MODAL'} 131 | 132 | if __name__ == "__main__": 133 | bpy.utils.register_class(import_animation) 134 | 135 | # test call 136 | print((bpy.ops.kkb.importanimation('INVOKE_DEFAULT'))) -------------------------------------------------------------------------------- /interface/dictionary_en.py: -------------------------------------------------------------------------------- 1 | translation_dictionary = { 2 | 'bake_mult' : 'Bake multiplier:', 3 | 'bake_mult_tt' : "Set this to 2 or 3 if the baked texture is blurry", 4 | 5 | 'seams' : "Fix body seams", 6 | 'seams_tt' : 'This performs a "remove doubles" operation on the body materials. Removing doubles also screws with the weights around certain areas. Disabling this will preserve the weights but may cause seams to appear around the neck and down the chest', 7 | 8 | 'outline' : 'Use single outline', 9 | 'outline_tt' : "Enable to use one generic outline material as opposed to using several unique ones. Checking this may cause outline transparency issues", 10 | 11 | 'keep_templates' : "Keep material templates", 12 | 'keep_templates_tt' : "Keep enabled to set the KKBP material templates to fake user. This will keep them from being deleted when blender is closed. Useful if you want to apply them to other objects after your character is finished", 13 | 14 | 'sfw_mode' : 'SFW mode', 15 | 'sfw_mode_tt' : 'Attempts to cover up some NSFW things', 16 | 17 | 'arm_drop' : "Armature type", 18 | 'arm_drop_A' : "Use KKBP Armature", 19 | 'arm_drop_A_tt' : "Use the KKBP armature. This will slightly modify the armature and give it basic IKs", 20 | 'arm_drop_B' : "Use Rigify Armature", 21 | 'arm_drop_B_tt' : "Use the Rigify armature. This is an advanced armature suitable for use in Blender", 22 | 'arm_drop_C' : "Use Koikatsu Armature", 23 | 'arm_drop_C_tt' : "Use the stock Koikatsu armature. This will match the bone naming and structure of the one in-game", 24 | 'arm_drop_D' : "Use PMX Armature", 25 | 'arm_drop_D_tt' : "Use the stock PMX armature. This is the armature you get from the KKBP exporter", 26 | 27 | 'cat_drop' : 'Run type', 28 | 'cat_drop_A' : "Don't pause to categorize", 29 | 'cat_drop_A_tt' : "Import everything and get a single object containing all your model's clothes. Hides any alternate clothes by default", 30 | 'cat_drop_B' : "Pause to categorize", 31 | 'cat_drop_B_tt' : "Import everything, but pause to manually separate the clothes into groups of objects. The hair must be separated and named \"Hair\" or \"hair\". When done separating, click the Finish categorization button to finish the import. Hides any alternate clothes by default", 32 | 'cat_drop_C' : "Automatically categorize", 33 | 'cat_drop_C_tt' : "Import everything and automatically separate every piece of clothing into several objects", 34 | 'cat_drop_D' : "Categorize by SMR Data", 35 | 'cat_drop_D_tt' : "Import everyting and automatically separate every object by it's Skinned Mesh Renderer. Note: This option is only for exporting meshes so it will not apply any material templates or colors", 36 | 37 | 'dark' : "Dark colors", 38 | 'dark_A' : "LUT Night", 39 | 'dark_A_tt' : "Makes the dark colors blue-ish", 40 | 'dark_B' : "LUT Sunset", 41 | 'dark_B_tt' : "Makes the dark colors red-ish", 42 | 'dark_C' : "LUT Day", 43 | 'dark_C_tt' : "Makes the dark colors the same as the light colors", 44 | 'dark_D' : "Saturation based", 45 | 'dark_D_tt' : "Makes the dark colors more saturated than the light ones", 46 | 'dark_E' : 'Value reduction', 47 | 'dark_E_tt' : "Makes the dark colors darker than the light ones", 48 | 'dark_F' : 'Experimental', 49 | 'dark_F_tt' : "Uses an experimental method to set the dark colors", 50 | 51 | 'prep_drop' : "Export type", 52 | 'prep_drop_A' : "Unity - VRM compatible", 53 | 'prep_drop_A_tt' : """Removes the outline and... 54 | removes duplicate Eyewhite material slot if present, 55 | edits bone hierarchy to allow Unity to automatically detect the right bones""", 56 | 'prep_drop_B' : "Generic FBX - No changes", 57 | 'prep_drop_B_tt' : """Removes the outline and... 58 | removes duplicate Eyewhite material slot if present""", 59 | 60 | 'simp_drop' : 'Armature simplification type', 61 | 'simp_drop_A' : 'Very simple (SLOW)', 62 | 'simp_drop_A_tt': 'Use this option if you want a very low bone count. Moves the pupil bones to layer 1 and simplifies bones on armature layers 3-5, 11-12, and 17-19 (Leaves you with ~110 bones not counting the skirt bones)', 63 | 'simp_drop_B' : 'Simple', 64 | 'simp_drop_B_tt': 'Moves the pupil bones to layer 1 and simplifies the useless bones on armature layer 11 (Leaves you with ~1000 bones)', 65 | 'simp_drop_C' : 'No changes (FAST)', 66 | 'simp_drop_C_tt': 'Does not simplify anything', 67 | 68 | 'bake' : 'Bake material templates', 69 | 'bake_light' : "Light", 70 | 'bake_light_tt' : "Bake light version of all textures", 71 | 'bake_dark' : "Dark", 72 | 'bake_dark_tt' : "Bake dark version of all textures", 73 | 'bake_norm' : "Normal", 74 | 'bake_norm_tt' : "Bake normal version of all textures", 75 | 76 | 'shape_A' : 'Use KKBP shapekeys', 77 | 'shape_A_tt' : 'Rename and delete the old shapekeys. This will merge the shapekeys that are part of the same expression and delete the rest', 78 | 'shape_B' : "Save partial shapekeys", 79 | 'shape_B_tt' : "Save the partial shapekeys that are used to generate the KK shapekeys. These are useless on their own", 80 | 'shape_C' : "Skip modifying shapekeys", 81 | 'shape_C_tt' : "Use the stock Koikatsu shapekeys. This will not change the shapekeys in any way", 82 | 83 | 'atlas' : 'Atlas type', 84 | 85 | 'export_fbx' : 'Export FBX', 86 | 'export_fbx_tt' : 'Exports all visible objects as an fbx file. This is the same as the FBX export function in the File menu', 87 | 88 | 'import_export' : 'Importing and Exporting', 89 | 'import_model' : 'Import model', 90 | 'finish_cat' : 'Finish categorization', 91 | 'recalc_dark' : 'Recalculate dark colors', 92 | 'prep' : 'Prep for target application', 93 | 'apply_temp' : 'Switch baked templates', 94 | 95 | 'rigify_convert': "Convert for Rigify", 96 | 'sep_eye' : "Separate Eyes and Eyebrows", 97 | 98 | 'convert_image' : 'Convert image with KKBP', 99 | 100 | 'quick_import_tt' : "Imports a Koikatsu model (.pmx format) and applies fixes to it", 101 | 'mat_import_tt' : "Finish separating objects, apply the textures and colors", 102 | 'export_prep_tt' : "Check the dropdown for more info", 103 | 'bake_mats_tt' : "Open the folder you want to bake the material templates to", 104 | 'apply_mats_tt' : "Open the folder that contains the baked materials. Use the menu to load the Light / Dark / Normal passes", 105 | 'import_colors_tt' : "Open the folder containing your model.pmx file to recalculate the dark colors", 106 | 107 | } 108 | 109 | def t(text_entry): 110 | try: 111 | return translation_dictionary[text_entry] 112 | except: 113 | return '???' 114 | 115 | -------------------------------------------------------------------------------- /extras/switcharmature.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from mathutils import Vector 3 | from ..importing.bonedrivers import rename_bones_for_clarity 4 | from ..importing.finalizepmx import modify_pmx_armature 5 | from ..importing.finalizegrey import modify_fbx_armature 6 | from ..importing.importeverything import apply_bone_widgets 7 | 8 | class switch_armature(bpy.types.Operator): 9 | bl_idname = "kkb.switcharmature" 10 | bl_label = "Switch koikatsu armature type" 11 | bl_description = "Click this to switch between the vanilla koikatsu armature structure and the modified KKBP armature. Using this after you have animated a character will ruin the animation" 12 | bl_options = {'REGISTER', 'UNDO'} 13 | 14 | def execute(self, context): 15 | armature = bpy.data.objects['Armature'] 16 | 17 | #reset to T pose 18 | for bone in armature.pose.bones: 19 | bone.rotation_quaternion = (1,0,0,0) 20 | bone.scale = (1,1,1) 21 | bone.location = (0,0,0) 22 | 23 | #if the armature has already been modified, and there are drivers active, mute all drivers 24 | #this means the user wants to switch from modified to the stock armature 25 | if armature.data.bones.get('Left elbow'): 26 | print('switching from premodified to stock') 27 | for driver in armature.animation_data.drivers: 28 | driver.mute = True 29 | 30 | #then mute all constraints 31 | for bone in armature.pose.bones: 32 | bonesWithConstraints = [constraint for constraint in bone.constraints if constraint.type == 'IK' or constraint.type == 'COPY_ROTATION'] 33 | for constraint in bonesWithConstraints: 34 | constraint.mute = True 35 | 36 | #place the pv bones back in their original spot 37 | def reparent(bone,newparent): 38 | #refresh armature? 39 | bpy.ops.object.mode_set(mode='OBJECT') 40 | bpy.ops.object.mode_set(mode='EDIT') 41 | armature.data.edit_bones[bone].parent = armature.data.edit_bones[newparent] 42 | 43 | reparent('cf_pv_elbo_R', 'cf_pv_root') 44 | reparent('cf_pv_elbo_L', 'cf_pv_root') 45 | reparent('cf_pv_foot_R', 'cf_pv_root') 46 | reparent('cf_pv_foot_L', 'cf_pv_root') 47 | reparent('cf_pv_hand_R', 'cf_pv_root') 48 | reparent('cf_pv_hand_L', 'cf_pv_root') 49 | 50 | #stow the footIKs in there too 51 | reparent('MasterFootIK.R', 'cf_pv_root') 52 | reparent('MasterFootIK.L', 'cf_pv_root') 53 | 54 | #move the root and body bones back to where they're supposed to be 55 | armature.data.edit_bones['p_cf_body_bone'].parent = None 56 | reparent('cf_j_root', 'p_cf_body_bone') 57 | reparent('Center', 'cf_j_root') 58 | 59 | #then rename bones to match the stock armature 60 | rename_bones_for_clarity('stock') 61 | 62 | # reset the orientation for the leg/arm bones to stock 63 | # also move some head/tail positions back to counteract the 64 | # results of the finalizepmx relocate tail function 65 | height_adder = Vector((0,0,0.1)) 66 | 67 | def unorient(bone): 68 | if 'leg' in bone: 69 | armature.data.edit_bones[bone].head.y -= -.004 70 | elif 'hand' in bone: 71 | armature.data.edit_bones[bone].tail.z -= .01 72 | 73 | armature.data.edit_bones[bone].tail = armature.data.edit_bones[bone].head + height_adder 74 | armature.data.edit_bones[bone].roll = 0 75 | 76 | unorient_bones = [ 77 | 'cf_j_thigh00_R', 'cf_j_thigh00_L', 78 | 'cf_j_leg01_R', 'cf_j_leg01_L', 79 | 'cf_j_foot_R', 'cf_j_foot_L', 80 | 'cf_j_forearm01_R', 'cf_j_forearm01_L', 81 | 'cf_d_bust00', 82 | 'cf_pv_hand_R', 'cf_pv_hand_L'] 83 | 84 | for bone in unorient_bones: 85 | unorient(bone) 86 | 87 | elif armature.data.bones.get('MasterFootIK.R'): 88 | #if the armature has already been modified, and the bones are not renamed, revert changes made above 89 | # this means the user wants to switch from stock to the premodified armature 90 | print('switching from stock to premodified') 91 | for driver in armature.animation_data.drivers: 92 | driver.mute = False 93 | 94 | #then unmute all constraints 95 | for bone in armature.pose.bones: 96 | bonesWithConstraints = [constraint for constraint in bone.constraints if constraint.type == 'IK' or constraint.type == 'COPY_ROTATION'] 97 | for constraint in bonesWithConstraints: 98 | constraint.mute = False 99 | 100 | #place the pv bones in their modified spots 101 | def reparent(bone,newparent): 102 | #refresh armature? 103 | bpy.ops.object.mode_set(mode='OBJECT') 104 | bpy.ops.object.mode_set(mode='EDIT') 105 | armature.data.edit_bones[bone].parent = armature.data.edit_bones[newparent] 106 | 107 | reparent('cf_pv_elbo_R', 'cf_pv_root_upper') 108 | reparent('cf_pv_elbo_L', 'cf_pv_root_upper') 109 | reparent('cf_pv_foot_R', 'MasterFootIK.R') 110 | reparent('cf_pv_foot_L', 'MasterFootIK.L') 111 | reparent('cf_pv_hand_R', 'cf_n_height') 112 | reparent('cf_pv_hand_L', 'cf_n_height') 113 | 114 | #unstow the foot IKs 115 | reparent('MasterFootIK.R', 'cf_n_height') 116 | reparent('MasterFootIK.L', 'cf_n_height') 117 | 118 | #move the root and body bones back to where they're supposed to be 119 | #armature.data.edit_bones['cf_n_height'].parent = None 120 | reparent('cf_j_root', 'cf_pv_root') 121 | reparent('p_cf_body_bone', 'cf_pv_root') 122 | 123 | #then modify the bone names back and set the orientations for IKs 124 | modify_pmx_armature() 125 | 126 | rename_bones_for_clarity('modified') 127 | 128 | else: 129 | #if the armature has renamed bones, and it was never modified and this is a stock armature 130 | #this means the user wants to switch to the modified armature 131 | print('switching from stock to modified for the first time') 132 | modify_pmx_armature() 133 | 134 | armature.hide = False 135 | scene = context.scene.kkbp 136 | scene.armature_edit_bool = True 137 | bpy.ops.kkb.bonedrivers('INVOKE_DEFAULT') 138 | bpy.ops.object.mode_set(mode='OBJECT') 139 | apply_bone_widgets() 140 | 141 | bpy.ops.object.mode_set(mode='OBJECT') 142 | 143 | return {'FINISHED'} 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /importing/importbuttons.py: -------------------------------------------------------------------------------- 1 | import bpy, time, traceback 2 | import os 3 | from bpy.props import StringProperty 4 | 5 | #load plugin language 6 | from bpy.app.translations import locale 7 | if locale == 'ja_JP': 8 | from ..interface.dictionary_jp import t 9 | else: 10 | from ..interface.dictionary_en import t 11 | 12 | def kklog(log_text, type = 'standard'): 13 | if not bpy.data.texts.get('KKBP Log'): 14 | bpy.data.texts.new(name='KKBP Log') 15 | if bpy.data.screens.get('Scripting'): 16 | for area in bpy.data.screens['Scripting'].areas: 17 | if area.type == 'TEXT_EDITOR': 18 | area.spaces[0].text = bpy.data.texts['KKBP Log'] 19 | 20 | if type == 'error': 21 | log_text = '\nError: ' + str(log_text) 22 | elif type == 'warn': 23 | log_text = 'Warning: ' + str(log_text) 24 | bpy.data.texts['KKBP Log'].write(str(log_text) + '\n') 25 | 26 | print(str(log_text)) 27 | 28 | def import_pmx_models(directory): 29 | 30 | for subdir, dirs, files in os.walk(directory): 31 | for file in files: 32 | if (file == 'model.pmx'): 33 | pmx_path = os.path.join(subdir, file) 34 | outfit = 'Outfit' in subdir 35 | 36 | #import the pmx file with mmd_tools 37 | bpy.ops.mmd_tools.import_model('EXEC_DEFAULT', 38 | files=[{'name': pmx_path}], 39 | directory=pmx_path, 40 | scale=1, 41 | types={'MESH', 'ARMATURE', 'MORPHS'} if not outfit else {'MESH'} , 42 | log_level='WARNING') 43 | 44 | if outfit: 45 | #keep track of outfit ID after pmx import. The active object is the empty after import, so that's where its going 46 | bpy.context.view_layer.objects.active['KKBP outfit ID'] = str(subdir[-2:]) 47 | 48 | #get rid of the text files mmd tools generates 49 | if bpy.data.texts.get('Model'): 50 | bpy.data.texts.remove(bpy.data.texts['Model']) 51 | bpy.data.texts.remove(bpy.data.texts['Model_e']) 52 | 53 | class quick_import(bpy.types.Operator): 54 | bl_idname = "kkb.quickimport" 55 | bl_label = "Import .pmx file" 56 | bl_description = t('quick_import_tt') 57 | bl_options = {'REGISTER', 'UNDO'} 58 | 59 | filepath : StringProperty(maxlen=1024, default='', options={'HIDDEN'}) 60 | filter_glob : StringProperty(default='*.pmx', options={'HIDDEN'}) 61 | 62 | def execute(self, context): 63 | #do this thing because cats does it 64 | if hasattr(context.scene, 'layers'): 65 | context.scene.layers[0] = True 66 | 67 | #delete the default scene if present 68 | if len(bpy.data.objects) == 3: 69 | for obj in ['Camera', 'Light', 'Cube']: 70 | if bpy.data.objects.get(obj): 71 | bpy.data.objects.remove(bpy.data.objects[obj]) 72 | 73 | #Set the view transform 74 | bpy.context.scene.view_settings.view_transform = 'Standard' 75 | 76 | #create KKLog 77 | kklog('==== KKBP Log ====') 78 | 79 | #save filepath for later 80 | context.scene.kkbp.import_dir = str(self.filepath)[:-9] 81 | 82 | #run commands based on selection 83 | if context.scene.kkbp.categorize_dropdown == 'A': #Automatic separation 84 | commands = [ 85 | import_pmx_models(context.scene.kkbp.import_dir), 86 | bpy.ops.kkb.finalizepmx('INVOKE_DEFAULT'), 87 | bpy.ops.kkb.shapekeys('INVOKE_DEFAULT'), 88 | bpy.ops.kkb.separatebody('INVOKE_DEFAULT'), 89 | bpy.ops.kkb.cleanarmature('INVOKE_DEFAULT'), 90 | bpy.ops.kkb.bonedrivers('INVOKE_DEFAULT'), 91 | bpy.ops.kkb.importeverything('INVOKE_DEFAULT'), 92 | bpy.ops.kkb.importcolors('EXEC_DEFAULT'), 93 | ] 94 | elif context.scene.kkbp.categorize_dropdown == 'B': #Manual separation 95 | commands = [ 96 | import_pmx_models(context.scene.kkbp.import_dir), 97 | bpy.ops.kkb.finalizepmx('INVOKE_DEFAULT'), 98 | bpy.ops.kkb.shapekeys('INVOKE_DEFAULT'), 99 | bpy.ops.kkb.separatebody('INVOKE_DEFAULT'), 100 | bpy.ops.kkb.cleanarmature('INVOKE_DEFAULT'), 101 | bpy.ops.kkb.bonedrivers('INVOKE_DEFAULT'), 102 | ] 103 | elif context.scene.kkbp.categorize_dropdown == 'C': #Separate every piece 104 | commands = [ 105 | import_pmx_models(context.scene.kkbp.import_dir), 106 | bpy.ops.kkb.finalizepmx('INVOKE_DEFAULT'), 107 | bpy.ops.kkb.shapekeys('INVOKE_DEFAULT'), 108 | bpy.ops.kkb.separatebody('INVOKE_DEFAULT'), 109 | bpy.ops.kkb.cleanarmature('INVOKE_DEFAULT'), 110 | bpy.ops.kkb.separatemeshes('EXEC_DEFAULT'), 111 | bpy.ops.kkb.bonedrivers('INVOKE_DEFAULT'), 112 | bpy.ops.kkb.importeverything('INVOKE_DEFAULT'), 113 | bpy.ops.kkb.importcolors('EXEC_DEFAULT'), 114 | ] 115 | else: #SMR pipeline 116 | commands = [ 117 | import_pmx_models(context.scene.kkbp.import_dir), 118 | bpy.ops.kkb.finalizepmx('INVOKE_DEFAULT'), 119 | bpy.ops.kkb.shapekeys('INVOKE_DEFAULT'), 120 | bpy.ops.kkb.separatebody('INVOKE_DEFAULT'), 121 | bpy.ops.kkb.cleanarmature('INVOKE_DEFAULT'), 122 | bpy.ops.kkb.separatemeshes('INVOKE_DEFAULT'), 123 | ] 124 | 125 | #run commands based on selection, and show progress bar 126 | #wm = bpy.context.window_manager 127 | #wm.progress_begin(0, len(commands)) 128 | for i, command in enumerate(commands): 129 | command 130 | #wm.progress_update(i) 131 | #wm.progress_end() 132 | 133 | if context.scene.kkbp.armature_dropdown == 'B' and context.scene.kkbp.categorize_dropdown in ['A', 'B', 'C']: 134 | bpy.ops.kkb.rigifyconvert('INVOKE_DEFAULT') 135 | 136 | if context.scene.kkbp.categorize_dropdown in ['A', 'B', 'C']: 137 | #set the viewport shading 138 | my_areas = bpy.context.workspace.screens[0].areas 139 | my_shading = 'MATERIAL' # 'WIREFRAME' 'SOLID' 'MATERIAL' 'RENDERED' 140 | 141 | for area in my_areas: 142 | for space in area.spaces: 143 | if space.type == 'VIEW_3D': 144 | space.shading.type = my_shading 145 | 146 | return {'FINISHED'} 147 | 148 | def invoke(self, context, event): 149 | context.window_manager.fileselect_add(self) 150 | return {'RUNNING_MODAL'} 151 | 152 | class mat_import(bpy.types.Operator): 153 | bl_idname = "kkb.matimport" 154 | bl_label = "Load textures and colors" 155 | bl_description = t('mat_import_tt') 156 | bl_options = {'REGISTER', 'UNDO'} 157 | 158 | def execute(self, context): 159 | 160 | # run bone drivers after 161 | if bpy.context.scene.kkbp.categorize_dropdown == 'D': 162 | bpy.ops.kkb.bonedrivers('INVOKE_DEFAULT') 163 | 164 | bpy.ops.kkb.importeverything('INVOKE_DEFAULT') 165 | bpy.ops.kkb.importcolors('EXEC_DEFAULT') 166 | 167 | if context.scene.kkbp.armature_dropdown == 'B' and context.scene.kkbp.categorize_dropdown in ['A', 'B', 'C']: 168 | bpy.ops.kkb.rigifyconvert('INVOKE_DEFAULT') 169 | 170 | return {'FINISHED'} 171 | 172 | -------------------------------------------------------------------------------- /exporting/applymaterials.py: -------------------------------------------------------------------------------- 1 | ''' 2 | APPLY MATERIALS FOR ATLAS GENERATION 3 | - Replaces all materials with their baked textures 4 | - This allows the Material Combiner feature in CATS to recognize the baked textures 5 | 6 | Usage: 7 | - Select an object that has had its materials baked 8 | - Select the folder that holds the textures in the Output Properties tab 9 | - Run script 10 | ''' 11 | 12 | import bpy, os, traceback 13 | from ..importing.importbuttons import kklog 14 | from pathlib import Path 15 | from.bakematerials import sanitizeMaterialName, showError 16 | 17 | from bpy.props import StringProperty, BoolProperty 18 | from bpy_extras.io_utils import ImportHelper 19 | from bpy.types import Operator 20 | 21 | #load plugin language 22 | from bpy.app.translations import locale 23 | if locale == 'ja_JP': 24 | from ..interface.dictionary_jp import t 25 | else: 26 | from ..interface.dictionary_en import t 27 | 28 | def create_atlas_helpers(): 29 | object = bpy.context.active_object 30 | for matslot in object.material_slots: 31 | 32 | material = matslot.material 33 | nodes = material.node_tree.nodes 34 | links = material.node_tree.links 35 | #print(matname) 36 | if not nodes.get('KK export'): 37 | #get output node 38 | outputNode = nodes['Material Output'] 39 | 40 | #make mix node for image transparency and track the state of the image file 41 | transpMix = nodes.new('ShaderNodeMixShader') 42 | transpMix.location = outputNode.location[0], outputNode.location[1] - 300 43 | transpMix.name = 'KK export' 44 | 45 | #make transparency node 46 | transpNode = nodes.new('ShaderNodeBsdfTransparent') 47 | transpNode.location = transpMix.location[0] - 300, transpMix.location[1] 48 | 49 | #make emissive node 50 | emissiveNode = nodes.new('ShaderNodeEmission') 51 | emissiveNode.location = transpMix.location[0], transpMix.location[1] - 300 52 | #Emissive node must be named 'Emission' or Material Combiner will fail 53 | try: 54 | nodes['Emission'].name = 'renamed for export' 55 | emissiveNode.name = 'Emission' 56 | except: 57 | #image is the only image in the current view 58 | pass 59 | 60 | #make image node 61 | imageNode = nodes.new('ShaderNodeTexImage') 62 | imageNode.location = emissiveNode.location[0]-300, emissiveNode.location[1] 63 | #Image node must be named 'Image Texture' or Material Combiner will fail 64 | try: 65 | nodes['Image Texture'].name = 'renamed for export' 66 | imageNode.name = 'Image Texture' 67 | except: 68 | #image is the only image in the current view 69 | pass 70 | 71 | #link the image node to the emissive node, and send it through the transparency mix shader 72 | links.new(imageNode.outputs[0], emissiveNode.inputs[0]) 73 | links.new(imageNode.outputs[1], transpMix.inputs[0]) 74 | 75 | links.new(transpNode.outputs[0], transpMix.inputs[1]) 76 | links.new(emissiveNode.outputs[0], transpMix.inputs[2]) 77 | 78 | #Then send that through the main mix shader 79 | mainMix = nodes.new('ShaderNodeMixShader') 80 | mainMix.name = 'KK Mix' 81 | mainMix.location = outputNode.location 82 | outputNode.location = outputNode.location[0] + 300, outputNode.location[1] 83 | links.new(transpMix.outputs[0], mainMix.inputs[2]) 84 | 85 | #make the node currently plugged into the output node go through the mix shader 86 | links.new(outputNode.inputs[0].links[0].from_node.outputs[0], mainMix.inputs[1]) 87 | links.new(mainMix.outputs[0], outputNode.inputs[0]) 88 | 89 | #set the mix shader's factor to 1 so the baked image is showing instead of the material 90 | mainMix.inputs[0].default_value=1 91 | 92 | def replace_images(folderpath, apply_type): 93 | fileList = Path(folderpath).glob('*.*') 94 | files = [file for file in fileList if file.is_file()] 95 | 96 | object = bpy.context.active_object 97 | for matslot in object.material_slots: 98 | material = matslot.material 99 | nodes = material.node_tree.nodes 100 | matname = sanitizeMaterialName(material.name) 101 | #print(matname) 102 | 103 | #Check if there's any images for this material 104 | #if there's no matching images, skip to the next material 105 | if apply_type == 'A': 106 | currentImage = [file.name for file in files if (matname in file.name and 'light' in file.name)] 107 | elif apply_type == 'B': 108 | currentImage = [file.name for file in files if (matname in file.name and 'dark' in file.name)] 109 | else: 110 | currentImage = [file.name for file in files if (matname in file.name and 'normal' in file.name)] 111 | if not currentImage: 112 | kklog("No {} baked image found for {}".format('light' if apply_type == 'A' else 'dark' if apply_type == 'B' else 'normal', matname)) 113 | continue 114 | 115 | imageName = currentImage[0] 116 | imagePath = folderpath + imageName 117 | 118 | #load the image into the image node 119 | transpMix = nodes['KK export'] 120 | imageName = currentImage[0] 121 | imagePath = folderpath + imageName 122 | 123 | bpy.ops.image.open(filepath=imagePath, use_udim_detecting=False) 124 | bpy.data.images[imageName].pack() 125 | 126 | imageNode = transpMix.inputs[0].links[0].from_node 127 | imageNode.image = bpy.data.images[imageName] 128 | 129 | nodes['KK Mix'].inputs[0].default_value = 1 130 | 131 | class apply_materials(bpy.types.Operator): 132 | bl_idname = "kkb.applymaterials" 133 | bl_label = "Open baked materials folder" 134 | bl_description = t('apply_mats_tt') 135 | bl_options = {'REGISTER', 'UNDO'} 136 | 137 | directory : StringProperty(maxlen=1024, default='', subtype='FILE_PATH', options={'HIDDEN'}) 138 | filter_glob : StringProperty(default='', options={'HIDDEN'}) 139 | data = None 140 | mats_uv = None 141 | structure = None 142 | 143 | def execute(self, context): 144 | try: 145 | scene = context.scene.kkbp 146 | apply_type = scene.atlas_dropdown 147 | 148 | #Get all files from the exported texture folder 149 | folderpath = scene.import_dir if scene.import_dir != 'cleared' else self.directory #if applymaterials is run right after bake, use import dir as a temp directory holder 150 | scene.import_dir == 'cleared' 151 | 152 | for ob in [obj for obj in bpy.context.view_layer.objects if obj.type == 'MESH']: 153 | bpy.ops.object.select_all(action='DESELECT') 154 | bpy.context.view_layer.objects.active = ob 155 | ob.select_set(True) 156 | create_atlas_helpers() 157 | replace_images(folderpath, apply_type) 158 | 159 | return {'FINISHED'} 160 | 161 | except: 162 | kklog('Unknown python error occurred', type = 'error') 163 | kklog(traceback.format_exc()) 164 | self.report({'ERROR'}, traceback.format_exc()) 165 | return {"CANCELLED"} 166 | 167 | def invoke(self, context, event): 168 | context.window_manager.fileselect_add(self) 169 | return {'RUNNING_MODAL'} 170 | 171 | if __name__ == "__main__": 172 | bpy.utils.register_class(apply_materials) 173 | 174 | # test call 175 | print((bpy.ops.kkb.applymaterials('INVOKE_DEFAULT'))) 176 | -------------------------------------------------------------------------------- /importing/finalizegrey.py: -------------------------------------------------------------------------------- 1 | #Finalize the accessory placements 2 | 3 | import bpy, math 4 | from mathutils import Vector 5 | 6 | def finalize(): 7 | bpy.ops.object.mode_set(mode='OBJECT') 8 | armature = bpy.data.objects['Armature'] 9 | 10 | def return_child(parent): 11 | try: 12 | new_parent = parent.children[0] 13 | return return_child(new_parent) 14 | except: 15 | return parent 16 | 17 | #go through the empties that have children 18 | for empty in [ob for ob in bpy.data.objects if ob.type == 'EMPTY' and len(ob.children) > 0]: 19 | #get the last child of the empty and check if it's a mesh (accessory object) 20 | if empty.parent_bone != None and empty.parent_bone != '': 21 | empty_name = empty.name 22 | empty_parent = bpy.data.objects[empty_name].parent_bone 23 | last_child = return_child(empty) 24 | if last_child.type == 'MESH': 25 | 26 | #unparent the mesh (accessory) and keep location 27 | bpy.ops.object.select_all(action='DESELECT') 28 | bpy.context.view_layer.objects.active = last_child 29 | last_child.select_set(True) 30 | #empty.select_set(True) 31 | bone_location = empty.location 32 | bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM') 33 | 34 | #create a bone on the armature for this accessory 35 | bpy.context.view_layer.objects.active = armature 36 | bpy.ops.object.mode_set(mode='EDIT') 37 | 38 | height_adder = Vector((0,0.1,0)) 39 | 40 | new_bone = armature.data.edit_bones.new(last_child.name) 41 | new_bone.head = armature.data.edit_bones[empty_parent].head + bone_location 42 | new_bone.tail = armature.data.edit_bones[empty_parent].head + bone_location + height_adder 43 | new_bone.parent = armature.data.edit_bones[empty_parent] 44 | 45 | #parent the accessory to the new armature bone 46 | bone = new_bone.name 47 | bpy.ops.object.mode_set(mode='POSE') 48 | 49 | bpy.ops.pose.select_all(action='DESELECT') 50 | armature.data.bones.active = armature.data.bones[bone] 51 | armature.data.bones[bone].select = True 52 | armature.data.bones[bone].select_head = True 53 | armature.data.bones[bone].select_tail = True 54 | bpy.ops.object.parent_set(type='BONE_RELATIVE') 55 | bpy.ops.object.mode_set(mode='OBJECT') 56 | 57 | #make sure the accessory has a vertex group for the bone when it's merged 58 | vertex_group = last_child.vertex_groups.new(name=bone) 59 | verticesToAdd = [] 60 | for vertex in last_child.data.vertices: 61 | verticesToAdd.append(vertex.index) 62 | vertex_group.add(verticesToAdd, 1.0, 'ADD') 63 | 64 | #delete the empty 65 | bpy.ops.object.select_all(action='DESELECT') 66 | empty.select_set(True) 67 | bpy.ops.object.delete(use_global=False, confirm=False) 68 | 69 | else: 70 | #delete the empty 71 | bpy.ops.object.select_all(action='DESELECT') 72 | empty.select_set(True) 73 | bpy.ops.object.delete(use_global=False, confirm=False) 74 | 75 | #delete the rest of the empties 76 | bpy.ops.object.select_all(action='DESELECT') 77 | for empty in [ob for ob in bpy.data.objects if ob.type == 'EMPTY']: 78 | empty.select_set(True) 79 | bpy.ops.object.delete(use_global=False, confirm=False) 80 | 81 | #then merge all objects to the face object since that one seems to work 82 | bpy.ops.object.select_all(action='DESELECT') 83 | for mesh in [ob for ob in bpy.data.objects if ob.type == 'MESH']: 84 | mesh.select_set(True) 85 | bpy.context.view_layer.objects.active = bpy.data.objects['cf_O_face'] 86 | bpy.ops.object.join() 87 | bpy.context.view_layer.objects.active.name = 'Body' 88 | bpy.context.scene.cursor.location = (0.0, 0.0, 0.0) 89 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN') 90 | 91 | #modify armature to match KK armature 92 | armature = bpy.data.objects['Armature'] 93 | armature.select_set(True) 94 | bpy.context.view_layer.objects.active=armature 95 | bpy.ops.object.mode_set(mode='EDIT') 96 | 97 | #reparent these to match the KK armature 98 | armature.data.edit_bones['p_cf_body_bone'].parent = None 99 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) #??? 100 | armature.data.edit_bones.remove(armature.data.edit_bones['BodyTop']) 101 | armature.data.edit_bones['cf_j_foot_R'].parent = armature.data.edit_bones['cf_j_leg03_R'] 102 | armature.data.edit_bones['cf_j_foot_L'].parent = armature.data.edit_bones['cf_j_leg03_L'] 103 | 104 | #remove the cf_hit_head bone because the bone location isn't needed anymore 105 | armature.data.edit_bones.remove(armature.data.edit_bones['cf_hit_head']) 106 | 107 | #give the new eye bones the correct parents 108 | armature.data.edit_bones['Eyesx'].parent = armature.data.edit_bones['cf_j_head'] 109 | armature.data.edit_bones['N_EyesLookTargetP'].parent = armature.data.edit_bones['cf_j_head'] 110 | 111 | #unlock the armature and all bones 112 | armature.lock_location = [False, False, False] 113 | armature.lock_rotation = [False, False, False] 114 | armature.lock_scale = [False, False, False] 115 | 116 | for bone in armature.pose.bones: 117 | bone.lock_location = [False, False, False] 118 | 119 | def modify_fbx_armature(): 120 | armature = bpy.data.objects['Armature'] 121 | armature.select_set(True) 122 | bpy.context.view_layer.objects.active=armature 123 | 124 | #move armature bones that didn't have animation data up a level 125 | bpy.ops.object.mode_set(mode='EDIT') 126 | armature.data.edit_bones['cf_n_height'].parent = None 127 | armature.data.edit_bones['cf_j_root'].parent = armature.data.edit_bones['cf_n_height'] 128 | armature.data.edit_bones['cf_pv_root'].parent = armature.data.edit_bones['cf_n_height'] 129 | 130 | #store unused bones under the pv_root bone 131 | armature.data.edit_bones['cf_j_root'].parent = armature.data.edit_bones['cf_pv_root'] 132 | armature.data.edit_bones['p_cf_body_bone'].parent = armature.data.edit_bones['cf_pv_root'] 133 | 134 | #relocate the tail of some bones to make IKs easier 135 | #this is different from the one in finalizepmx.py 136 | def relocate_tail(bone1, bone2, direction): 137 | if direction == 'leg': 138 | armature.data.edit_bones[bone1].tail.y = armature.data.edit_bones[bone2].head.y 139 | #move the bone forward a bit or the ik bones won't bend correctly 140 | armature.data.edit_bones[bone1].head.z += 0.002 141 | #armature.data.edit_bones[bone1].tail.z += -0.002 142 | armature.data.edit_bones[bone1].roll = 0 143 | elif direction == 'arm': 144 | armature.data.edit_bones[bone1].tail.x = armature.data.edit_bones[bone2].head.x 145 | armature.data.edit_bones[bone1].tail.y = armature.data.edit_bones[bone2].head.y 146 | #move the bone back a bit or the ik bones won't bend correctly 147 | armature.data.edit_bones[bone1].head.z += -0.002 148 | armature.data.edit_bones[bone1].roll = -math.pi/2 149 | elif direction == 'hand': 150 | armature.data.edit_bones[bone1].tail = armature.data.edit_bones[bone2].tail 151 | armature.data.edit_bones[bone1].tail.y += .01 152 | armature.data.edit_bones[bone1].head = armature.data.edit_bones[bone2].head 153 | else: 154 | armature.data.edit_bones[bone1].tail.z = armature.data.edit_bones[bone2].head.z 155 | armature.data.edit_bones[bone1].tail.y = armature.data.edit_bones[bone2].head.y 156 | armature.data.edit_bones[bone1].roll = 0 157 | 158 | relocate_tail('cf_j_leg01_R', 'cf_j_foot_R', 'leg') 159 | relocate_tail('cf_j_leg01_R', 'cf_j_toes_R', 'foot') 160 | relocate_tail('cf_j_forearm01_R', 'cf_j_hand_R', 'arm') 161 | relocate_tail('cf_pv_hand_R', 'cf_j_hand_R', 'hand') 162 | 163 | relocate_tail('cf_j_leg01_L', 'cf_j_foot_L', 'leg') 164 | relocate_tail('cf_j_leg01_L', 'cf_j_toes_L', 'foot') 165 | relocate_tail('cf_j_forearm01_L', 'cf_j_hand_L', 'arm') 166 | relocate_tail('cf_pv_hand_L', 'cf_j_hand_L', 'hand') 167 | 168 | bpy.ops.object.mode_set(mode='OBJECT') 169 | 170 | class finalize_grey(bpy.types.Operator): 171 | bl_idname = "kkb.finalizegrey" 172 | bl_label = "Finalize .fbx file" 173 | bl_description = "Finalize accessory placements and .fbx file" 174 | bl_options = {'REGISTER', 'UNDO'} 175 | 176 | def execute(self, context): 177 | 178 | scene = context.scene.kkbp 179 | modify_armature = scene.armature_edit_bool 180 | 181 | finalize() 182 | if modify_armature: 183 | modify_fbx_armature() 184 | 185 | #redraw the UI after each operation to let the user know the plugin is actually doing something 186 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) 187 | bpy.ops.kkb.shapekeys('INVOKE_DEFAULT') 188 | 189 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) 190 | bpy.ops.kkb.separatebody('INVOKE_DEFAULT') 191 | 192 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) 193 | bpy.ops.kkb.cleanarmature('INVOKE_DEFAULT') 194 | 195 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) 196 | bpy.ops.kkb.bonedrivers('INVOKE_DEFAULT') 197 | 198 | #Set the view transform 199 | bpy.context.scene.view_settings.view_transform = 'Standard' 200 | 201 | return {'FINISHED'} 202 | 203 | if __name__ == "__main__": 204 | bpy.utils.register_class(finalize_grey) 205 | 206 | # test call 207 | print((bpy.ops.kkb.finalizegrey('INVOKE_DEFAULT'))) -------------------------------------------------------------------------------- /importing/importgrey.py: -------------------------------------------------------------------------------- 1 | #Imports the fbx file from GME and performs a few fixes 2 | 3 | import bpy 4 | from mathutils import Vector 5 | 6 | from bpy.props import StringProperty 7 | 8 | def import_the_fbx(directory): 9 | 10 | #delete the cube, light and camera 11 | if len(bpy.data.objects) == 3: 12 | for obj in bpy.data.objects: 13 | bpy.data.objects.remove(obj) 14 | 15 | #import the fbx file 16 | bpy.ops.import_scene.fbx(filepath=str(directory), use_prepost_rot=False, global_scale=96) 17 | 18 | #make sure the following objects don't have a .001 tag after being imported 19 | objects = [ 20 | 'cf_Ohitomi_L', 21 | 'cf_Ohitomi_R', 22 | 'cf_O_eyeline', 23 | 'cf_O_eyeline_low', 24 | 'cf_O_face', 25 | 'cf_O_tooth', 26 | 'cf_O_noseline', 27 | 'o_tang', 28 | 'cf_O_mayuge', 29 | 'o_body_a'] 30 | 31 | for object in objects: 32 | if bpy.data.objects.get(object + '.001'): 33 | bpy.data.objects[object + '.001'].name = object 34 | 35 | #rename all the shapekeys to be compatible with the other script 36 | #rename face shapekeys based on category 37 | keyset = bpy.data.objects['cf_O_face'].data.shape_keys.name 38 | index = 0 39 | while index < len(bpy.data.shape_keys[keyset].key_blocks): 40 | key = bpy.data.shape_keys[keyset].key_blocks[index] 41 | 42 | #reset the key value 43 | key.value = 0 44 | 45 | #rename the key 46 | if index < 29: 47 | key.name = key.name.replace("f00", "eye_face.f00") 48 | else: 49 | key.name = key.name.replace("f00", "kuti_face.f00") 50 | key.name = key.name.replace('.001','') 51 | 52 | index+=1 53 | 54 | #rename nose shapekeys based on category 55 | keyset = bpy.data.objects['cf_O_noseline'].data.shape_keys.name 56 | index = 0 57 | while index < len(bpy.data.shape_keys[keyset].key_blocks): 58 | key = bpy.data.shape_keys[keyset].key_blocks[index] 59 | 60 | #reset the key value 61 | key.value = 0 62 | 63 | #rename the key 64 | if index < 26: 65 | key.name = key.name.replace("nl00", "eye_nose.nl00") 66 | else: 67 | key.name = key.name.replace("nl00", "kuti_nose.nl00") 68 | key.name = key.name.replace('.001','') 69 | 70 | index+=1 71 | 72 | #rename the rest of the shapekeys 73 | def rename_keys(object): 74 | keyset = bpy.data.objects[object].data.shape_keys.name 75 | for key in bpy.data.shape_keys[keyset].key_blocks: 76 | key.value = 0 77 | key.name = key.name.replace("sL00", "eye_siroL.sL00") 78 | key.name = key.name.replace("sR00", "eye_siroR.sR00") 79 | key.name = key.name.replace('elu00', "eye_line_u.elu00") 80 | key.name = key.name.replace('ell00', "eye_line_l.ell00") 81 | key.name = key.name.replace('ha00', "kuti_ha.ha00") 82 | key.name = key.name.replace('y00', "kuti_yaeba.y00") 83 | key.name = key.name.replace('t00', "kuti_sita.t00") 84 | key.name = key.name.replace('mayu00',"mayuge.mayu00") 85 | 86 | objects = [ 87 | 'cf_Ohitomi_L', 88 | 'cf_Ohitomi_R', 89 | 'cf_O_eyeline', 90 | 'cf_O_eyeline_low', 91 | #eyenaM? 92 | 'cf_O_tooth', 93 | #fangs? 94 | 'o_tang', 95 | 'cf_O_mayuge'] 96 | 97 | for object in objects: 98 | rename_keys(object) 99 | 100 | #reset rotations, scale and locations in pose mode for all bones 101 | armature = bpy.data.objects['Armature'] 102 | armature.show_in_front = True 103 | 104 | for bone in armature.pose.bones: 105 | bone.rotation_quaternion = (1,0,0,0) 106 | bone.scale = (1,1,1) 107 | bone.location = (0,0,0) 108 | 109 | #Hide all root bones 110 | if 'root' in bone.name: 111 | armature.data.bones[bone.name].hide = True 112 | 113 | #Hide the height bone 114 | armature.data.bones['cf_n_height'].hide = True 115 | 116 | #tag the armature with a bone to let the plugin distinguish between pmx/fbx origin 117 | bpy.context.view_layer.objects.active = armature 118 | bpy.ops.object.mode_set(mode='EDIT') 119 | new_bone = armature.data.edit_bones.new('Greybone') 120 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) 121 | new_bone.head = Vector((0,0,0)) 122 | new_bone.tail = Vector((0,0.1,0)) 123 | 124 | #create the missing armature bones 125 | missing_bones = [ 126 | 'cf_j_thumb04_L', 'cf_j_index04_L', 'cf_j_middle04_L', 'cf_j_ring04_L', 'cf_j_little04_L', 127 | 'cf_j_thumb04_R', 'cf_j_index04_R', 'cf_j_middle04_R', 'cf_j_ring04_R', 'cf_j_little04_R', 128 | 'cf_pv_root', 129 | 'cf_pv_elbo_L', 'cf_pv_elbo_R', 130 | 'cf_pv_foot_L', 'cf_pv_foot_R', 131 | 'cf_pv_hand_L', 'cf_pv_hand_R', 132 | 'cf_pv_heel_L', 'cf_pv_heel_R', 133 | 'cf_pv_knee_L', 'cf_pv_knee_R', 134 | 'N_EyesLookTargetP', 'cf_hit_head' 135 | ] 136 | 137 | height_adder = Vector((0,0.1,0)) 138 | for bone in missing_bones: 139 | empty_location = bpy.data.objects[bone].location 140 | new_bone = armature.data.edit_bones.new(bone) 141 | new_bone.head = empty_location 142 | new_bone.tail = empty_location + height_adder 143 | 144 | #then fix the cf hit_head location 145 | new_bone.head = armature.data.edit_bones['cf_s_head'].head + empty_location 146 | new_bone.tail = armature.data.edit_bones['cf_s_head'].head + height_adder + empty_location 147 | 148 | #then fix the wrist location 149 | for side in ['R','L']: 150 | armature.data.edit_bones['cf_pv_hand_'+side].head.z = armature.data.edit_bones['cf_j_forearm01_'+side].head.z 151 | armature.data.edit_bones['cf_pv_hand_'+side].tail.z = armature.data.edit_bones['cf_j_forearm01_'+side].tail.z 152 | armature.data.edit_bones['cf_s_hand_'+side].head.z = armature.data.edit_bones['cf_j_forearm01_'+side].head.z 153 | armature.data.edit_bones['cf_s_hand_'+side].tail.z = armature.data.edit_bones['cf_j_forearm01_'+side].tail.z 154 | armature.data.edit_bones['cf_d_hand_'+side].head.z = armature.data.edit_bones['cf_j_forearm01_'+side].head.z 155 | armature.data.edit_bones['cf_d_hand_'+side].tail.z = armature.data.edit_bones['cf_j_forearm01_'+side].tail.z 156 | armature.data.edit_bones['cf_j_hand_'+side].head.z = armature.data.edit_bones['cf_j_forearm01_'+side].head.z 157 | armature.data.edit_bones['cf_j_hand_'+side].tail.z = armature.data.edit_bones['cf_j_forearm01_'+side].tail.z 158 | 159 | #then recreate the eyex bone so I can use it for later 160 | new_bone = armature.data.edit_bones.new('Eyesx') 161 | new_bone.head = armature.data.edit_bones['cf_hit_head'].tail 162 | new_bone.head.y = new_bone.head.y + 0.05 163 | new_bone.tail = armature.data.edit_bones['cf_J_Mayu_R'].tail 164 | new_bone.tail.x = new_bone.head.x 165 | new_bone.tail.y = new_bone.head.y 166 | 167 | #then reparent the pv bones to the root bone 168 | armature.data.edit_bones['cf_pv_root'].parent = armature.data.edit_bones['cf_j_root'] 169 | armature.data.edit_bones['cf_pv_root'].head = armature.data.edit_bones['cf_j_root'].head 170 | armature.data.edit_bones['cf_pv_root'].tail = armature.data.edit_bones['cf_j_root'].tail 171 | pv_bones = [ 172 | 'Greybone', 173 | 'cf_pv_elbo_L', 'cf_pv_elbo_R', 174 | 'cf_pv_foot_L', 'cf_pv_foot_R', 175 | 'cf_pv_hand_L', 'cf_pv_hand_R', 176 | 'cf_pv_heel_L', 'cf_pv_heel_R', 177 | 'cf_pv_knee_L', 'cf_pv_knee_R'] 178 | for bone in pv_bones: 179 | armature.data.edit_bones[bone].parent = armature.data.edit_bones['cf_pv_root'] 180 | 181 | bpy.ops.object.mode_set(mode='OBJECT') 182 | 183 | #rename the first UV map to be consistent with the PMX file 184 | #The fbx file seems to contains the extra UV maps for blush, hair shine and nipple locations 185 | #but I'm not going to use these in the KK shader until pmx imports are dropped entirely 186 | #remove for now because it'll probably cause issues with material baking and atlas generation 187 | for obj in bpy.data.objects: 188 | if obj.type == 'MESH': 189 | if obj.data.uv_layers[0]: 190 | obj.data.uv_layers[0].name = 'UVMap' 191 | 192 | try: 193 | obj.data.uv_layers.remove(obj.data.uv_layers['uv2']) 194 | obj.data.uv_layers.remove(obj.data.uv_layers['uv3']) 195 | obj.data.uv_layers.remove(obj.data.uv_layers['uv4']) 196 | except: 197 | pass 198 | 199 | #manually scale armature down 200 | bpy.context.view_layer.objects.active = armature 201 | bpy.ops.object.mode_set(mode='EDIT') 202 | bpy.ops.armature.select_all(action='SELECT') 203 | armature.data.edit_bones['cf_n_height'].select=False 204 | armature.data.edit_bones['cf_n_height'].select_head=False 205 | armature.data.edit_bones['cf_n_height'].select_tail=False 206 | 207 | scale_multiplier = armature.data.edit_bones['cf_n_height'].tail.y / armature.data.edit_bones['cf_hit_head'].head.y 208 | bpy.context.scene.tool_settings.transform_pivot_point = 'CURSOR' 209 | bpy.context.scene.cursor.location = (0.0, 0.0, 0.0) 210 | 211 | for bone in bpy.context.selected_bones: 212 | bone.head *= (scale_multiplier * .9675) 213 | bone.tail *= (scale_multiplier * .9675) 214 | 215 | #Set the view transform 216 | bpy.context.scene.view_settings.view_transform = 'Standard' 217 | 218 | class import_grey(bpy.types.Operator): 219 | bl_idname = "kkb.importgrey" 220 | bl_label = "Import Grey's .fbx" 221 | bl_description = "Select the .fbx file from Grey's Mesh Exporter" 222 | bl_options = {'REGISTER', 'UNDO'} 223 | 224 | filepath : StringProperty(maxlen=1024, default='', options={'HIDDEN'}) 225 | filter_glob : StringProperty(default='*.fbx', options={'HIDDEN'}) 226 | 227 | def execute(self, context): 228 | 229 | import_the_fbx(self.filepath) 230 | 231 | return {'FINISHED'} 232 | 233 | def invoke(self, context, event): 234 | context.window_manager.fileselect_add(self) 235 | return {'RUNNING_MODAL'} 236 | 237 | if __name__ == "__main__": 238 | bpy.utils.register_class(import_grey) 239 | 240 | # test call 241 | print((bpy.ops.kkb.importgrey('INVOKE_DEFAULT'))) 242 | -------------------------------------------------------------------------------- /exporting/exportprep.py: -------------------------------------------------------------------------------- 1 | #simplfies bone count using the merge weights function in CATS 2 | 3 | import bpy, traceback, time 4 | from ..importing.importbuttons import kklog 5 | 6 | #load plugin language 7 | from bpy.app.translations import locale 8 | if locale == 'ja_JP': 9 | from ..interface.dictionary_jp import t 10 | else: 11 | from ..interface.dictionary_en import t 12 | 13 | def main(prep_type, simp_type): 14 | 15 | armature = bpy.data.objects['Armature'] 16 | 17 | kklog('\nPrepping for export...') 18 | bpy.ops.object.mode_set(mode='OBJECT') 19 | bpy.ops.object.select_all(action='DESELECT') 20 | 21 | #Assume hidden items are unused and move them to their own collection 22 | kklog('Moving unused objects to their own collection...') 23 | no_move_objects = ['Bonelyfans', 'Shadowcast', 'Hitboxes', 'Body', 'Armature'] 24 | for object in bpy.context.scene.objects: 25 | #print(object.name) 26 | move_this_one = object.name not in no_move_objects and 'Widget' not in object.name and object.hide 27 | if move_this_one: 28 | object.hide = False 29 | object.select_set(True) 30 | bpy.context.view_layer.objects.active=object 31 | if bpy.context.selected_objects: 32 | bpy.ops.object.move_to_collection(collection_index=0, is_new=True, new_collection_name='Unused clothing items') 33 | #hide the new collection 34 | try: 35 | bpy.context.scene.view_layers[0].active_layer_collection = bpy.context.view_layer.layer_collection.children['Unused clothing items'] 36 | bpy.context.scene.view_layers[0].active_layer_collection.exclude = True 37 | except: 38 | try: 39 | #maybe the collection is in the default Collection collection 40 | bpy.context.scene.view_layers[0].active_layer_collection = bpy.context.view_layer.layer_collection.children['Collection'].children['Unused clothing items'] 41 | bpy.context.scene.view_layers[0].active_layer_collection.exclude = True 42 | except: 43 | #maybe the collection is already hidden, or doesn't exist 44 | pass 45 | bpy.ops.object.select_all(action='DESELECT') 46 | body = bpy.data.objects['Body'] 47 | bpy.context.view_layer.objects.active=body 48 | body.select_set(True) 49 | 50 | kklog('Removing object outline modifier...') 51 | for ob in bpy.data.objects: 52 | if ob.modifiers.get('Outline Modifier'): 53 | ob.modifiers['Outline Modifier'].show_render = False 54 | ob.modifiers['Outline Modifier'].show_viewport = False 55 | 56 | #remove the second Template Eyewhite slot if there are two of the same name in a row 57 | eye_index = 0 58 | for mat_slot_index in range(len(body.material_slots)): 59 | if body.material_slots[mat_slot_index].name == 'KK Eyewhites (sirome)': 60 | index = mat_slot_index 61 | if body.material_slots[index].name == body.material_slots[index-1].name: 62 | body.active_material_index = index 63 | bpy.ops.object.material_slot_remove() 64 | 65 | #Select the armature and make it active 66 | bpy.ops.object.mode_set(mode='OBJECT') 67 | bpy.ops.object.select_all(action='DESELECT') 68 | bpy.data.objects['Armature'].hide_set(False) 69 | bpy.data.objects['Armature'].select_set(True) 70 | bpy.context.view_layer.objects.active=bpy.data.objects['Armature'] 71 | bpy.ops.object.mode_set(mode='POSE') 72 | 73 | #If simplifying the bones... 74 | if simp_type in ['A', 'B']: 75 | #show all bones on the armature 76 | allLayers = [True, True, True, True, True, True, True, True, 77 | True, True, True, True, True, True, True, True, 78 | True, True, True, True, True, True, True, True, 79 | True, True, True, True, True, True, True, True] 80 | bpy.data.objects['Armature'].data.layers = allLayers 81 | bpy.ops.pose.select_all(action='DESELECT') 82 | 83 | #Move pupil bones to layer 1 84 | armature = bpy.data.objects['Armature'] 85 | armature.data.bones['Left Eye'].layers[0] = True 86 | armature.data.bones['Left Eye'].layers[10] = False 87 | armature.data.bones['Right Eye'].layers[0] = True 88 | armature.data.bones['Right Eye'].layers[10] = False 89 | 90 | #Select bones on layer 11 91 | for bone in armature.data.bones: 92 | if bone.layers[10]==True: 93 | bone.select = True 94 | 95 | #if very simple selected, also get 3-5,12,17-19 96 | if simp_type in ['A']: 97 | for bone in armature.data.bones: 98 | select_bool = bone.layers[2] or bone.layers[3] or bone.layers[4] or bone.layers[11] or bone.layers[12] or bone.layers[16] or bone.layers[17] or bone.layers[18] 99 | if select_bool: 100 | bone.select = True 101 | 102 | kklog('Using the merge weights function in CATS to simplify bones...') 103 | bpy.ops.object.mode_set(mode='EDIT') 104 | bpy.ops.kkb.cats_merge_weights() 105 | 106 | #If exporting for VRM... 107 | if prep_type == 'A': 108 | kklog('Editing armature for VRM...') 109 | bpy.context.view_layer.objects.active=armature 110 | bpy.ops.object.mode_set(mode='EDIT') 111 | 112 | #Rearrange bones to match CATS output 113 | armature.data.edit_bones['Pelvis'].parent = None 114 | armature.data.edit_bones['Spine'].parent = armature.data.edit_bones['Pelvis'] 115 | armature.data.edit_bones['Hips'].name = 'dont need lol' 116 | armature.data.edit_bones['Pelvis'].name = 'Hips' 117 | armature.data.edit_bones['Left leg'].parent = armature.data.edit_bones['Hips'] 118 | armature.data.edit_bones['Right leg'].parent = armature.data.edit_bones['Hips'] 119 | armature.data.edit_bones['Left ankle'].parent = armature.data.edit_bones['Left knee'] 120 | armature.data.edit_bones['Right ankle'].parent = armature.data.edit_bones['Right knee'] 121 | armature.data.edit_bones['Left shoulder'].parent = armature.data.edit_bones['Upper Chest'] 122 | armature.data.edit_bones['Right shoulder'].parent = armature.data.edit_bones['Upper Chest'] 123 | armature.data.edit_bones.remove(armature.data.edit_bones['dont need lol']) 124 | 125 | bpy.ops.object.mode_set(mode='POSE') 126 | bpy.ops.pose.select_all(action='DESELECT') 127 | 128 | #Merge specific bones for unity rig autodetect 129 | armature = bpy.data.objects['Armature'] 130 | merge_these = ['cf_j_waist02', 'cf_s_waist01', 'cf_s_hand_L', 'cf_s_hand_R'] 131 | for bone in armature.data.bones: 132 | if bone.name in merge_these: 133 | bone.select = True 134 | 135 | bpy.ops.object.mode_set(mode='EDIT') 136 | bpy.ops.kkb.cats_merge_weights() 137 | 138 | #If exporting for MMD... 139 | if prep_type == 'C': 140 | #Create the empty 141 | bpy.ops.object.mode_set(mode='OBJECT') 142 | bpy.ops.object.empty_add(type='PLAIN_AXES', align='WORLD', location=(0, 0, 0)) 143 | empty = bpy.data.objects['Empty'] 144 | bpy.ops.object.select_all(action='DESELECT') 145 | armature.parent = empty 146 | bpy.context.view_layer.objects.active = armature 147 | 148 | #rename bones to stock 149 | if armature.data.bones.get('Center'): 150 | bpy.ops.kkb.switcharmature('INVOKE_DEFAULT') 151 | 152 | #then rename bones to japanese 153 | pmx_rename_dict = { 154 | '全ての親':'cf_n_height', 155 | 'センター':'cf_j_hips', 156 | '上半身':'cf_j_spine01', 157 | '上半身2':'cf_j_spine02', 158 | '上半身3':'cf_j_spine03', 159 | '首':'cf_j_neck', 160 | '頭':'cf_j_head', 161 | '両目':'Eyesx', 162 | '左目':'cf_J_hitomi_tx_L', 163 | '右目':'cf_J_hitomi_tx_R', 164 | '左腕':'cf_j_arm00_L', 165 | '右腕':'cf_j_arm00_R', 166 | '左ひじ':'cf_j_forearm01_L', 167 | '右ひじ':'cf_j_forearm01_R', 168 | '左肩':'cf_j_shoulder_L', 169 | '右肩':'cf_j_shoulder_R', 170 | '左手首':'cf_j_hand_L', 171 | '右手首':'cf_j_hand_R', 172 | '左親指0':'cf_j_thumb01_L', 173 | '左親指1':'cf_j_thumb02_L', 174 | '左親指2':'cf_j_thumb03_L', 175 | '左薬指1':'cf_j_ring01_L', 176 | '左薬指2':'cf_j_ring02_L', 177 | '左薬指3':'cf_j_ring03_L', 178 | '左中指1':'cf_j_middle01_L', 179 | '左中指2':'cf_j_middle02_L', 180 | '左中指3':'cf_j_middle03_L', 181 | '左小指1':'cf_j_little01_L', 182 | '左小指2':'cf_j_little02_L', 183 | '左小指3':'cf_j_little03_L', 184 | '左人指1':'cf_j_index01_L', 185 | '左人指2':'cf_j_index02_L', 186 | '左人指3':'cf_j_index03_L', 187 | '右親指0':'cf_j_thumb01_R', 188 | '右親指1':'cf_j_thumb02_R', 189 | '右親指2':'cf_j_thumb03_R', 190 | '右薬指1':'cf_j_ring01_R', 191 | '右薬指2':'cf_j_ring02_R', 192 | '右薬指3':'cf_j_ring03_R', 193 | '右中指1':'cf_j_middle01_R', 194 | '右中指2':'cf_j_middle02_R', 195 | '右中指3':'cf_j_middle03_R', 196 | '右小指1':'cf_j_little01_R', 197 | '右小指2':'cf_j_little02_R', 198 | '右小指3':'cf_j_little03_R', 199 | '右人指1':'cf_j_index01_R', 200 | '右人指2':'cf_j_index02_R', 201 | '右人指3':'cf_j_index03_R', 202 | '下半身':'cf_j_waist01', 203 | '左足':'cf_j_thigh00_L', 204 | '右足':'cf_j_thigh00_R', 205 | '左ひざ':'cf_j_leg01_L', 206 | '右ひざ':'cf_j_leg01_R', 207 | '左足首':'cf_j_leg03_L', 208 | '右足首':'cf_j_leg03_R', 209 | } 210 | 211 | for bone in pmx_rename_dict: 212 | armature.data.bones[pmx_rename_dict[bone]].name = bone 213 | 214 | #Rearrange bones to match a random pmx model I found 215 | bpy.ops.object.mode_set(mode='EDIT') 216 | armature.data.edit_bones['左肩'].parent = armature.data.edit_bones['上半身3'] 217 | armature.data.edit_bones['右肩'].parent = armature.data.edit_bones['上半身3'] 218 | armature.data.edit_bones['左足'].parent = armature.data.edit_bones['下半身'] 219 | armature.data.edit_bones['右足'].parent = armature.data.edit_bones['下半身'] 220 | 221 | #refresh the vertex groups? Bones will act as if they're detached if this isn't done 222 | body.vertex_groups.active=0 223 | 224 | kklog('Using CATS to simplify more bones for MMD...') 225 | 226 | #use mmd_tools to convert 227 | bpy.ops.mmd_tools.convert_to_mmd_model() 228 | 229 | bpy.ops.object.mode_set(mode='OBJECT') 230 | 231 | class export_prep(bpy.types.Operator): 232 | bl_idname = "kkb.selectbones" 233 | bl_label = "Prep for target application" 234 | bl_description = t('export_prep_tt') 235 | bl_options = {'REGISTER', 'UNDO'} 236 | 237 | def execute(self, context): 238 | scene = context.scene.kkbp 239 | prep_type = scene.prep_dropdown 240 | simp_type = scene.simp_dropdown 241 | last_step = time.time() 242 | kklog('Finished in ' + str(time.time() - last_step)[0:4] + 's') 243 | try: 244 | main(prep_type, simp_type) 245 | scene.is_prepped = True 246 | return {'FINISHED'} 247 | except: 248 | kklog('Unknown python error occurred', type = 'error') 249 | kklog(traceback.format_exc()) 250 | self.report({'ERROR'}, traceback.format_exc()) 251 | return {"CANCELLED"} 252 | 253 | 254 | if __name__ == "__main__": 255 | bpy.utils.register_class(export_prep) 256 | 257 | # test call 258 | print((bpy.ops.kkb.selectbones('INVOKE_DEFAULT'))) 259 | -------------------------------------------------------------------------------- /extras/separatemeshes.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import json 3 | from pathlib import Path 4 | from bpy.props import ( 5 | StringProperty, 6 | BoolProperty, 7 | FloatProperty, 8 | EnumProperty, 9 | ) 10 | from bpy_extras.io_utils import ( 11 | ImportHelper, 12 | ExportHelper, 13 | orientation_helper, 14 | path_reference_mode, 15 | axis_conversion, 16 | ) 17 | 18 | ########## ERRORS ########## 19 | def kk_folder_error(self, context): 20 | self.layout.label(text="Please make sure to open the folder that was exported. (Hint: go into the folder before confirming)") 21 | 22 | ########## FUNCTIONS ########## 23 | def checks(directory): 24 | # "Borrowed" some logic from importeverything.py :P 25 | file_list = Path(directory).glob('*.*') 26 | files = [file for file in file_list if file.is_file()] 27 | filtered_files = [] 28 | 29 | json_file_missing = True 30 | for file in files: 31 | if 'KK_SMRData.json' in str(file): 32 | json_file_path = str(file) 33 | json_file = open(json_file_path) 34 | json_file_missing = False 35 | 36 | if json_file_missing: 37 | bpy.context.window_manager.popup_menu(kk_folder_error, title="Error", icon='ERROR') 38 | return True 39 | 40 | return False 41 | 42 | def load_smr_data(directory): 43 | # "Borrowed" some logic from importeverything.py :P 44 | file_list = Path(directory).glob('*.*') 45 | files = [file for file in file_list if file.is_file()] 46 | 47 | for file in files: 48 | if 'KK_SMRData.json' in str(file): 49 | json_file_path = str(file) 50 | json_file = open(json_file_path) 51 | json_smr_data = json.load(json_file) 52 | 53 | separate_clothes(json_smr_data) 54 | if bpy.context.scene.kkbp.categorize_dropdown != 'C': 55 | separate_body(json_smr_data) 56 | 57 | def separate_clothes(json_smr_data): 58 | #loop over each outfit 59 | for obj in bpy.data.objects: 60 | if "Outfit" in obj.name and "Hair" not in obj.name: 61 | #Get Outfit and it's object data 62 | outfit = obj 63 | outfit_data = outfit.data 64 | 65 | materialname = outfit_data.materials[0].name 66 | use_template_name = "Template " in materialname 67 | 68 | #Pass 1: To make sure each material has a mesh 69 | #Select the Outfit object and remove it's unused material slots 70 | #try to make sure we are in object mode 71 | try: 72 | bpy.ops.object.mode_set(mode = 'OBJECT') 73 | except: 74 | pass 75 | bpy.ops.object.select_all(action='DESELECT') 76 | bpy.context.view_layer.objects.active = outfit 77 | outfit.select_set(True) 78 | bpy.ops.object.material_slot_remove_unused() 79 | 80 | #To keep track if a mesh has been separated already 81 | separated_meshes = [] 82 | 83 | #Loop over each renderer in KK_SMRData.json 84 | for row in json_smr_data: 85 | #Deselect everything 86 | #try to make sure we are in object mode 87 | try: 88 | bpy.ops.object.mode_set(mode = 'OBJECT') 89 | except: 90 | pass 91 | bpy.ops.object.select_all(action='DESELECT') 92 | bpy.context.view_layer.objects.active = outfit 93 | bpy.ops.object.mode_set(mode = 'EDIT') 94 | bpy.ops.mesh.select_all(action = 'DESELECT') 95 | 96 | #Loop over each renderer material and select it 97 | found_a_material = False 98 | for mat_name in row['SMRMaterialNames']: 99 | found_mat_idx = outfit_data.materials.find(("Template " if use_template_name else "") + mat_name) 100 | 101 | if found_mat_idx > -1 and mat_name not in separated_meshes: 102 | separated_meshes.append(mat_name) 103 | 104 | bpy.context.object.active_material_index = found_mat_idx 105 | bpy.ops.object.material_slot_select() 106 | found_a_material = True 107 | 108 | if not found_a_material: 109 | continue 110 | 111 | #Seperate to a new mesh 112 | bpy.ops.mesh.separate(type='SELECTED') 113 | 114 | #Remove unused materials from the new object and rename it to it's corresponding Renderer name 115 | bpy.ops.object.mode_set(mode = 'OBJECT') 116 | bpy.ops.object.material_slot_remove_unused() 117 | bpy.context.selected_objects[0].name = row['SMRName'] 118 | bpy.ops.object.mode_set(mode = 'OBJECT') 119 | bpy.ops.object.select_all(action='DESELECT') 120 | 121 | #Pass 2: Clean up 122 | #Select the Outfit object and remove it's unused material slots 123 | outfit.select_set(True) 124 | bpy.ops.object.material_slot_remove_unused() 125 | 126 | #delete the Outfit object if it has no materials left 127 | if len(outfit_data.materials) == 0: 128 | bpy.ops.object.delete() 129 | 130 | 131 | def separate_body(json_smr_data): 132 | body = bpy.data.objects['Body'] 133 | body_data = body.data 134 | 135 | use_template_name = "Template " in body_data.materials[0].name 136 | 137 | #If user has applied materials then don't separate body. 138 | if use_template_name: 139 | return 140 | 141 | body_obj_material_map = { 142 | 'cf_Ohitomi_L' : {'cf_m_sirome_00'}, 143 | 'cf_Ohitomi_R' : {'cf_m_sirome_00.001'}, 144 | 'cf_Ohitomi_L02' : {'cf_m_hitomi_00_cf_Ohitomi_L02'}, 145 | 'cf_Ohitomi_R02' : {'cf_m_hitomi_00_cf_Ohitomi_R02'}, 146 | 'cf_O_namida_L' : {'cf_m_namida_00'}, 147 | 'cf_O_namida_M' : {'cf_m_namida_00.001'}, 148 | 'cf_O_namida_S' : {'cf_m_namida_00.002'}, 149 | 'cf_O_gag_eye_00' : {'cf_m_gageye_00'}, 150 | 'cf_O_gag_eye_01' : {'cf_m_gageye_01'}, 151 | 'cf_O_gag_eye_02' : {'cf_m_gageye_02'}, 152 | 'cf_O_eyeline' : {'cf_m_eyeline_00_up','cf_m_eyeline_kage'}, 153 | 'cf_O_eyeline_low' : {'cf_m_eyeline_down'}, 154 | 'o_tang' : {'cf_m_tang'}, 155 | } 156 | 157 | #Pass 1: To make sure each material has a mesh 158 | #Select the Body object and remove it's unused material slots 159 | 160 | #try to make sure we are in object mode 161 | try: 162 | bpy.ops.object.mode_set(mode = 'OBJECT') 163 | except: 164 | pass 165 | 166 | bpy.ops.object.select_all(action='DESELECT') 167 | bpy.context.view_layer.objects.active = body 168 | body.select_set(True) 169 | bpy.ops.object.material_slot_remove_unused() 170 | 171 | #Loop over each renderer in KK_SMRData.json 172 | for row in json_smr_data: 173 | #Deselect everything 174 | bpy.ops.object.mode_set(mode = 'OBJECT') 175 | bpy.ops.object.select_all(action='DESELECT') 176 | bpy.context.view_layer.objects.active = body 177 | 178 | if row['SMRName'] not in body_obj_material_map: 179 | continue 180 | 181 | bpy.ops.object.mode_set(mode = 'EDIT') 182 | bpy.ops.mesh.select_all(action = 'DESELECT') 183 | 184 | #deal with tang 185 | if (row['SMRName'] == 'o_tang' and len(row['SMRBoneNames']) > 1): 186 | mat_name = 'cf_m_tang.001' 187 | found_mat_idx = body_data.materials.find(mat_name) 188 | 189 | if found_mat_idx == -1: 190 | continue 191 | 192 | bpy.context.object.active_material_index = found_mat_idx 193 | bpy.ops.object.material_slot_select() 194 | else: 195 | #Loop over each renderer material and select it 196 | found_a_material = False 197 | for mat_name in body_obj_material_map[row['SMRName']]: 198 | found_mat_idx = body_data.materials.find(("Template " if use_template_name else "") + mat_name) 199 | 200 | if found_mat_idx > -1: 201 | bpy.context.object.active_material_index = found_mat_idx 202 | bpy.ops.object.material_slot_select() 203 | found_a_material = True 204 | 205 | if not found_a_material: 206 | continue 207 | 208 | #Seperate to a new mesh 209 | bpy.ops.mesh.separate(type='SELECTED') 210 | 211 | #Remove unused materials from the new object and rename it to it's corresponding Renderer name 212 | bpy.ops.object.mode_set(mode = 'OBJECT') 213 | bpy.ops.object.material_slot_remove_unused() 214 | bpy.context.selected_objects[0].name = row['SMRName'] 215 | bpy.ops.object.mode_set(mode = 'OBJECT') 216 | bpy.ops.object.select_all(action='DESELECT') 217 | 218 | #Pass 2: Clean up 219 | #Select the Body object and remove it's unused material slots 220 | body.select_set(True) 221 | bpy.ops.object.material_slot_remove_unused() 222 | bpy.ops.object.select_all(action='DESELECT') 223 | 224 | 225 | class separate_meshes(bpy.types.Operator): 226 | bl_idname = "kkb.separatemeshes" 227 | bl_label = "Separate Meshes" 228 | bl_description = "Open the folder containing the KK_SMRData.json file" 229 | bl_options = {'REGISTER', 'UNDO'} 230 | 231 | directory : StringProperty(maxlen=1024, default='', subtype='FILE_PATH', options={'HIDDEN'}) 232 | filter_glob : StringProperty(default='', options={'HIDDEN'}) 233 | 234 | def execute(self, context): 235 | if self.directory == '': 236 | directory = context.scene.kkbp.import_dir 237 | for obj in bpy.data.objects: 238 | if obj.modifiers.get('Outline Modifier'): 239 | obj.modifiers['Outline Modifier'].show_render = False 240 | obj.modifiers['Outline Modifier'].show_viewport = False 241 | else: 242 | directory = self.directory 243 | error = checks(directory) 244 | 245 | if not error: 246 | load_smr_data(directory) 247 | 248 | return {'FINISHED'} 249 | 250 | if __name__ == "__main__": 251 | bpy.utils.register_class(separate_meshes) 252 | 253 | # test call 254 | print((bpy.ops.kkb.separatemeshes('INVOKE_DEFAULT'))) 255 | 256 | 257 | ########## EXPORTER ########## 258 | def export_meshes(directory): 259 | bpy.ops.object.mode_set(mode = 'OBJECT') 260 | 261 | obj_material_map = { 262 | 'cf_Ohitomi_L' : 'cf_m_sirome_00', 263 | 'cf_Ohitomi_R' : 'cf_m_sirome_00', 264 | 'cf_Ohitomi_L02' : 'cf_m_hitomi_00_cf_Ohitomi_L02', 265 | 'cf_Ohitomi_R02' : 'cf_m_hitomi_00_cf_Ohitomi_R02', 266 | 'cf_O_namida_L' : 'cf_m_namida_00', 267 | 'cf_O_namida_M' : 'cf_m_namida_00', 268 | 'cf_O_namida_S' : 'cf_m_namida_00', 269 | 'o_tang' : 'cf_m_tang', 270 | 'o_tang.001' : 'cf_m_tang', 271 | } 272 | 273 | armature = bpy.data.objects['Armature'] 274 | for obj in bpy.data.objects: 275 | if obj.parent == armature and obj.visible_get(): 276 | bpy.ops.object.select_all(action='DESELECT') 277 | 278 | bpy.context.view_layer.objects.active = armature 279 | armature.select_set(True) 280 | obj.select_set(True) 281 | 282 | #Special case rename 283 | if obj.name in obj_material_map: 284 | obj.data.materials[0].name = obj_material_map[obj.name] 285 | 286 | bpy.ops.export_scene.fbx(filepath = directory + obj.name + '.fbx', use_selection = True, use_active_collection = False, global_scale = 1.0, apply_unit_scale = True, apply_scale_options = 'FBX_SCALE_NONE', use_space_transform = True, bake_space_transform = False, object_types={'ARMATURE', 'MESH'}, use_mesh_modifiers = True, use_mesh_modifiers_render = True, mesh_smooth_type = 'FACE', use_subsurf = False, use_mesh_edges = False, use_tspace = False, use_custom_props = False, add_leaf_bones = False, primary_bone_axis= 'Z', secondary_bone_axis='Y', use_armature_deform_only = False, armature_nodetype = 'NULL', bake_anim = False, bake_anim_use_all_bones = False, bake_anim_use_nla_strips = False, bake_anim_use_all_actions = True, bake_anim_force_startend_keying = True, bake_anim_step = 1.0, bake_anim_simplify_factor = 0, path_mode = 'AUTO', embed_textures = False, batch_mode = 'OFF', use_batch_own_dir = True, axis_forward='-X', axis_up='Z') 287 | 288 | bpy.ops.object.select_all(action='DESELECT') 289 | 290 | 291 | class export_separate_meshes(bpy.types.Operator, ExportHelper): 292 | bl_idname = "kkb.exportseparatemeshes" 293 | bl_label = "Export Separate Meshes" 294 | bl_description = "Choose where to export meshes" 295 | bl_options = {'REGISTER', 'UNDO'} 296 | 297 | filepath : StringProperty(maxlen=1024, default='', subtype='FILE_PATH', options={'HIDDEN'}) 298 | filter_glob : StringProperty(default='', options={'HIDDEN'}) 299 | 300 | def execute(self, context): 301 | filepath = self.filepath 302 | 303 | export_meshes(filepath) 304 | 305 | return {'FINISHED'} 306 | 307 | def invoke(self, context, event): 308 | context.window_manager.fileselect_add(self) 309 | return {'RUNNING_MODAL'} 310 | -------------------------------------------------------------------------------- /importing/darkcolors.py: -------------------------------------------------------------------------------- 1 | #multiplying things per element according to https://github.com/Unity-Technologies/Unity.Mathematics/blob/master/src/Unity.Mathematics/float4.gen.cs#L330 2 | #returning things like float.XZW as [Xposition = X, Yposition = Z, Zposition = W] according to https://github.com/Unity-Technologies/Unity.Mathematics/blob/master/src/Unity.Mathematics/float4.gen.cs#L3056 3 | #using the variable order x, y, z, w according to https://github.com/Unity-Technologies/Unity.Mathematics/blob/master/src/Unity.Mathematics/float4.gen.cs#L42 4 | 5 | import math 6 | 7 | #class to mimic part of float4 class in Unity 8 | class float4: 9 | def __init__(self, x = None, y = None, z = None, w = None): 10 | self.x = x 11 | self.y = y 12 | self.z = z 13 | self.w = w 14 | def __mul__ (self, vector): 15 | #if a float4, multiply piece by piece, else multiply full vector 16 | if type(vector) in [float, int]: 17 | vector = float4(vector, vector, vector, vector) 18 | x = self.x * vector.x if self.get('x') != None else None 19 | y = self.y * vector.y if self.get('y') != None else None 20 | z = self.z * vector.z if self.get('z') != None else None 21 | w = self.w * vector.w if self.get('w') != None else None 22 | return float4(x,y,z,w) 23 | __rmul__ = __mul__ 24 | def __add__ (self, vector): 25 | #if a float4, add piece by piece, else add full vector 26 | if type(vector) in [float, int]: 27 | vector = float4(vector, vector, vector, vector) 28 | x = self.x + vector.x if self.get('x') != None else None 29 | y = self.y + vector.y if self.get('y') != None else None 30 | z = self.z + vector.z if self.get('z') != None else None 31 | w = self.w + vector.w if self.get('w') != None else None 32 | return float4(x,y,z,w) 33 | __radd__ = __add__ 34 | def __sub__ (self, vector): 35 | #if a float4, subtract piece by piece, else subtract full vector 36 | if type(vector) in [float, int]: 37 | vector = float4(vector, vector, vector, vector) 38 | x = self.x - vector.x if self.get('x') != None else None 39 | y = self.y - vector.y if self.get('y') != None else None 40 | z = self.z - vector.z if self.get('z') != None else None 41 | w = self.w - vector.w if self.get('w') != None else None 42 | return float4(x,y,z,w) 43 | __rsub__ = __sub__ 44 | def __gt__ (self, vector): 45 | #if a float4, compare piece by piece, else compare full vector 46 | if type(vector) in [float, int]: 47 | vector = float4(vector, vector, vector, vector) 48 | x = self.x > vector.x if self.get('x') != None else None 49 | y = self.y > vector.y if self.get('y') != None else None 50 | z = self.z > vector.z if self.get('z') != None else None 51 | w = self.w > vector.w if self.get('w') != None else None 52 | return float4(x,y,z,w) 53 | def __neg__ (self): 54 | x = -self.x if self.get('x') != None else None 55 | y = -self.y if self.get('y') != None else None 56 | z = -self.z if self.get('z') != None else None 57 | w = -self.w if self.get('w') != None else None 58 | return float4(x,y,z,w) 59 | def frac(self): 60 | x = self.x - math.floor (self.x) if self.get('x') != None else None 61 | y = self.y - math.floor (self.y) if self.get('y') != None else None 62 | z = self.z - math.floor (self.z) if self.get('z') != None else None 63 | w = self.w - math.floor (self.w) if self.get('w') != None else None 64 | return float4(x,y,z,w) 65 | def abs(self): 66 | x = abs(self.x) if self.get('x') != None else None 67 | y = abs(self.y) if self.get('y') != None else None 68 | z = abs(self.z) if self.get('z') != None else None 69 | w = abs(self.w) if self.get('w') != None else None 70 | return float4(x,y,z,w) 71 | def clamp(self): 72 | x = (0 if self.x < 0 else 1 if self.x > 1 else self.x) if self.get('x') != None else None 73 | y = (0 if self.y < 0 else 1 if self.y > 1 else self.y) if self.get('y') != None else None 74 | z = (0 if self.z < 0 else 1 if self.z > 1 else self.z) if self.get('z') != None else None 75 | w = (0 if self.w < 0 else 1 if self.w > 1 else self.w) if self.get('w') != None else None 76 | return float4(x,y,z,w) 77 | saturate = clamp 78 | def clamphalf(self): 79 | x = (0 if self.x < 0 else .5 if self.x > .5 else self.x) if self.get('x') != None else None 80 | y = (0 if self.y < 0 else .5 if self.y > .5 else self.y) if self.get('y') != None else None 81 | z = (0 if self.z < 0 else .5 if self.z > .5 else self.z) if self.get('z') != None else None 82 | w = (0 if self.w < 0 else .5 if self.w > .5 else self.w) if self.get('w') != None else None 83 | return float4(x,y,z,w) 84 | def get(self, var): 85 | if hasattr(self, var): 86 | return getattr(self, var) 87 | else: 88 | return None 89 | def __str__(self): 90 | return str([self.x, self.y, self.z, self.w]) 91 | __repr__ = __str__ 92 | 93 | 94 | #this function is from https://github.com/xukmi/KKShadersPlus/blob/main/Shaders/Item/KKPItemDiffuse.cginc 95 | #lines without comments at the end have been copied verbatim from the C# source 96 | def ShadeAdjustItem(col, _ShadowColor): 97 | #start at line 63 98 | u_xlat0 = col 99 | u_xlat1 = float4(u_xlat0.y, u_xlat0.z, None, u_xlat0.x) * float4(_ShadowColor.y, _ShadowColor.z, None, _ShadowColor.x) #line 65 100 | u_xlat2 = float4(u_xlat1.y, u_xlat1.x) #66 101 | u_xlat3 = float4(u_xlat0.y, u_xlat0.z) * float4(_ShadowColor.y, _ShadowColor.z) + (-float4(u_xlat2.x, u_xlat2.y)); #67 102 | u_xlatb30 = u_xlat2.y >= u_xlat1.y; 103 | u_xlat30 = 1 if u_xlatb30 else 0; 104 | u_xlat2 = float4(u_xlat2.x, u_xlat2.y, -1.0, 0.666666687); #70-71 105 | u_xlat3 = float4(u_xlat3.x, u_xlat3.y, 1.0, -1); #72-73 106 | u_xlat2 = (u_xlat30) * u_xlat3 + u_xlat2; 107 | u_xlatb30 = u_xlat1.w >= u_xlat2.x; 108 | u_xlat30 = 1 if u_xlatb30 else float(0.0); 109 | u_xlat1 = float4(u_xlat2.x, u_xlat2.y, u_xlat2.w, u_xlat1.w) #77 110 | u_xlat2 = float4(u_xlat1.w, u_xlat1.y, u_xlat2.z, u_xlat1.x) #78 111 | u_xlat2 = (-u_xlat1) + u_xlat2; 112 | u_xlat1 = (u_xlat30) * u_xlat2 + u_xlat1; 113 | u_xlat30 = min(u_xlat1.y, u_xlat1.w); 114 | u_xlat30 = (-u_xlat30) + u_xlat1.x; 115 | u_xlat2.x = u_xlat30 * 6.0 + 1.00000001e-10; 116 | u_xlat11 = (-u_xlat1.y) + u_xlat1.w; 117 | u_xlat11 = u_xlat11 / u_xlat2.x; 118 | u_xlat11 = u_xlat11 + u_xlat1.z; 119 | u_xlat1.x = u_xlat1.x + 1.00000001e-10; 120 | u_xlat30 = u_xlat30 / u_xlat1.x; 121 | u_xlat30 = u_xlat30 * 0.5; 122 | #the w component of u_xlat1 is no longer used, so ignore it 123 | u_xlat1 = abs((u_xlat11)) + float4(0.0, -0.333333343, 0.333333343, 1); #90 124 | u_xlat1 = u_xlat1.frac(); #91 125 | u_xlat1 = -u_xlat1 * 2 + 1; #92 126 | u_xlat1 = u_xlat1.abs() * 3 + (-1) #93 127 | u_xlat1 = u_xlat1.clamp() #94 128 | u_xlat1 = u_xlat1 + (-1); #95 129 | u_xlat1 = (u_xlat30) * u_xlat1 + 1; #96 130 | return float4(u_xlat1.x, u_xlat1.y, u_xlat1.z, 1) #97 131 | 132 | #rest is from https://github.com/xukmi/KKShadersPlus/blob/main/Shaders/Item/MainItemPlus.shader 133 | #lines without comments at the end have been copied verbatim from the C# source 134 | def kk_dark_color(color, shadow_color): 135 | ################### variable setup 136 | _ShadowExtendAnother = 0 #manually set in game 137 | _ShadowExtend = 0 #manually set in game 138 | finalRamp = .26 #assume ramp is inversion of shadow setting in the charamaker. setting it to 1 gives the original light color back 139 | a = 0.666 140 | _ambientshadowG = float4(a,a,a,1) #defaults to 0.666, 0.666, 0.666, 1 141 | _CustomAmbient = _ambientshadowG 142 | _LightColor0 = float4(1,1,1,1) #assume light color is 1 143 | vertexLighting = float4(0,0,0,1) #assume vertex lighting is 0 144 | vertexLightRamp = float4(0,0,0,1) #assume light ramp is black 145 | detailMask = float4(0,0,0,1) #black if no detail mask loaded in 146 | 147 | diffuse = float4(color[0],color[1],color[2],1) #maintex color 148 | _ShadowColor = float4(shadow_color[0],shadow_color[1],shadow_color[2],1) #shadow color from material editor defaults to [.764, .880, 1, 1] 149 | ########################## 150 | 151 | #start at line 344 because the other one is for outlines 152 | shadingAdjustment = ShadeAdjustItem(diffuse, _ShadowColor) 153 | 154 | #skip to line 352 155 | diffuseShaded = shadingAdjustment * 0.899999976 - 0.5; 156 | diffuseShaded = -diffuseShaded * 2 + 1; 157 | ambientShadow = 1 - float4(_ambientshadowG.w, _ambientshadowG.x, _ambientshadowG.y, _ambientshadowG.z) #354 158 | ambientShadowIntensity = -ambientShadow.x * float4(ambientShadow.y, ambientShadow.z, ambientShadow.w, 1) + 1 #355 159 | ambientShadowAdjust = _ambientshadowG.w * 0.5 + 0.5; 160 | ambientShadowAdjustDoubled = ambientShadowAdjust + ambientShadowAdjust; 161 | ambientShadowAdjustShow = 0.5 < ambientShadowAdjust; 162 | ambientShadow = ambientShadowAdjustDoubled * _ambientshadowG; #359 163 | finalAmbientShadow = ambientShadowIntensity if ambientShadowAdjustShow else ambientShadow; #360 164 | finalAmbientShadow = finalAmbientShadow.saturate(); #361 165 | invertFinalAmbientShadow = 1 - finalAmbientShadow; 166 | compTest = 0.555555582 < shadingAdjustment; 167 | shadingAdjustment *= finalAmbientShadow; 168 | shadingAdjustment *= 1.79999995; 169 | diffuseShaded = -diffuseShaded * invertFinalAmbientShadow + 1; 170 | 171 | hlslcc_movcTemp = shadingAdjustment; 172 | hlslcc_movcTemp.x = diffuseShaded.x if (compTest.x) else shadingAdjustment.x; #370 173 | hlslcc_movcTemp.y = diffuseShaded.y if (compTest.y) else shadingAdjustment.y; #371 174 | hlslcc_movcTemp.z = diffuseShaded.z if (compTest.z) else shadingAdjustment.z; #372 175 | #shadowCol = lerp(1, _ShadowColor.rgb, 1 - saturate(_ShadowColor.a)); 176 | shadowCol = _ShadowColor # but the lerp result is going to be this because shadowColor's alpha is always 1 177 | shadingAdjustment = (hlslcc_movcTemp * shadowCol).saturate(); #374 178 | shadowExtendAnother = 1 - _ShadowExtendAnother; 179 | shadowExtendAnother -= 0; #assume KKMetal is 0 180 | shadowExtendAnother += 1; 181 | shadowExtendAnother = (0 if shadowExtendAnother < 0 else 1 if shadowExtendAnother > 1 else shadowExtendAnother) * 0.670000017 + 0.330000013 #384 182 | shadowExtendShaded = shadowExtendAnother * shadingAdjustment; 183 | shadingAdjustment = -shadingAdjustment * shadowExtendAnother + 1; 184 | diffuseShadow = diffuse * shadowExtendShaded; 185 | diffuseShadowBlended = -shadowExtendShaded * diffuse + diffuse; 186 | 187 | #skip to 437 188 | diffuseShadow = finalRamp * diffuseShadowBlended + diffuseShadow; 189 | 190 | #jump up to 248 function definition 191 | def AmbientShadowAdjust(): 192 | u_xlatb30 = _ambientshadowG.y >= _ambientshadowG.z; 193 | u_xlat30 = 1 if u_xlatb30 else float(0.0); #256 194 | u_xlat5 = float4(_ambientshadowG.y, _ambientshadowG.z); #257 195 | u_xlat5.z = float(0.0); 196 | u_xlat5.w = float(-0.333333343); 197 | u_xlat6 = float4(_ambientshadowG.z, _ambientshadowG.y); #260 198 | u_xlat6.z = float(-1.0); 199 | u_xlat6.w = float(0.666666687); 200 | u_xlat5 = u_xlat5 + (-u_xlat6); 201 | u_xlat5 = (u_xlat30) * float4(u_xlat5.x, u_xlat5.y, u_xlat5.w, u_xlat5.z) + float4(u_xlat6.x, u_xlat6.y, u_xlat6.w, u_xlat6.z); #264 202 | u_xlatb30 = _ambientshadowG.x >= u_xlat5.x; 203 | u_xlat30 = 1 if u_xlatb30 else float(0.0); #266 204 | u_xlat6.z = u_xlat5.w; 205 | u_xlat5.w = _ambientshadowG.x; 206 | u_xlat6 = float4(u_xlat5.w, u_xlat5.y, u_xlat6.z, u_xlat5.x) #269 207 | u_xlat6 = (-u_xlat5) + u_xlat6; 208 | u_xlat5 = (u_xlat30) * u_xlat6 + u_xlat5; 209 | u_xlat30 = min(u_xlat5.y, u_xlat5.w); 210 | u_xlat30 = (-u_xlat30) + u_xlat5.x; 211 | u_xlat30 = u_xlat30 * 6.0 + 1.00000001e-10; 212 | u_xlat31 = (-u_xlat5.y) + u_xlat5.w; 213 | u_xlat30 = u_xlat31 / u_xlat30; 214 | u_xlat30 = u_xlat30 + u_xlat5.z; 215 | #xlat5's w component is not used after this point, so ignore it 216 | u_xlat5 = abs((u_xlat30)) + float4(0.0, -0.333333343, 0.333333343, None); #278 217 | u_xlat5 = u_xlat5.frac(); #279 218 | u_xlat5 = (-u_xlat5) * 2.0 + 1.0; #280 219 | u_xlat5 = u_xlat5.abs() * 3 + (-1); #281 220 | u_xlat5 = u_xlat5.clamp(); #282 221 | u_xlat5 = u_xlat5 * float4(0.400000006, 0.400000006, 0.400000006) + float4(0.300000012, 0.300000012, 0.300000012); #283 222 | return float4(u_xlat5.x, u_xlat5.y, u_xlat5.z, 1) 223 | 224 | #jump back down to 470 where this function is used 225 | ambientShadowAdjust2 = AmbientShadowAdjust() 226 | #skip to 482 227 | ambientShadowAdjust2 = ambientShadowAdjust2.clamphalf() #clamp between zero and .5 228 | diffuseShadow += ambientShadowAdjust2; 229 | 230 | lightCol = (_LightColor0 + vertexLighting * vertexLightRamp) * float4(0.600000024, 0.600000024, 0.600000024, 0) + _CustomAmbient; 231 | ambientCol = max(lightCol, _ambientshadowG); 232 | diffuseShadow = diffuseShadow * ambientCol; 233 | shadowExtend = _ShadowExtend * -1.20000005 + 1.0; 234 | drawnShadow = detailMask.y * (1 - shadowExtend) + shadowExtend; 235 | 236 | #skip to 495 237 | shadingAdjustment = drawnShadow * shadingAdjustment + shadowExtendShaded 238 | shadingAdjustment *= diffuseShadow; 239 | diffuse *= shadowExtendShaded; 240 | 241 | #print('diffuse is ' + str(diffuse)) 242 | #print('diffuseShadow is ' + str(diffuseShadow)) 243 | #print('diffuseShaded is ' + str(diffuseShaded)) 244 | #print('drawnShadow is ' + str(drawnShadow)) 245 | #print('diffuseShadowblended is ' + str(diffuseShadowBlended)) 246 | return [diffuse.x, diffuse.y, diffuse.z] 247 | 248 | if __name__ == '__main__': 249 | color = [.5, .5, .5] 250 | shadow_color = [.764, .880, 1] 251 | new_color = kk_dark_color(color, shadow_color) -------------------------------------------------------------------------------- /importing/cleanarmature.py: -------------------------------------------------------------------------------- 1 | ### redo toe bone connections 2 | # when did i put this note here 3 | 4 | ''' 5 | AFTER CATS (CLEAN ARMATURE) SCRIPT 6 | - Hides all bones that aren't in the bonelists 7 | - Connects the finger bones that CATS sometimes misses for koikatsu imports 8 | - Corrects the toe bones on the better penetration armature 9 | Usage: 10 | - Run the script 11 | ''' 12 | 13 | import bpy, time, traceback 14 | from .finalizepmx import survey_vertexes 15 | from .importbuttons import kklog 16 | 17 | #function that returns a type of bone list 18 | def get_bone_list(kind): 19 | if kind == 'core_list': 20 | #main bone list 21 | return [ 22 | 'cf_n_height', 'cf_j_hips', 'cf_j_waist01', 'cf_j_waist02', 23 | 'cf_j_spine01', 'cf_j_spine02', 'cf_j_spine03', 24 | 'cf_j_neck', 'cf_j_head', 25 | 'cf_d_bust00', 'cf_j_bust01_L', 'cf_j_bust01_R', 'Eyesx', 26 | 27 | 'cf_j_shoulder_L', 'cf_j_shoulder_R', 'cf_j_arm00_L', 'cf_j_arm00_R', 28 | 'cf_j_forearm01_L', 'cf_j_forearm01_R', 'cf_j_hand_R', 'cf_j_hand_L', 29 | 30 | 'cf_j_thumb01_L','cf_j_thumb02_L', 'cf_j_thumb03_L', 31 | 'cf_j_ring01_L', 'cf_j_ring02_L', 'cf_j_ring03_L', 32 | 'cf_j_middle01_L','cf_j_middle02_L', 'cf_j_middle03_L', 33 | 'cf_j_little01_L','cf_j_little02_L', 'cf_j_little03_L', 34 | 'cf_j_index01_L','cf_j_index02_L', 'cf_j_index03_L', 35 | 36 | 'cf_j_thumb01_R','cf_j_thumb02_R', 'cf_j_thumb03_R', 37 | 'cf_j_ring01_R','cf_j_ring02_R', 'cf_j_ring03_R', 38 | 'cf_j_middle01_R','cf_j_middle02_R', 'cf_j_middle03_R', 39 | 'cf_j_little01_R','cf_j_little02_R', 'cf_j_little03_R', 40 | 'cf_j_index01_R', 'cf_j_index02_R', 'cf_j_index03_R', 41 | 42 | 'cf_j_thigh00_L', 'cf_j_thigh00_R', 'cf_j_leg01_L', 'cf_j_leg01_R', 43 | 'cf_j_foot_L', 'cf_j_foot_R', 'cf_j_toes_L', 'cf_j_toes_R', 44 | 45 | 'cf_j_siri_L', 'cf_j_siri_R', 46 | 47 | 'cf_pv_knee_L', 'cf_pv_knee_R', 48 | 'cf_pv_elbo_L', 'cf_pv_elbo_R', 49 | 'cf_pv_hand_L', 'cf_pv_hand_R', 50 | 'cf_pv_foot_L', 'cf_pv_foot_R' 51 | ] 52 | 53 | elif kind == 'non_ik': 54 | #IK bone list 55 | return [ 56 | 'cf_j_forearm01_L', 'cf_j_forearm01_R', 57 | 'cf_j_arm00_L', 'cf_j_arm00_R', 58 | 'cf_j_thigh00_L', 'cf_j_thigh00_R', 59 | 'cf_j_leg01_L', 'cf_j_leg01_R', 60 | 'cf_j_leg03_L', 'cf_j_leg03_R', 61 | 'cf_j_foot_L', 'cf_j_foot_R', 62 | 'cf_j_hand_L', 'cf_j_hand_R', 63 | 'cf_j_bust03_L', 'cf_j_bnip02root_L', 'cf_j_bnip02_L', 64 | 'cf_j_bust03_R', 'cf_j_bnip02root_R', 'cf_j_bnip02_R'] 65 | 66 | elif kind == 'eye_list': 67 | return [ 68 | 'cf_J_Eye01_s_L', 'cf_J_Eye01_s_R', 69 | 'cf_J_Eye02_s_L', 'cf_J_Eye02_s_R', 70 | 'cf_J_Eye03_s_L', 'cf_J_Eye03_s_R', 71 | 'cf_J_Eye04_s_L', 'cf_J_Eye04_s_R', 72 | 'cf_J_Eye05_s_L', 'cf_J_Eye05_s_R', 73 | 'cf_J_Eye06_s_L', 'cf_J_Eye06_s_R', 74 | 'cf_J_Eye07_s_L', 'cf_J_Eye07_s_R', 75 | 'cf_J_Eye08_s_L', 'cf_J_Eye08_s_R', 76 | 77 | 'cf_J_Mayu_R', 'cf_J_MayuMid_s_R', 'cf_J_MayuTip_s_R', 78 | 'cf_J_Mayu_L', 'cf_J_MayuMid_s_L', 'cf_J_MayuTip_s_L'] 79 | 80 | elif kind == 'mouth_list': 81 | return [ 82 | 'cf_J_Mouth_R', 'cf_J_Mouth_L', 83 | 'cf_J_Mouthup', 'cf_J_MouthLow', 'cf_J_MouthMove', 'cf_J_MouthCavity', 84 | 85 | 'cf_J_EarUp_L', 'cf_J_EarBase_ry_L', 'cf_J_EarLow_L', 86 | 'cf_J_CheekUp2_L', 'cf_J_Eye_rz_L', 'cf_J_Eye_rz_L', 87 | 'cf_J_CheekUp_s_L', 'cf_J_CheekLow_s_L', 88 | 89 | 'cf_J_EarUp_R', 'cf_J_EarBase_ry_R', 'cf_J_EarLow_R', 90 | 'cf_J_CheekUp2_R', 'cf_J_Eye_rz_R', 'cf_J_Eye_rz_R', 91 | 'cf_J_CheekUp_s_R', 'cf_J_CheekLow_s_R', 92 | 93 | 'cf_J_ChinLow', 'cf_J_Chin_s', 'cf_J_ChinTip_Base', 94 | 'cf_J_NoseBase', 'cf_J_NoseBridge_rx', 'cf_J_Nose_tip'] 95 | 96 | elif kind == 'toe_list': 97 | #bones that appear on the Better Penetration armature 98 | return [ 99 | 'cf_j_toes0_L', 'cf_j_toes1_L', 'cf_j_toes10_L', 100 | 'cf_j_toes2_L', 'cf_j_toes20_L', 101 | 'cf_j_toes3_L', 'cf_j_toes30_L', 'cf_j_toes4_L', 102 | 103 | 'cf_j_toes0_R', 'cf_j_toes1_R', 'cf_j_toes10_R', 104 | 'cf_j_toes2_R', 'cf_j_toes20_R', 105 | 'cf_j_toes3_R', 'cf_j_toes30_R', 'cf_j_toes4_R'] 106 | 107 | elif kind == 'bp_list': 108 | #more bones that appear on the Better Penetration armature 109 | return [ 110 | 'cf_j_kokan', 'cf_j_ana', 'cf_J_Vagina_root', 'cf_J_Vagina_B', 'cf_J_Vagina_F', 111 | 'cf_J_Vagina_L.001', 'cf_J_Vagina_L.002', 'cf_J_Vagina_L.003', 'cf_J_Vagina_L.004', 'cf_J_Vagina_L.005', 112 | 'cf_J_Vagina_R.001', 'cf_J_Vagina_R.002', 'cf_J_Vagina_R.003', 'cf_J_Vagina_R.004', 'cf_J_Vagina_R.005'] 113 | 114 | elif kind == 'skirt_list': 115 | return [ 116 | 'cf_j_sk_00_00', 'cf_j_sk_00_01', 'cf_j_sk_00_02', 'cf_j_sk_00_03', 'cf_j_sk_00_04', 117 | 'cf_j_sk_01_00', 'cf_j_sk_01_01', 'cf_j_sk_01_02', 'cf_j_sk_01_03', 'cf_j_sk_01_04', 118 | 'cf_j_sk_02_00', 'cf_j_sk_02_01', 'cf_j_sk_02_02', 'cf_j_sk_02_03', 'cf_j_sk_02_04', 119 | 'cf_j_sk_03_00', 'cf_j_sk_03_01', 'cf_j_sk_03_02', 'cf_j_sk_03_03', 'cf_j_sk_03_04', 120 | 'cf_j_sk_04_00', 'cf_j_sk_04_01', 'cf_j_sk_04_02', 'cf_j_sk_04_03', 'cf_j_sk_04_04', 121 | 'cf_j_sk_05_00', 'cf_j_sk_05_01', 'cf_j_sk_05_02', 'cf_j_sk_05_03', 'cf_j_sk_05_04', 122 | 'cf_j_sk_06_00', 'cf_j_sk_06_01', 'cf_j_sk_06_02', 'cf_j_sk_06_03', 'cf_j_sk_06_04', 123 | 'cf_j_sk_07_00', 'cf_j_sk_07_01', 'cf_j_sk_07_02', 'cf_j_sk_07_03', 'cf_j_sk_07_04'] 124 | 125 | elif kind == 'tongue_list': 126 | return [ 127 | 'cf_j_tang_01', 'cf_j_tang_02', 'cf_j_tang_03', 'cf_j_tang_04', 'cf_j_tang_05', 128 | 'cf_j_tang_L_03', 'cf_j_tang_L_04', 'cf_j_tang_L_05', 129 | 'cf_j_tang_R_03', 'cf_j_tang_R_04', 'cf_j_tang_R_05', 130 | ] 131 | 132 | def set_armature_layer(bone_name, show_layer, hidden = False): 133 | bpy.data.armatures[0].bones[bone_name].layers = ( 134 | True, False, False, False, False, False, False, False, 135 | False, False, False, False, False, False, False, False, 136 | False, False, False, False, False, False, False, False, 137 | False, False, False, False, False, False, False, False 138 | ) 139 | bpy.data.armatures[0].bones[bone_name].layers[show_layer] = True 140 | bpy.data.armatures[0].bones[bone_name].layers[0] = False 141 | bpy.data.armatures[0].bones[bone_name].hide = hidden 142 | 143 | def reorganize_armature_layers(): 144 | #Select the armature and make it active 145 | bpy.ops.object.mode_set(mode='OBJECT') 146 | bpy.ops.object.select_all(action='DESELECT') 147 | armature = bpy.data.objects['Armature'] 148 | armature.hide = False 149 | armature.select_set(True) 150 | bpy.context.view_layer.objects.active=armature 151 | bpy.ops.object.mode_set(mode='POSE') 152 | 153 | core_list = get_bone_list('core_list') 154 | non_ik = get_bone_list('non_ik') 155 | toe_list = get_bone_list('toe_list') 156 | bp_list = get_bone_list('bp_list') 157 | eye_list = get_bone_list('eye_list') 158 | mouth_list = get_bone_list('mouth_list') 159 | skirt_list = get_bone_list('skirt_list') 160 | tongue_list = get_bone_list('tongue_list') 161 | 162 | armature = bpy.data.objects['Armature'] 163 | bpy.ops.pose.select_all(action='DESELECT') 164 | 165 | #throw all bones to armature layer 11 166 | for bone in bpy.data.armatures[0].bones: 167 | set_armature_layer(bone.name, 10) 168 | 169 | #reshow cf_hit_ bones on layer 12 170 | for bone in [bones for bones in bpy.data.armatures[0].bones if 'cf_hit_' in bones.name]: 171 | set_armature_layer(bone.name, show_layer = 11) 172 | 173 | #reshow k_f_ bones on layer 13 174 | for bone in [bones for bones in bpy.data.armatures[0].bones if 'k_f_' in bones.name]: 175 | set_armature_layer(bone.name, show_layer = 12) 176 | 177 | #reshow core bones on layer 1 178 | for bone in core_list: 179 | set_armature_layer(bone, show_layer = 0) 180 | 181 | #reshow non_ik bones on layer 2 182 | for bone in non_ik: 183 | set_armature_layer(bone, show_layer = 1) 184 | 185 | #Put the charamaker bones on layer 3 186 | for bone in [bones for bones in bpy.data.armatures[0].bones if 'cf_s_' in bones.name]: 187 | set_armature_layer(bone.name, show_layer = 2) 188 | 189 | #Put the deform bones on layer 4 190 | for bone in [bones for bones in bpy.data.armatures[0].bones if 'cf_d_' in bones.name]: 191 | set_armature_layer(bone.name, show_layer = 3) 192 | 193 | try: 194 | #Put the better penetration bones on layer 5 195 | for bone in bp_list: 196 | set_armature_layer(bone, show_layer = 4) 197 | 198 | #rename the bones so you can mirror them over the x axis in pose mode 199 | if 'Vagina_L_' in bone or 'Vagina_R_' in bone: 200 | bpy.data.armatures[0].bones[bone].name = 'Vagina' + bone[8:] + '_' + bone[7] 201 | 202 | #Put the toe bones on layer 5 203 | for bone in toe_list: 204 | set_armature_layer(bone, show_layer = 4) 205 | except: 206 | #this armature isn't a BP armature 207 | pass 208 | 209 | #Put the upper eye bones on layer 17 210 | for bone in eye_list: 211 | set_armature_layer(bone, show_layer = 16) 212 | 213 | #Put the lower mouth bones on layer 18 214 | for bone in mouth_list: 215 | set_armature_layer(bone, show_layer = 17) 216 | 217 | #Put the tongue rig bones on layer 19 218 | for bone in tongue_list: 219 | set_armature_layer(bone, show_layer = 18) 220 | 221 | #Put the skirt bones on layer 9 222 | for bone in skirt_list: 223 | set_armature_layer(bone, show_layer = 8) 224 | 225 | #put accessory bones on layer 10 during reshow_accessory_bones() later on 226 | 227 | bpy.ops.pose.select_all(action='DESELECT') 228 | 229 | #Make all bone layers visible for now 230 | all_layers = [ 231 | True, True, True, True, True, False, False, False, #body 232 | True, True, True, False, False, False, False, False, #clothes 233 | True, True, False, False, False, False, False, False, #face 234 | False, False, False, False, False, False, False, False] 235 | bpy.ops.armature.armature_layers(layers=all_layers) 236 | bpy.context.object.data.display_type = 'STICK' 237 | bpy.ops.object.mode_set(mode='OBJECT') 238 | 239 | #make sure certain bones are visually connected 240 | def visually_connect_bones(): 241 | 242 | skirt_list = get_bone_list('skirt_list') 243 | bpy.ops.object.mode_set(mode='EDIT') 244 | armature = bpy.data.objects['Armature'] 245 | 246 | # Make sure all toe bones are visually correct if using the better penetration armature 247 | try: 248 | armature.data.edit_bones['Toes4_L'].tail.y = armature.data.edit_bones['Toes30_L'].head.y 249 | armature.data.edit_bones['Toes4_L'].tail.z = armature.data.edit_bones['Toes30_L'].head.z*.8 250 | armature.data.edit_bones['Toes0_L'].tail.y = armature.data.edit_bones['Toes10_L'].head.y 251 | armature.data.edit_bones['Toes0_L'].tail.z = armature.data.edit_bones['Toes30_L'].head.z*.9 252 | 253 | armature.data.edit_bones['Toes30_L'].tail.z = armature.data.edit_bones['Toes30_L'].head.z*0.8 254 | armature.data.edit_bones['Toes30_L'].tail.y = armature.data.edit_bones['Toes30_L'].head.y*1.2 255 | armature.data.edit_bones['Toes20_L'].tail.z = armature.data.edit_bones['Toes20_L'].head.z*0.8 256 | armature.data.edit_bones['Toes20_L'].tail.y = armature.data.edit_bones['Toes20_L'].head.y*1.2 257 | armature.data.edit_bones['Toes10_L'].tail.z = armature.data.edit_bones['Toes10_L'].head.z*0.8 258 | armature.data.edit_bones['Toes10_L'].tail.y = armature.data.edit_bones['Toes10_L'].head.y*1.2 259 | 260 | armature.data.edit_bones['Toes4_R'].tail.y = armature.data.edit_bones['Toes30_R'].head.y 261 | armature.data.edit_bones['Toes4_R'].tail.z = armature.data.edit_bones['Toes30_R'].head.z*.8 262 | armature.data.edit_bones['Toes0_R'].tail.y = armature.data.edit_bones['Toes10_R'].head.y 263 | armature.data.edit_bones['Toes0_R'].tail.z = armature.data.edit_bones['Toes30_R'].head.z*.9 264 | 265 | armature.data.edit_bones['Toes30_R'].tail.z = armature.data.edit_bones['Toes30_R'].head.z*0.8 266 | armature.data.edit_bones['Toes30_R'].tail.y = armature.data.edit_bones['Toes30_R'].head.y*1.2 267 | armature.data.edit_bones['Toes20_R'].tail.z = armature.data.edit_bones['Toes20_R'].head.z*0.8 268 | armature.data.edit_bones['Toes20_R'].tail.y = armature.data.edit_bones['Toes20_R'].head.y*1.2 269 | armature.data.edit_bones['Toes10_R'].tail.z = armature.data.edit_bones['Toes10_R'].head.z*0.8 270 | armature.data.edit_bones['Toes10_R'].tail.y = armature.data.edit_bones['Toes10_R'].head.y*1.2 271 | except: 272 | #this character isn't using the BP/toe control armature 273 | pass 274 | 275 | '''#Make sure top skirt root bones are visually correct (flip them) 276 | def flip(switchbone): 277 | chain = switchbone[5:10] 278 | armature.data.edit_bones[switchbone].tail = (armature.data.edit_bones[switchbone].head + armature.data.edit_bones['cf_j_'+chain+'_00'].tail)/2 279 | for skirtroot in skirt_list: 280 | if '_d_' in skirtroot: 281 | flip(skirtroot) 282 | ''' 283 | bpy.ops.object.mode_set(mode='OBJECT') 284 | 285 | def move_accessory_bones(context): 286 | armature = bpy.data.objects['Armature'] 287 | #go through each outfit and move ALL accessory bones to layer 10 288 | dont_move_these = [ 289 | 'cf_pv', 'Eyesx', 290 | 'cf_J_hitomi_tx_', 'cf_J_FaceRoot', 'cf_J_FaceUp_t', 291 | 'n_cam', 'EyesLookTar', 'N_move', 'a_n_', 'cf_hit', 292 | 'cf_j_bnip02', 'cf_j_kokan', 'cf_j_ana'] 293 | 294 | for outfit_or_hair in [obj for obj in bpy.data.objects if 'Outfit ' in obj.name]: 295 | # Find empty vertex groups 296 | vertexWeightMap = survey_vertexes(outfit_or_hair) 297 | #add outfit id to all accessory bones used by that outfit in an array 298 | number_of_outfits = len([outfit for outfit in bpy.data.objects if 'Outfit ' in outfit.name and 'Hair' not in outfit.name and 'alt ' not in outfit.name and 'Indoor' not in outfit.name]) 299 | for bone in [bone for bone in armature.data.bones if bone.layers[10]]: 300 | no_move_bone = False 301 | for this_prefix in dont_move_these: 302 | if this_prefix in bone.name: 303 | no_move_bone = True 304 | if not no_move_bone and vertexWeightMap.get(bone.name): 305 | try: 306 | outfit_id_array = bone['KKBP outfit ID'].to_list() 307 | outfit_id_array.append(outfit_or_hair['KKBP outfit ID']) 308 | bone['KKBP outfit ID'] = outfit_id_array 309 | except: 310 | bone['KKBP outfit ID'] = [outfit_or_hair['KKBP outfit ID']] 311 | 312 | #move accessory bones to armature layer 10 313 | for bone in [bone for bone in armature.data.bones if bone.get('KKBP outfit ID')]: 314 | set_armature_layer(bone.name, show_layer = 9) 315 | 316 | bpy.ops.object.mode_set(mode='POSE') 317 | bpy.ops.pose.select_all(action='DESELECT') 318 | 319 | class clean_armature(bpy.types.Operator): 320 | bl_idname = "kkb.cleanarmature" 321 | bl_label = "Clean armature" 322 | bl_description = "Makes the armature less of an eyesore" 323 | bl_options = {'REGISTER', 'UNDO'} 324 | 325 | def execute(self, context): 326 | try: 327 | last_step = time.time() 328 | 329 | kklog('\nCategorizing bones into armature layers...', 'timed') 330 | 331 | reorganize_armature_layers() 332 | if context.scene.kkbp.categorize_dropdown in ['A', 'B']: 333 | visually_connect_bones() 334 | move_accessory_bones(context) 335 | 336 | kklog('Finished in ' + str(time.time() - last_step)[0:4] + 's') 337 | 338 | return {'FINISHED'} 339 | except: 340 | kklog('Unknown python error occurred', type = 'error') 341 | kklog(traceback.format_exc()) 342 | self.report({'ERROR'}, traceback.format_exc()) 343 | return {"CANCELLED"} 344 | if __name__ == "__main__": 345 | bpy.utils.register_class(clean_armature) 346 | 347 | # test call 348 | print((bpy.ops.kkb.cleanarmature('INVOKE_DEFAULT'))) 349 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ### Changes for V6.0.0 2 | Huge feature and usability updates by **MediaMoots**! 3 | * The KKBP exporter now works in Koikatsu Sunshine! 4 | * The KKBP Blender plugin now works in Blender 3.1+! 5 | * All outfits are now exported! 6 | * These are automatically exported from Koikatsu 7 | * These are available as hidden objects after the model is imported into Blender. They are parented to the armature 8 | * If you don't want to use these, you can shorten your import time by deleting the "Outfit ##" folder from the export folder 9 | * Alternate clothing states (shift / hang state) can now be exported! 10 | * Export these by checking the "Export Variations" box in Koikatsu 11 | * These are available as hidden objects after the model is imported into Blender. They are parented to the outfit object 12 | * Hitboxes can now be exported! 13 | * Export these by checking the "Export Hit Meshes" box in Koikatsu 14 | * These are placed in their own collection when the model is imported into Blender 15 | * The tears object is now exported and available as new shapekeys on the body object! 16 | * These are parented to the body 17 | * The tears material also has settings to allow minor color edits 18 | * The eye gag object for heart eyes, firey eyes, etc is now exported and available as new shapekeys on the body object! 19 | * These are parented to the body 20 | * The shapekeys will automatically hide the eyes and eyeline materials when active 21 | * The swirly eye rotation speed, heart eye pulse speed and cry/fire eye animation speed can be changed in the Eye Gag materials 22 | * The animated tongue is now exported! 23 | * This is parented to the body object and hidden by default 24 | * The rigged tongue doesn't use shapekeys like the rest of the face does 25 | * Shapekeys are more accurate than before! 26 | * The heart and sparkle Eye overlays are now exported 27 | * Eyewhite shapekeys are fixed on the exporter-side now! 28 | * This means Blender is less likely to crash when importing the model 29 | * Small fangs are now exported! 30 | * Accessories are now automatically linked to the correct limb bone! 31 | * Eye and overlay textures are now scaled automatically! 32 | * Hair shine, eyeshadow, nipple and underhair UVmaps are now exported! 33 | * Thanks to that, these items no longer need to be set and scaled manually for each character 34 | * Converted Normal Maps for use in Unreal and Unity are now exported 35 | * Separated objects can now be exported with the "Export separated meshes" button 36 | * A lot of character info is now exported 37 | * Check the .json files in the export folder for info on materials, accessories, objects, renderers and bones 38 | * Exported character heights are 4% more accurate 39 | * Texture suffixes are shortened 40 | * Image names over 64 characters long would cause blender to cufoff the filename, so this means long texture names are less likely to cause issues during import 41 | * The Koikatsu / BepInEx console will now be print out each mesh being exported. If the exporter is not working for a specific character, accessory or clothing item, you can use this to track down what is causing the exporter to fail. 42 | * These messages are prefixed with [Info : Console] 43 | 44 | Rigify armature updates by **an anonymous contributor**! 45 | * Better Penetration bones will now be placed in the "Torso (Tweak)" layer when converting to the Rigify armature 46 | * You need to use the Better Penetration armature type in Koikatsu for these bones to appear 47 | 48 | Unity normal blending and mirrored blush scaling by **poisenbery**! 49 | * Unity normal blending is an alternative normal map detail blending method that can be accessed in the "Raw shading" group 50 | * Mirrored blush scaling is an easier way to scale the blush and eyeshadow if the texture is symmetrical. This can be accessed in the "Blush positioning" group on the Face material 51 | 52 | Better face normals using shader nodes! 53 | * The face now uses a bastardized version of the Generated Face Normals setup [described in this post by **aVersionOfReality**](https://www.aversionofreality.com/blog/2021/9/5/clean-toon-shading) 54 | * This setup is disabled by default for performance reasons. Enable it by going to the face material > swap the "Raw Shading" node group to "Raw Shading (face)" 55 | * This setup only works in Blender 56 | * The GFN Empty position and scale can be edited by unhiding it. It's parented to the armature 57 | * The GFN options can be edited in the node group called "Generated Face Normals" inside of the the "Raw shading (face)" group 58 | 59 | Plus some misc changes to the Blender plugin: 60 | * Added a one-click option for importing models! 61 | * Hair is now separated from the model automatically, so the entire import process has been reduced to a single button 62 | * The behaviour from V5.0 (where you can separate objects as you please) can still be accessed by changing the "Don't pause to categorize" option on the upper right to "Pause to categorize", then pressing the "Finish categorization" button when you're done. 63 | * The main plugin UI can now be fully translated! 64 | * Current languages: English, Japanese (日本語) 65 | * Material baking is now done [through the use of Geometry Nodes!](https://blender.stackexchange.com/questions/231662/) 66 | * This works with multiple UV maps, so you no longer need to create a new mesh for hair highlights or anything that uses a separate uv map 67 | * You can also speed up the baking process by skipping the dark and normal bakes if you don't want them 68 | * Exporting can now be done without installing the CATS addon 69 | * Material Combiner is still required if you want a material atlas 70 | * Added a simplification choices menu to the export prep button 71 | * Removed the vanilla armature type toggle in favor of a menu 72 | * There's four options to choose from. Check the tooltip for a brief description of each option 73 | * The ability to switch between armature types after import was removed 74 | * Shapekeys on clothes and hair objects are now deleted 75 | * Shapekeys only affect the face, so these weren't needed anyway 76 | * The Eye, Eyebrow, Eyewhite, Eyeline and Nose materials are now marked as freestyle faces by default (for freestyle exclusion) 77 | * Lipstick and Flush textures are now loaded into the face material 78 | * Added a safe for work mode toggle that probably works 79 | * The permalight/permadark texture has been merged into one file 80 | * Python errors are now copied to the KK Log in the Scripting tab on the top 81 | * The import directory string listed in the KK Log is now censored if it detects your Windows username 82 | * The plugin now uses an HDRI from polyhaven 83 | * All KKBP panel options are now visible by default 84 | * The KKBP panel will now gray out some buttons after a model is imported 85 | 86 | And many bugfixes 87 | 88 | ### Changes for V5.1.0 89 | * Added per-character light linking to the KK shader 90 | * If you have multiple characters in a scene, this allows you to "link" a light to a character, so you can achieve ideal lighting for each character and not have to fiddle with a global lighting setup that affects all characters at once. This works for up to three characters / light sources. It's enabled by default and can be found inside of the Raw Shading group. 91 | * Usage: Make sure the "Light linking options (open me)" group framed in pink has it's slider set to 1, and any red lights will light your character. Import a second character, return to the ""Light linking options (open me)" group, and open it. Change the output of the pink "Match RGB output and sunlight color for light linking" frame from R to G. Any green lights will light the second character, but not the first character. Using a yellow light will light both R and G characters. Using a white light will light all characters. 92 | * Added reaction to colored light to the KK shader 93 | * When you use a light that isn't pure white, the colors on the model will become affected by the color of the light. This is disabled by default and can be found inside of the Raw Shading group. Colored lights and light linking can't be used at the same time. 94 | * Usage: Make sure the "Light linking options (open me)" group framed in pink has it's slider set to 0, and any colored lights will add additional color to your character. Any white lights will light the model like normal. 95 | * Updated Rigify scripts to the latest version (Feb 24th) 96 | * Overlay masks for Body and Face shaders now retain their original color. 97 | * The "Overlay color" inputs on the body/face shader that used to set the overlay color have been changed to "Overlay color multiplier" inputs. Keep this as a white color to preserve the overlay mask's original color. If the overlay is pure white itself, the color on the multiplier will just set the color of the overlay. 98 | * Added an image slot for the "plain" version of the maintex for clothing items. 99 | * This can be accessed by going into the clothing shader group and setting the "Use colored maintex?" slider to zero 100 | * If there's no plain version available, you'll get a pink placeholder texture as a warning 101 | * The permanant light and dark image masks in the raw shading group have been moved to the green texture group 102 | * This removes the need to create a unique raw shading group for every material. 103 | * Bugfixes ([#94](https://github.com/FlailingFog/KK-Blender-Porter-Pack/issues/94), [#90](https://github.com/FlailingFog/KK-Blender-Porter-Pack/issues/90), [#105](https://github.com/FlailingFog/KK-Blender-Porter-Pack/issues/105), [#114](https://github.com/FlailingFog/KK-Blender-Porter-Pack/issues/114)) 104 | 105 | ### Changes for V5.0.1 106 | * Bugfixes (see [#106](https://github.com/FlailingFog/KK-Blender-Porter-Pack/pull/106), [#95](https://github.com/FlailingFog/KK-Blender-Porter-Pack/issues/95)) 107 | 108 | ### Changes for V5.0.0 109 | * New export plugin, image saturation functions, and automated color setting by **MediaMoots**! 110 | * These make the import process more than twice as fast as the previous version! 111 | * Color data from the game is now extracted and applied to the shaders automatically during the import process 112 | * Textures are now automatically saturated to replicate the look of the in-game saturation filter 113 | * More textures are extracted than before 114 | * The new export plugin fixes common bugs involving broken charamaker functionality, shapekeys and accessories 115 | * Rigify compatible armature conversion by an anonymous contributor! 116 | * Requires Rigify and the "auto-execute scripts" setting to be enabled 117 | * Adds IK toggles for fingers, skirts, and accessories 118 | * Improves the eye controls with extra features like single eye manipulation and eye target tracking 119 | * Adds FK <-> IK switches to limb bones 120 | * Makes more armature controls accessible 121 | * Better joint correction bones! 122 | * Taken from **johnbbob_la_petite** on the Koikatsu Discord 123 | * Better viewport performance using preview quality normal nodes! 124 | * Taken from **pcanback** [on blenderartists](https://blenderartists.org/t/way-faster-normal-map-node-for-realtime-animation-playback-with-tangent-space-normals/1175379) 125 | * Extracted in-game animation files can now be imported automatically! 126 | * New base armature! 127 | * Bones are now organized by type through armature layers 128 | * Toggle for keeping the stock KK armature 129 | * Accessory bones show up automatically now 130 | * Upgrades to the export process! 131 | * Added a button for exporting prep 132 | * Smarter baking script 133 | * Rebaking materials at higher resolutions is easier now 134 | * Support for baking and atlasing normal maps 135 | * KK shader.blend is now integrated into the plugin! 136 | * Easy slots for forcing light and dark colors! 137 | * Easy slots for makeup and body paint! 138 | * Easy slots for patterns! 139 | * Support for colorized normal maps! 140 | * Automatic separation of Eyes and Eyebrow materials! 141 | * This allows you to make the Eyes and Eyebrows show through the hair using Cryptomatte and other compositor features 142 | * Shader-to-RGB nodes added to clothing color inputs! 143 | * This allows you to plug metal and other types of materials into clothing colormask inputs 144 | 145 | ### Changes for V4.3.1: 146 | * Renamed the spine bones during the "1) Run right after importing" script 147 | * CATS was not detecting the Spine, Chest and Upper Chest bones correctly. This resulted in the spine and chest bones being merged into one bone with awkward spine bends. Renaming the bones lets CATS detect the three bones correctly. 148 | 149 | ### Changes for V4.3.0: 150 | * Added a new button to the KK Panel: **Import Studio Object** 151 | * This script speeds up the import process for an accessory, studio object, or studio map that has been exported as an fbx file with SB3U 152 | * Example usage video: https://www.youtube.com/watch?v=PeryYTsAN6E 153 | * The shader applied to the object can be left as the default, changed to an emission shader, changed to the KK Shader, or changed to a custom user-defined node group 154 | * Added a new button to the KK Panel: **Link Shapekeys** 155 | * If the Eyebrows material is separated from the body object, this script will allow you to link the shapekeys values on the Body object to the Eyebrows object 156 | * Example usage video: https://www.youtube.com/watch?v=sqEBau1enWE 157 | * (click this button instead of running the script in the editor as shown) 158 | * This can be used for the Eyes and Eyeline materials as well 159 | * Script source: https://blender.stackexchange.com/questions/86757/ 160 | * Added a Center bone back to the armature 161 | * This bone lets you move all the bones on the armature without moving the armature's object location 162 | * Added a Pelvis bone back to the armature 163 | * This bone lets you move the pelvis and legs without moving the upper spine 164 | * Edited the armature bone tree a little 165 | * Changed the bone widget shapes in the KK shader to be slightly more rigify-like 166 | * Fixed the orientation of the top skirt bones 167 | * Gave the face material a separate outline 168 | * The "Select unused bones" button now unparents the Hips bone from the new Center bone 169 | * Leaving the Hips bone parented caused import issues in Unity 170 | 171 | ### Changes for V4.2.3: 172 | * Made eyewhite operations for the shapekeys optional through a toggle 173 | * Fixed the Basis shapekey being deleted when not set to English 174 | 175 | ### Changes for V4.2.2: 176 | * The plugin now works in Blender 2.93 177 | * The plugin should now work when Blender's interface is set to any language 178 | * Added lazy crash prevention to the shapekeys button 179 | 180 | ### Changes for V4.2.1: 181 | * Bake button wasn't assigned correctly in the new panel layout 182 | 183 | ### Changes for V4.2: 184 | * The eyewhites shapekeys are now used when generating the KK shapekeys 185 | * This will prevent gaps between the eyewhite and the lower eyelash for certain expressions 186 | * The plugin will now recognize the *KK Better Penetration* body type 187 | * Toes are placed on armature layer 2 188 | * BP bones are placed on armature layer 3 189 | * Materials without an image texture should properly bake now 190 | * This will ensure fully transparent materials and materials that are a solid color (but still use the KK Shader) are baked 191 | * The Separate Body button will now attempt to separate the shadowcast and bonelyfans meshes automatically, then shove them into a collection 192 | * The Import Textures button will now check if the user has actually selected the Textures folder and will abort if they chose the wrong folder 193 | * This was done to prevent texture loading issues 194 | * This check can be disabled with a toggle 195 | * Adjusted panel layout 196 | * Added better joint correction for the hip bone 197 | * In order to use them, the "Enable hip drivers" toggle needs to be enabled before pressing the "5 Add bone drivers" button 198 | * Better descriptions of what each toggle does 199 | * Made using multiple outlines optional through a toggle 200 | * The hair object can now be named either "hair" or "Hair" 201 | 202 | ### Changes for V4.1.2: 203 | * Added an easier way to scale and move UVs with the "Scale Vector" node group. 204 | * This replaces the mapping node setup used for the eyes, blush, etc 205 | * Materials that have a maintex or alphamask will now get their own outline material 206 | * This should prevent transparency issues with the outline 207 | * Added a heel controller to the armature 208 | * This is for rotating the foot bone while keeping the toes in place; a "tip toe" bone. 209 | * Added HSV sliders for the maintex of general/clothes materials 210 | * Fixed the nsfw texture parser node group in the body shader 211 | * The Bone Widgets collection is now hidden automatically 212 | * Fix bake script to account for world lighting 213 | * Fixed the foot IK 214 | * Moved the "MouthCavity" bone to the secondary layer 215 | * Moved supporting bones to their own armature layers instead of hiding them(layers 17, 18, and 19) 216 | * Relocated bone scaling and armature layer script operations to different buttons 217 | * Moved debug mode toggles to the shapekeys/drivers buttons 218 | 219 | ### Changes for V4.0.6: 220 | * Fix male body material not being recognized by the scripts 221 | * Added sliders to the body shader to control optional genital textures 222 | * The genital texture can be loaded into the "NSFW Textures" node group in the body template 223 | * The file location of the genital texture (cf_body_00_t) depends on what body mod you're using 224 | * Added an example for materials that are partially metal to the KK Shader.blend 225 | 226 | ### Changes for V4.0.5: 227 | * Quick fix for an issue with the Import Textures button 228 | * If the body alpha mask did not exist in the "Textures" folder, the script would not handle it correctly 229 | 230 | ### Changes for V4.0.4: 231 | * Put all the scripts into a blender plugin so the user can press a button instead of having to copy paste scripts 232 | * Added a button that applies the material templates to the model automatically 233 | * These templates can be edited in the KK Shader.blend 234 | * Added a button that applies the textures to the model automatically 235 | * *Grey's Mesh Exporter plugin for Koikatsu* is required in order for this to work 236 | * Colors still need to be set manually 237 | * This works 90% of the time, so a handful of textures might need to be loaded in manually 238 | * Added custom shapes for the armature to make the important bones more obvious 239 | * These shapes can be edited in the KK Shader.blend 240 | * These can be disabled in the armature tab (Armature tab > Viewport Display > Shapes) 241 | * Added second bone layer on the armature for skirt, face and eyebrow bones 242 | * Multiple bone layers can be made visible at once by shift selecting the layer in the Armature > Skeleton section 243 | -------------------------------------------------------------------------------- /importing/shapekeys.py: -------------------------------------------------------------------------------- 1 | ''' 2 | SHAPEKEYS SCRIPT 3 | - Renames the face shapekeys to english 4 | - Creates new, full shapekeys using the existing partial shapekeys 5 | - Deletes the partial shapekeys if keep_partial_shapekeys is not set to True 6 | ''' 7 | 8 | import bpy, time,traceback 9 | import bmesh 10 | from .importbuttons import kklog 11 | 12 | ################################ 13 | #Translate most of the shapekeys 14 | def translate_shapekeys(): 15 | 16 | translation_dict = { 17 | #Prefixes 18 | "eye_face.f00": "Eyes", 19 | "kuti_face.f00": "Lips", 20 | "eye_siroL.sL00": "EyeWhitesL", 21 | "eye_siroR.sR00": "EyeWhitesR", 22 | "eye_line_u.elu00": "Eyelashes1", 23 | "eye_line_l.ell00": "Eyelashes2", 24 | "eye_naM.naM00": "EyelashesPos", 25 | "eye_nose.nl00": "NoseTop", 26 | "kuti_nose.nl00": "NoseBot", 27 | "kuti_ha.ha00": "Teeth", 28 | "kuti_yaeba.y00": "Fangs", 29 | "kuti_sita.t00": "Tongue", 30 | "mayuge.mayu00": "KK Eyebrows", 31 | "eye_naL.naL00": "Tear_big", 32 | "eye_naM.naM00": "Tear_med", 33 | "eye_naS.naS00": "Tear_small", 34 | 35 | #Prefixes (Yelan headmod exception) 36 | "namida_l": "Tear_big", 37 | "namida_m": "Tear_med", 38 | "namida_s": "Tear_small", 39 | 'tang.': 'Tongue', 40 | 41 | #Emotions (eyes and mouth) 42 | "_def_": "_default_", 43 | "_egao_": "_smile_", 44 | "_bisyou_": "_smile_sharp_", 45 | "_uresi_ss_": "_happy_slight_", 46 | "_uresi_s_": "_happy_moderate_", 47 | "_uresi_": "_happy_broad_", 48 | "_doki_ss_": "_doki_slight_", 49 | "_doki_s_": "_doki_moderate_", 50 | "_ikari_": "_angry_", 51 | "_ikari02_": "_angry_2_", 52 | "_sinken_": "_serious_", 53 | "_sinken02_": "_serious_1_", 54 | "_sinken03_": "_serious_2_", 55 | "_keno_": "_hate_", 56 | "_sabisi_": "_lonely_", 57 | "_aseri_": "_impatient_", 58 | "_huan_": "_displeased_", 59 | "_human_": "_displeased_", 60 | "_akire_": "_amazed_", 61 | "_odoro_": "_shocked_", 62 | "_odoro_s_": "_shocked_moderate_", 63 | "_doya_": "_smug_", 64 | "_pero_": "_lick_", 65 | "_name_": "_eating_", 66 | "_tabe_": "_eating_2_", 67 | "_kuwae_": "_hold_in_mouth_", 68 | "_kisu_": "_kiss_", 69 | "_name02_": "_tongue_out_", 70 | "_mogu_": "_chewing_", 71 | "_niko_": "_cartoon_mouth_", 72 | "_san_": "_triangle_", 73 | 74 | #Emotions (Eyes) 75 | "_winkl_": "_wink_left_", 76 | "_winkr_": "_wink_right_", 77 | "_setunai_": "_distress_", 78 | "_tere_": "_shy_", 79 | "_tmara_": "_bored_", 80 | "_tumara_": "_bored_", 81 | "_kurusi_": "_pain_", 82 | "_sian_": "_thinking_", 83 | "_kanasi_": "_sad_", 84 | "_naki_": "_crying_", 85 | "_rakutan_": "_dejected_", 86 | "_komaru_": "_worried_", 87 | "_gag": "_gageye", 88 | "_gyul_": "_squeeze_left_", 89 | "_gyur_": "_squeeze_right_", 90 | "_gyu_": "_squeeze_", 91 | "_gyul02_": "_squeeze_left_2_", 92 | "_gyur02_": "_squeeze_right_2_", 93 | "_gyu02_": "_squeeze_2_", 94 | 95 | #Emotions (Eyebrows) 96 | "_koma_": "_worried_", 97 | "_gimoL_": "_doubt_left_", 98 | "_gimoR_": "_doubt_right_", 99 | "_sianL_": "_thinking_left_", 100 | "_sianR_": "_thinking_right_", 101 | "_oko_": "_angry_", 102 | "_oko2L_": "_angry_left_", 103 | "_oko2R_": "_angry_right_", 104 | 105 | #Emotions extra 106 | "_s_": "_small_", 107 | "_l_": "_big_", 108 | 109 | #Emotions Yelan headmod exception 110 | 'T_Default': '_default_op', 111 | } 112 | 113 | body = bpy.data.objects['Body'] 114 | body.active_shape_key_index = 0 115 | 116 | originalExists = False 117 | for shapekey in bpy.data.shape_keys: 118 | for keyblock in shapekey.key_blocks: 119 | #check if the original shapekeys still exists 120 | if 'Basis' not in keyblock.name: 121 | if 'Lips' in keyblock.name: 122 | originalExists = True 123 | 124 | #rename original shapekeys 125 | for shapekey in bpy.data.shape_keys: 126 | for keyblock in shapekey.key_blocks: 127 | for key in translation_dict: 128 | if 'gageye' not in keyblock.name: 129 | keyblock.name = keyblock.name.replace(key, translation_dict[key]) 130 | 131 | try: 132 | #delete the KK shapekeys if the original shapekeys still exist 133 | if originalExists and 'KK ' in keyblock.name and 'KK Eyebrows' not in keyblock.name: 134 | body.active_shape_key_index = body.data.shape_keys.key_blocks.keys().index(keyblock.name) 135 | bpy.ops.object.shape_key_remove() 136 | 137 | #delete the 'bounse' shapekey that sometimes appears on pmx imports 138 | if 'bounse' in keyblock.name: 139 | body.active_shape_key_index = body.data.shape_keys.key_blocks.keys().index(keyblock.name) 140 | bpy.ops.object.shape_key_remove() 141 | except: 142 | #or not 143 | kklog("Couldn't delete shapekey: " + keyblock.name, 'error') 144 | pass 145 | 146 | 147 | ''' 148 | ######################################################## 149 | #Fix the right eyewhites/sirome shapekeys for pmx imports only 150 | def fix_eyewhite_shapekeys(): 151 | armature = bpy.data.objects['Armature'] 152 | 153 | bpy.ops.object.mode_set(mode = 'OBJECT') 154 | body = bpy.data.objects['Body'] 155 | body.active_shape_key_index = 0 156 | 157 | #Remove the ### tag on the second eyewhite material 158 | for mat in body.data.materials: 159 | if 'cf_m_sirome_00' in mat.name and len(mat.name) > 15: 160 | mat.name = 'cf_m_sirome_00.001' 161 | 162 | #Deselect all objects 163 | bpy.ops.object.select_all(action='DESELECT') 164 | #Select the Body object 165 | body.select_set(True) 166 | #and make it active 167 | bpy.context.view_layer.objects.active = body 168 | 169 | #merge the sirome materials into one 170 | try: 171 | #loop through all materials and get the two sirome materials 172 | currentMat=0 173 | eyewhitePos=0 174 | eyewhiteMats = [None,None] 175 | materialCount = len(body.data.materials.values())-1 176 | while currentMat <= materialCount: 177 | if 'cf_m_sirome_00' in body.data.materials[currentMat].name: 178 | eyewhiteMats[eyewhitePos] = body.data.materials[currentMat].name 179 | eyewhitePos+=1 180 | currentMat+=1 181 | 182 | bpy.context.object.active_material_index = body.data.materials.find(eyewhiteMats[0]) 183 | #The script will fail here if the sirome material was already merged 184 | while body.data.materials.find(eyewhiteMats[0]) > body.data.materials.find(eyewhiteMats[1]) and body.data.materials.find(eyewhiteMats[1]) != -1: 185 | bpy.ops.object.material_slot_move(direction='UP') 186 | 187 | body.data.materials[body.data.materials.find(eyewhiteMats[0])].name = 'cf_m_sirome_00.temp' 188 | body.data.materials[body.data.materials.find(eyewhiteMats[1])].name = 'cf_m_sirome_00' 189 | body.data.materials[body.data.materials.find('cf_m_sirome_00.temp')].name = 'cf_m_sirome_00.001' 190 | 191 | bpy.ops.object.mode_set(mode='EDIT') 192 | bpy.ops.mesh.select_all(action='DESELECT') 193 | bpy.context.object.active_material_index = body.data.materials.find(eyewhiteMats[0]) 194 | bpy.ops.object.material_slot_select() 195 | bpy.context.object.active_material_index = body.data.materials.find(eyewhiteMats[1]) 196 | bpy.ops.object.material_slot_assign() 197 | 198 | except: 199 | #the sirome material was already merged 200 | body.data.materials[body.data.materials.find(eyewhiteMats[0])].name = 'cf_m_sirome_00' 201 | pass 202 | 203 | #delete the right eyewhites mesh 204 | bpy.ops.object.mode_set(mode = 'EDIT') 205 | bpy.ops.mesh.select_all(action = 'DESELECT') 206 | bpy.context.object.active_material_index = body.data.materials.find('cf_m_sirome_00') 207 | bpy.ops.object.material_slot_select() 208 | 209 | #refresh the selection 210 | #bpy.ops.object.mode_set(mode = 'OBJECT') 211 | #bpy.ops.object.mode_set(mode = 'EDIT') 212 | bm = bmesh.from_edit_mesh(body.data) 213 | bm.select_flush_mode() 214 | body.data.update() 215 | 216 | bm = bmesh.from_edit_mesh(body.data) 217 | vgVerts = [v for v in bm.verts if v.select] 218 | 219 | for v in vgVerts: 220 | v.select = (v.co.x < 0) 221 | 222 | vgVerts = [v for v in bm.verts if v.select] 223 | 224 | bm.select_flush_mode() 225 | body.data.update() 226 | 227 | bmesh.ops.delete(bm, geom=vgVerts, context='VERTS') 228 | bmesh.update_edit_mesh(body.data) 229 | 230 | #assign the left eyewhites into a vertex group 231 | bpy.ops.mesh.select_all(action = 'DESELECT') 232 | bpy.context.object.active_material_index = body.data.materials.find('cf_m_sirome_00') 233 | bpy.ops.object.material_slot_select() 234 | 235 | bm = bmesh.from_edit_mesh(body.data) 236 | vgVerts = [v for v in bm.verts if v.select] 237 | 238 | bpy.ops.object.vertex_group_add() 239 | bpy.ops.object.vertex_group_assign() 240 | body.vertex_groups.active.name = "EyewhitesL" 241 | 242 | #duplicate the left eyewhites 243 | bpy.ops.mesh.duplicate_move(MESH_OT_duplicate={"mode":1}, TRANSFORM_OT_translate={"value":(0, 0, 0), "orient_type":'GLOBAL', "orient_matrix":((0, 0, 0), (0, 0, 0), (0, 0, 0)), "orient_matrix_type":'GLOBAL', "constraint_axis":(False, False, False), "mirror":False, "use_proportional_edit":False, "proportional_edit_falloff":'SMOOTH', "proportional_size":1, "use_proportional_connected":False, "use_proportional_projected":False, "snap":False, "snap_target":'CLOSEST', "snap_point":(0, 0, 0), "snap_align":False, "snap_normal":(0, 0, 0), "gpencil_strokes":False, "cursor_transform":False, "texture_space":False, "remove_on_cancel":False, "release_confirm":False, "use_accurate":False}) 244 | bpy.context.scene.tool_settings.transform_pivot_point = 'CURSOR' 245 | bpy.context.scene.cursor.location = (0.0, 0.0, 0.0) 246 | 247 | #and mirror it on the x axis to recreate the right eyewhites 248 | bm = bmesh.from_edit_mesh(body.data) 249 | vgVerts = [v for v in bm.verts if v.select] 250 | 251 | for v in vgVerts: 252 | v.co.x *=-1 253 | 254 | bm.select_flush_mode() 255 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) 256 | body.data.update() 257 | 258 | #remove eyewhiter vertices from eyewhiteL group 259 | bpy.ops.object.vertex_group_remove_from() 260 | 261 | #make eyewhiter vertex group 262 | bpy.ops.object.vertex_group_add() 263 | bpy.ops.object.vertex_group_assign() 264 | body.vertex_groups.active.name = "EyewhitesR" 265 | 266 | #then recalculate normals for the right group to avoid outline issues later on 267 | bpy.ops.object.material_slot_select() 268 | #why is this so fucking finicky 269 | bpy.ops.object.mode_set(mode = 'OBJECT') 270 | bpy.ops.object.mode_set(mode = 'EDIT') 271 | bpy.ops.mesh.normals_make_consistent(inside=False) 272 | bpy.ops.mesh.select_all(action='DESELECT') 273 | 274 | bpy.ops.object.mode_set(mode = 'OBJECT') 275 | 276 | #make eyewhiteL shapekeys only affect the eyewhiteL vertex group 277 | #ditto for right 278 | for shapekey in bpy.data.shape_keys: 279 | for keyblock in shapekey.key_blocks: 280 | if 'EyeWhitesL_' in keyblock.name: 281 | keyblock.vertex_group = 'EyewhitesL' 282 | elif 'EyeWhitesR_' in keyblock.name: 283 | keyblock.vertex_group = 'EyewhitesR' 284 | 285 | bpy.ops.object.mode_set(mode = 'EDIT') 286 | bpy.ops.mesh.select_all(action='DESELECT') 287 | bpy.ops.object.mode_set(mode = 'OBJECT') 288 | ''' 289 | 290 | ###################### 291 | #Combine the shapekeys 292 | def combine_shapekeys(keep_partial_shapekeys): 293 | #make the basis shapekey active 294 | body = bpy.data.objects['Body'] 295 | body.active_shape_key_index = 0 296 | 297 | def whatCat(keyName): 298 | #Eyelashes1 is used because I couldn't see a difference between the other one and they overlap if both are used 299 | #EyelashPos is unused because Eyelashes work better and it overlaps with Eyelashes 300 | 301 | eyes = [keyName.find("Eyes"), 302 | keyName.find("NoseT"), 303 | keyName.find("Eyelashes1"), 304 | keyName.find("EyeWhites"), 305 | keyName.find('Tear_big'), 306 | keyName.find('Tear_med'), 307 | keyName.find('Tear_small')] 308 | if not all(v == -1 for v in eyes): 309 | return 'Eyes' 310 | 311 | mouth = [keyName.find("NoseB"), 312 | keyName.find("Lips"), 313 | keyName.find("Tongue"), 314 | keyName.find("Teeth"), 315 | keyName.find("Fangs")] 316 | if not all(v==-1 for v in mouth): 317 | return 'Mouth' 318 | 319 | return 'None' 320 | 321 | #setup two arrays to keep track of the shapekeys that have been used 322 | #and the shapekeys currently in use 323 | used = [] 324 | inUse = [] 325 | 326 | #These mouth shapekeys require the default teeth and tongue shapekeys to be active 327 | correctionList = ['_u_small_op', '_u_big_op', '_e_big_op', '_o_small_op', '_o_big_op', '_neko_op', '_triangle_op'] 328 | shapekey_block = bpy.data.shape_keys[body.data.shape_keys.name].key_blocks 329 | 330 | ACTIVE = 0.9 331 | def activate_shapekey(key_act): 332 | if shapekey_block.get(key_act) != None: 333 | shapekey_block[key_act].value = ACTIVE 334 | 335 | #go through the keyblock list twice 336 | #Do eye shapekeys first then mouth shapekeys 337 | for type in ['Eyes_', 'Lips_']: 338 | 339 | counter = len(shapekey_block) 340 | for current_keyblock in shapekey_block: 341 | 342 | counter = counter - 1 343 | #print(counter) 344 | if (counter == 0): 345 | break 346 | 347 | #categorize the shapekey (eye or mouth) 348 | cat = whatCat(current_keyblock.name) 349 | 350 | #get the emotion from the shapekey name 351 | if (cat != 'None') and ('KK' not in current_keyblock.name) and (type in current_keyblock.name): 352 | emotion = current_keyblock.name[current_keyblock.name.find("_"):] 353 | 354 | #go through every shapekey to check if any match the current shapekey's emotion 355 | for supporting_shapekey in shapekey_block: 356 | 357 | #If the's emotion matches the current one and is the correct category... 358 | if emotion in supporting_shapekey.name and cat == whatCat(supporting_shapekey.name): 359 | 360 | #and this key has hasn't been used yet activate it, else skip to the next 361 | if (supporting_shapekey.name not in used): 362 | supporting_shapekey.value = ACTIVE 363 | inUse.append(supporting_shapekey.name) 364 | 365 | #The shapekeys for the current emotion are now all active 366 | 367 | #Some need manual corrections 368 | correction_needed = False 369 | for c in correctionList: 370 | if c in current_keyblock.name: 371 | correction_needed = True 372 | 373 | if correction_needed: 374 | activate_shapekey('Fangs_default_op') 375 | activate_shapekey('Teeth_default_op') 376 | activate_shapekey('Tongue_default_op') 377 | 378 | if ('_e_small_op' in current_keyblock.name): 379 | activate_shapekey('Fangs_default_op') 380 | activate_shapekey('Lips_e_small_op') 381 | 382 | if ('_cartoon_mouth_op' in current_keyblock.name): 383 | activate_shapekey('Tongue_default_op') 384 | activate_shapekey('Lips_cartoon_mouth_op') 385 | 386 | if ('_smile_sharp_op' in current_keyblock.name and cat == 'Mouth'): 387 | if shapekey_block.get('Teeth_smile_sharp_op1') != None: 388 | shapekey_block['Teeth_smile_sharp_op1'].value = 0 389 | activate_shapekey('Lips_smile_sharp_op') 390 | 391 | if ('_eating_2_op' in current_keyblock.name): 392 | activate_shapekey('Fangs_default_op') 393 | activate_shapekey('Teeth_tongue_out_op') 394 | activate_shapekey('Tongue_serious_2_op') 395 | activate_shapekey('Lips_eating_2_op') 396 | 397 | if ('_i_big_op' in current_keyblock.name): 398 | activate_shapekey('Teeth_i_big_cl') 399 | activate_shapekey('Fangs_default_op') 400 | activate_shapekey('Lips_i_big_op') 401 | 402 | if ('_i_small_op' in current_keyblock.name): 403 | activate_shapekey('Teeth_i_small_cl') 404 | activate_shapekey('Fangs_default_op') 405 | activate_shapekey('Lips_i_small_op') 406 | 407 | if (current_keyblock.name not in used): 408 | bpy.data.objects['Body'].shape_key_add(name=('KK ' + cat + emotion)) 409 | 410 | #make sure this shapekey set isn't used again 411 | used.extend(inUse) 412 | inUse =[] 413 | 414 | #reset all shapekey values 415 | for reset_keyblock in shapekey_block: 416 | reset_keyblock.value = 0 417 | 418 | #lazy crash prevention 419 | if counter % 20 == 0: 420 | bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1) 421 | 422 | #Delete all shapekeys that don't have a "KK" in their name 423 | #Don't delete the Basis shapekey though 424 | if not keep_partial_shapekeys: 425 | for remove_shapekey in shapekey_block: 426 | try: 427 | if ('KK ' not in remove_shapekey.name and remove_shapekey.name != shapekey_block[0].name): 428 | body.shape_key_remove(remove_shapekey) 429 | except: 430 | #I don't know if this needs to be in a try catch anymore 431 | kklog('Couldn\'t remove shapekey ' + remove_shapekey.name, 'error') 432 | pass 433 | 434 | #make the basis shapekey active 435 | body.active_shape_key_index = 0 436 | 437 | #and reset the pivot point to median 438 | bpy.context.scene.tool_settings.transform_pivot_point = 'MEDIAN_POINT' 439 | 440 | 441 | class shape_keys(bpy.types.Operator): 442 | bl_idname = "kkb.shapekeys" 443 | bl_label = "The shapekeys script" 444 | bl_description = "Fixes the shapekeys" 445 | bl_options = {'REGISTER', 'UNDO'} 446 | 447 | def execute(self, context): 448 | try: 449 | last_step = time.time() 450 | scene = context.scene.kkbp 451 | 452 | if scene.shapekeys_dropdown in ['A', 'B'] : 453 | keep_partial_shapekeys = scene.shapekeys_dropdown == 'B' 454 | 455 | kklog('\nTranslating and combining shapekeys...', type = 'timed') 456 | translate_shapekeys() 457 | combine_shapekeys(keep_partial_shapekeys) 458 | 459 | kklog('Finished in ' + str(time.time() - last_step)[0:4] + 's') 460 | 461 | return {'FINISHED'} 462 | except: 463 | kklog('Unknown python error occurred', type = 'error') 464 | kklog(traceback.format_exc()) 465 | self.report({'ERROR'}, traceback.format_exc()) 466 | return {"CANCELLED"} 467 | 468 | if __name__ == "__main__": 469 | bpy.utils.register_class(shape_keys) 470 | 471 | # test call 472 | print((bpy.ops.kkb.shapekeys('INVOKE_DEFAULT'))) 473 | -------------------------------------------------------------------------------- /extras/rigifyscripts/rigify_after.py: -------------------------------------------------------------------------------- 1 | 2 | #Switch to Object Mode and select Generated Rig 3 | 4 | import bpy 5 | import traceback 6 | import sys 7 | 8 | from . import commons as koikatsuCommons 9 | 10 | def main(): 11 | #koikatsuCommonsName = "Koikatsu Commons.py"; 12 | """ 13 | koikatsuCommonsPath = "C:\\Users\\UserName\\Desktop\\" 14 | 15 | text = bpy.data.texts.get(koikatsuCommonsName) 16 | if text is not None: 17 | bpy.data.texts.remove(text) 18 | text = bpy.data.texts.load(koikatsuCommonsPath + koikatsuCommonsName) 19 | koikatsuCommons = text.as_module() 20 | """ 21 | #koikatsuCommons = bpy.data.texts[koikatsuCommonsName].as_module() 22 | 23 | generatedRig = bpy.context.active_object 24 | 25 | assert generatedRig.mode == "OBJECT", 'assert generated_rig.mode == "OBJECT"' 26 | assert generatedRig.type == "ARMATURE", 'assert generatedRig.type == "ARMATURE"' 27 | 28 | bpy.ops.object.mode_set(mode='EDIT') 29 | 30 | for bone in generatedRig.data.edit_bones: 31 | if generatedRig.data.bones[bone.name].layers[koikatsuCommons.getRigifyLayerIndexByName(koikatsuCommons.junkLayerName)] == True: 32 | koikatsuCommons.deleteBone(generatedRig, bone.name) 33 | continue 34 | if generatedRig.data.bones[bone.name].layers[koikatsuCommons.defLayerIndex] == True: 35 | bone.use_deform = True 36 | 37 | generatedRig.data.edit_bones[koikatsuCommons.eyesTrackTargetParentBoneName].parent = None 38 | 39 | bpy.ops.object.mode_set(mode='OBJECT') 40 | 41 | """ 42 | for i in range(32): 43 | if i == 0 or i == 1: 44 | generatedRig.data.layers[i] = True 45 | else: 46 | generatedRig.data.layers[i] = False 47 | """ 48 | 49 | body = bpy.data.objects[koikatsuCommons.bodyName] 50 | leftEyeModifier = body.modifiers["Left Eye UV warp"] 51 | leftEyeModifier.object_from = generatedRig 52 | leftEyeModifier.bone_from = koikatsuCommons.eyesXBoneName 53 | leftEyeModifier.object_to = generatedRig 54 | leftEyeModifier.bone_to = koikatsuCommons.leftEyeBoneName 55 | rightEyeModifier = body.modifiers["Right Eye UV warp"] 56 | rightEyeModifier.object_from = generatedRig 57 | rightEyeModifier.bone_from = koikatsuCommons.eyesXBoneName 58 | rightEyeModifier.object_to = generatedRig 59 | rightEyeModifier.bone_to = koikatsuCommons.rightEyeBoneName 60 | 61 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.eyesTrackTargetBoneName, 1.5) 62 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.headTweakBoneName, 1.1) 63 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.crotchBoneName, 25) 64 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.anusBoneName, 3.5) 65 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastDeformBone1Name, 0.4) 66 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastDeformBone1Name, 0.4) 67 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastBone2Name, 3) 68 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastBone2Name, 3) 69 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastDeformBone2Name, 0.3) 70 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastDeformBone2Name, 0.3) 71 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastBone3Name, 2) 72 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastBone3Name, 2) 73 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastDeformBone3Name, 0.2) 74 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastDeformBone3Name, 0.2) 75 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftNippleBone1Name, 1) 76 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightNippleBone1Name, 1) 77 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftNippleDeformBone1Name, 0.1) 78 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightNippleDeformBone1Name, 0.1) 79 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftNippleBone2Name, 0.5) 80 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightNippleBone2Name, 0.5) 81 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftNippleDeformBone2Name, 0.05) 82 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightNippleDeformBone2Name, 0.05) 83 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftShoulderBoneName, 1.25) 84 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightShoulderBoneName, 1.25) 85 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftThumbBone1Name, 1.3) 86 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightThumbBone1Name, 1.3) 87 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftThumbBone2Name, 1.3) 88 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightThumbBone2Name, 1.3) 89 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftThumbBone3Name, 1.2) 90 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightThumbBone3Name, 1.2) 91 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftIndexFingerBone2Name, 1.1) 92 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightIndexFingerBone2Name, 1.1) 93 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftIndexFingerBone3Name, 1.1) 94 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightIndexFingerBone3Name, 1.1) 95 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftMiddleFingerBone2Name, 1.1) 96 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightMiddleFingerBone2Name, 1.1) 97 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftMiddleFingerBone3Name, 1.1) 98 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightMiddleFingerBone3Name, 1.1) 99 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftRingFingerBone2Name, 1.1) 100 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightRingFingerBone2Name, 1.1) 101 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftRingFingerBone3Name, 1.1) 102 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightRingFingerBone3Name, 1.1) 103 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftLittleFingerPalmBoneName, 1.1) 104 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightLittleFingerPalmBoneName, 1.1) 105 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftLittleFingerPalmFkBoneName, 1.1) 106 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightLittleFingerPalmFkBoneName, 1.1) 107 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftLittleFingerBone2Name, 1.1) 108 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightLittleFingerBone2Name, 1.1) 109 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftLittleFingerBone3Name, 1.1) 110 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightLittleFingerBone3Name, 1.1) 111 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rootBoneName, 0.35) 112 | 113 | for bone in generatedRig.pose.bones: 114 | if generatedRig.data.bones[bone.name].layers[koikatsuCommons.getRigifyLayerIndexByName(koikatsuCommons.faceLayerName + koikatsuCommons.mchLayerSuffix)] == True and bone.name not in koikatsuCommons.faceMchLayerBoneNames: 115 | koikatsuCommons.setBoneCustomShapeScale(generatedRig, bone.name, 0.15) 116 | 117 | """ 118 | bpy.ops.object.mode_set(mode='EDIT') 119 | 120 | sourceBoneNameAndBoneWidgetHolderNameTuples = [] 121 | 122 | def createOffsetBoneWidgetHolder(rig, sourceBoneName, vertexGroupExtremities, xFactor, yFactor, zFactor): 123 | boneWidgetHolderName = koikatsuCommons.getBoneWidgetHolderName(sourceBoneName) 124 | boneWidgetHolder = koikatsuCommons.copyBone(rig, sourceBoneName, boneWidgetHolderName) 125 | sourceBone = rig.data.edit_bones.get(sourceBoneName) 126 | if xFactor is not None: 127 | vertexGroupPosX = (vertexGroupExtremities.maxX - vertexGroupExtremities.minX) * xFactor + vertexGroupExtremities.minX 128 | boneWidgetHolder.head.x = vertexGroupPosX 129 | else: 130 | boneWidgetHolder.head.x = sourceBone.head.x 131 | if yFactor is not None: 132 | vertexGroupPosY = (vertexGroupExtremities.maxY - vertexGroupExtremities.minY) * yFactor + vertexGroupExtremities.minY 133 | boneWidgetHolder.head.y = vertexGroupPosY 134 | else: 135 | boneWidgetHolder.head.y = sourceBone.head.y 136 | if zFactor is not None: 137 | vertexGroupPosZ = (vertexGroupExtremities.maxZ - vertexGroupExtremities.minZ) * zFactor + vertexGroupExtremities.minZ 138 | boneWidgetHolder.head.z = vertexGroupPosZ 139 | else: 140 | boneWidgetHolder.head.z = sourceBone.head.z 141 | boneWidgetHolder.tail = sourceBone.tail - (sourceBone.head - boneWidgetHolder.head) 142 | boneWidgetHolder.roll = sourceBone.roll 143 | sourceBoneNameAndBoneWidgetHolderNameTuples.append((sourceBoneName, boneWidgetHolderName)) 144 | return boneWidgetHolder 145 | 146 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftShoulderBoneName, koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.originalShoulderDeformBoneName, koikatsuCommons.bodyName), 0.1, 0.6, 0.66) 147 | armDeformBone1Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.armDeformBone1Name, koikatsuCommons.bodyName) 148 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.armTweakBone1Name, armDeformBone1Extremities, 0.33, None, None) 149 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.armParentBoneName, armDeformBone1Extremities, 0.33, None, None) 150 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.armIkBoneName, armDeformBone1Extremities, 0.33, None, None) 151 | wristDeformBoneExtremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.wristDeformBoneName, koikatsuCommons.bodyName) 152 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.wristFkBoneName, wristDeformBoneExtremities, None, 0.57, 0.53) 153 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.wristIkBoneName, wristDeformBoneExtremities, None, 0.57, 0.53) 154 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.wristTweakBoneName, wristDeformBoneExtremities, None, 0.57, 0.53) 155 | thumbDeformBone1Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.thumbDeformBone1Name, koikatsuCommons.bodyName) 156 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftThumbBo1Name, thumbDeformBone1Extremities, 0.15, 0.55, 0.5) 157 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.thumbMasterBoneName, thumbDeformBone1Extremities, 0.15, 0.55, 0.5) 158 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftThumbBo2Name, koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.thumbDeformBone2Name, koikatsuCommons.bodyName), 0.42, 0.47, 0.3) 159 | thumbDeformBone3Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.thumbDeformBone3Name, koikatsuCommons.bodyName) 160 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftThumbBo3Name, thumbDeformBone3Extremities, 0.3, 0.6, 0.55) 161 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.thumbTweakBoneName, thumbDeformBone3Extremities, 0.97, 0.03, 0.35) 162 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.thumbIkBoneName, thumbDeformBone3Extremities, 0.97, 0.03, 0.35) 163 | leftIndexFingerDeformBone1Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftIndexFingerDeformBone1Name, koikatsuCommons.bodyName) 164 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftIndexFingerBone1Name, leftIndexFingerDeformBone1Extremities, None, 0.5, 0.4) 165 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftIndexFingerMasterBoneName, leftIndexFingerDeformBone1Extremities, None, 0.5, 0.4) 166 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftIndexFingerBone2Name, koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftIndexFingerDeformBone2Name, koikatsuCommons.bodyName), None, 0.45, 0.47) 167 | leftIndexFingerDeformBone3Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftIndexFingerDeformBone3Name, koikatsuCommons.bodyName) 168 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftIndexFingerTweakBoneName, leftIndexFingerDeformBone3Extremities, 1, None, 0.6) 169 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftIndexFingerIkBoneName, leftIndexFingerDeformBone3Extremities, 1, None, 0.6) 170 | leftMiddleFingerDeformBone1Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftMiddleFingerDeformBone1Name, koikatsuCommons.bodyName) 171 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftMiddleFingerBone1Name, leftMiddleFingerDeformBone1Extremities, None, None, 0.4) 172 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftMiddleFingerMasterBoneName, leftMiddleFingerDeformBone1Extremities, None, None, 0.4) 173 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftMiddleFingerBone2Name, koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftMiddleFingerDeformBone2Name, koikatsuCommons.bodyName), None, None, 0.5) 174 | leftMiddleFingerDeformBone3Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftMiddleFingerDeformBone3Name, koikatsuCommons.bodyName) 175 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftMiddleFingerTweakBoneName, leftMiddleFingerDeformBone3Extremities, 1, None, 0.6) 176 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftMiddleFingerIkBoneName, leftMiddleFingerDeformBone3Extremities, 1, None, 0.6) 177 | leftRingFingerDeformBone1Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftRingFingerDeformBone1Name, koikatsuCommons.bodyName) 178 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftRingFingerBone1Name, leftRingFingerDeformBone1Extremities, None, None, 0.45) 179 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftRingFingerMasterBoneName, leftRingFingerDeformBone1Extremities, None, None, 0.45) 180 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftRingFingerBone2Name, koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftRingFingerDeformBone2Name, koikatsuCommons.bodyName), None, None, 0.5) 181 | leftRingFingerDeformBone3Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftRingFingerDeformBone3Name, koikatsuCommons.bodyName) 182 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftRingFingerBone3Name, leftRingFingerDeformBone3Extremities, None, 0.5, 0.5) 183 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftRingFingerTweakBoneName, leftRingFingerDeformBone3Extremities, 1, None, 0.6) 184 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftRingFingerIkBoneName, leftRingFingerDeformBone3Extremities, 1, None, 0.6) 185 | leftLittleFingerDeformBone1Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftLittleFingerDeformBone1Name, koikatsuCommons.bodyName) 186 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftLittleFingerBone1Name, leftLittleFingerDeformBone1Extremities, None, None, 0.4) 187 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftLittleFingerMasterBoneName, leftLittleFingerDeformBone1Extremities, None, None, 0.4) 188 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftLittleFingerBone2Name, koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftLittleFingerDeformBone2Name, koikatsuCommons.bodyName), None, 0.535, 0.52) 189 | leftLittleFingerDeformBone3Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.leftLittleFingerDeformBone3Name, koikatsuCommons.bodyName) 190 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftLittleFingerTweakBoneName, leftLittleFingerDeformBone3Extremities, 1, None, 0.6) 191 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.leftLittleFingerIkBoneName, leftLittleFingerDeformBone3Extremities, 1, None, 0.6) 192 | thighDeformBone1Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.thighDeformBone1Name, koikatsuCommons.bodyName) 193 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.legTweakBone1Name, thighDeformBone1Extremities, None, 0.45, 0.66) 194 | thighDeformBone2Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.thighDeformBone2Name, koikatsuCommons.bodyName) 195 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.legTweakBone2Name, thighDeformBone2Extremities, None, 0.5, None) 196 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.legTweakBone3Name, koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.thighDeformBone3Name, koikatsuCommons.bodyName), None, 0.5, None) 197 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.legParentBoneName, thighDeformBone1Extremities, None, 0.45, 0.66) 198 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.legIkBoneName, thighDeformBone1Extremities, None, 0.45, 0.66) 199 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.legFkBoneName, thighDeformBone2Extremities, None, 0.5, None) 200 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.kneeTweakBone1Name, koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.legDeformBone1Name, koikatsuCommons.bodyName), None, 0.45, None) 201 | legDeformBone2Extremities = koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.legDeformBone2Name, koikatsuCommons.bodyName) 202 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.kneeTweakBone2Name, legDeformBone2Extremities, None, 0.6, None) 203 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.kneeTweakBone3Name, koikatsuCommons.findVertexGroupExtremities(koikatsuCommons.legDeformBone3Name, koikatsuCommons.bodyName), None, 0.62, None) 204 | createOffsetBoneWidgetHolder(generatedRig, koikatsuCommons.kneeFkBoneName, legDeformBone2Extremities, None, 0.45, None) 205 | 206 | bpy.ops.object.mode_set(mode='OBJECT') 207 | 208 | for tuple in sourceBoneNameAndBoneWidgetHolderNameTuples: 209 | sourceBoneName = tuple[0] 210 | boneWidgetHolderName = tuple[1] 211 | copyTransformsConstraint = generatedRig.pose.bones[boneWidgetHolderName].constraints.new('COPY_TRANSFORMS') 212 | copyTransformsConstraint.target = generatedRig 213 | copyTransformsConstraint.subtarget = sourceBoneName 214 | copyTransformsConstraint.owner_space = 'LOCAL' 215 | copyTransformsConstraint.target_space = 'LOCAL' 216 | generatedRig.pose.bones[sourceBoneName].custom_shape_transform = generatedRig.pose.bones[boneWidgetHolderName] 217 | """ 218 | 219 | for constraint in generatedRig.pose.bones[koikatsuCommons.eyesTrackTargetParentBoneName].constraints: 220 | if constraint.type == 'ARMATURE': 221 | for target in constraint.targets: 222 | if target.subtarget: 223 | target.target = generatedRig 224 | 225 | for driver in bpy.data.objects[koikatsuCommons.bodyName].data.shape_keys.animation_data.drivers: 226 | if driver.data_path.startswith("key_blocks"): 227 | ownerName = driver.data_path.split('"')[1] 228 | if ownerName == koikatsuCommons.eyelidsShapeKeyCopyName: 229 | for variable in driver.driver.variables: 230 | for target in variable.targets: 231 | if target.bone_target: 232 | target.id = generatedRig 233 | 234 | for driver in generatedRig.animation_data.drivers: 235 | #print(driver.data_path) 236 | #print(driver.driver.variables[0].targets[0].bone_target) 237 | if driver.data_path.startswith("pose.bones"): 238 | ownerName = driver.data_path.split('"')[1] 239 | property = driver.data_path.rsplit('.', 1)[1] 240 | if ownerName in koikatsuCommons.bonesWithDrivers and property == "location": 241 | for variable in driver.driver.variables: 242 | for target in variable.targets: 243 | for targetToChange in koikatsuCommons.targetsToChange: 244 | if target.bone_target == koikatsuCommons.originalBonePrefix + targetToChange: 245 | target.bone_target = koikatsuCommons.deformBonePrefix + targetToChange 246 | 247 | #koikatsuCommons.setBoneManagerLayersFromRigifyLayers(generatedRig) 248 | 249 | class rigify_after(bpy.types.Operator): 250 | bl_idname = "kkb.rigafter" 251 | bl_label = "After Each Rigify Generate - Public" 252 | bl_description = 'why is the context wrong' 253 | bl_options = {'REGISTER', 'UNDO'} 254 | 255 | def execute(self, context): 256 | main() 257 | return {'FINISHED'} 258 | 259 | if __name__ == "__main__": 260 | bpy.utils.register_class(rigify_after) 261 | 262 | # test call 263 | print((bpy.ops.kkb.rigafter('INVOKE_DEFAULT'))) --------------------------------------------------------------------------------