├── .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 | 
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 | 
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')))
--------------------------------------------------------------------------------