feat: Add KoikatsuBlenderPipeline addon with import/export functionality

- Add core addon files (__init__.py, KKPanel.py, preferences.py, common.py)
- Add import pipeline with armature, mesh, and material modification modules
- Add export pipeline with material baking and FBX preparation utilities
- Add material combiner tool for texture atlas generation and optimization
- Add extras utilities for animation, rigging, and asset management
- Add bone orientation data from better_fbx for accurate skeletal structure
- Add comprehensive documentation and wiki with multi-language support (EN, JP, ZH)
- Add animation library retargeting lists for ARP and Rokoko motion capture
- Add Rigify integration scripts for advanced rigging workflows
- Add shader file (KK Shader V8.0.blend) for material rendering
- Add manifest and license files for addon distribution
- Add changelog documenting version history and improvements
- Initialize complete Blender addon project for Koikatsu character import/export
This commit is contained in:
2025-12-06 15:26:19 +08:00
commit 5d9e09e9c3
122 changed files with 22683 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,283 @@
{
"Rokoko_custom_names": true,
"version": 1,
"bones": {
"custom_bone_cf_j_hips": [
"cf_j_hips",
"torso"
],
"custom_bone_cf_j_spine01": [
"cf_j_spine01",
"Spine_fk"
],
"custom_bone_cf_j_spine02": [
"cf_j_spine02",
"Chest_fk"
],
"custom_bone_cf_j_spine03": [
"cf_j_spine03",
"Upper Chest_fk"
],
"custom_bone_cf_j_hand_L": [
"cf_j_hand_L",
"Left wrist_fk"
],
"custom_bone_cf_j_arm_L": [
"cf_j_arm00_L",
"Left arm_fk"
],
"custom_bone_cf_j_elbow_L": [
"cf_j_forearm01_L",
"Left elbow_fk"
],
"custom_bone_cf_j_index01_L": [
"cf_j_index01_L",
"IndexFinger1_L"
],
"custom_bone_cf_j_index02_L": [
"cf_j_index02_L",
"IndexFinger2_L"
],
"custom_bone_cf_j_index03_L": [
"cf_j_index03_L",
"IndexFinger3_L"
],
"custom_bone_cf_j_Little01_L": [
"cf_j_little01_L",
"LittleFinger1_L"
],
"custom_bone_cf_j_Little02_L": [
"cf_j_little02_L",
"LittleFinger2_L"
],
"custom_bone_cf_j_Little03_L": [
"cf_j_little03_L",
"LittleFinger3_L"
],
"custom_bone_cf_j_middle01_L": [
"cf_j_middle01_L",
"MiddleFinger1_L"
],
"custom_bone_cf_j_middle02_L": [
"cf_j_middle02_L",
"MiddleFinger2_L"
],
"custom_bone_cf_j_middle03_L": [
"cf_j_middle03_L",
"MiddleFinger3_L"
],
"custom_bone_cf_j_ring01_L": [
"cf_j_ring01_L",
"RingFinger1_L"
],
"custom_bone_cf_j_ring02_L": [
"cf_j_ring02_L",
"RingFinger2_L"
],
"custom_bone_cf_j_ring03_L": [
"cf_j_ring03_L",
"RingFinger3_L"
],
"custom_bone_cf_j_thumb01_L": [
"cf_j_thumb01_L",
"Thumb0_L"
],
"custom_bone_cf_j_thumb02_L": [
"cf_j_thumb02_L",
"Thumb1_L"
],
"custom_bone_cf_j_thumb03_L": [
"cf_j_thumb03_L",
"Thumb2_L"
],
"custom_bone_cf_j_arm_R": [
"cf_j_arm00_R",
"Right arm_fk"
],
"custom_bone_cf_j_elbow": [
"cf_j_forearm01_R",
"Right elbow_fk"
],
"custom_bone_cf_j_hand_R": [
"cf_j_hand_R",
"Right wrist_fk"
],
"custom_bone_cf_j_index01_R": [
"cf_j_index01_R",
"IndexFinger1_R"
],
"custom_bone_cf_j_index02_R": [
"cf_j_index02_R",
"IndexFinger2_R"
],
"custom_bone_cf_j_index03_R": [
"cf_j_index03_R",
"IndexFinger3_R"
],
"custom_bone_cf_j_Little01_R": [
"cf_j_little01_R",
"LittleFinger1_R"
],
"custom_bone_cf_j_Little02_R": [
"cf_j_little02_R",
"LittleFinger2_R"
],
"custom_bone_cf_j_Little03_R": [
"cf_j_little03_R",
"LittleFinger3_R"
],
"custom_bone_cf_j_middle01_R": [
"cf_j_middle01_R",
"MiddleFinger1_R"
],
"custom_bone_cf_j_middle02_R": [
"cf_j_middle02_R",
"MiddleFinger2_R"
],
"custom_bone_cf_j_middle03_R": [
"cf_j_middle03_R",
"MiddleFinger3_R"
],
"custom_bone_cf_j_ring01_R": [
"cf_j_ring01_R",
"RingFinger1_R"
],
"custom_bone_cf_j_ring02_R": [
"cf_j_ring02_R",
"RingFinger2_R"
],
"custom_bone_cf_j_ring03_R": [
"cf_j_ring03_R",
"RingFinger3_R"
],
"custom_bone_cf_j_thumb01_R": [
"cf_j_thumb01_R",
"Thumb0_R"
],
"custom_bone_cf_j_thumb02_R": [
"cf_j_thumb02_R",
"Thumb1_R"
],
"custom_bone_cf_j_thumb03_R": [
"cf_j_thumb03_R",
"Thumb2_R"
],
"custom_bone_cf_j_neck": [
"cf_j_neck",
"neck"
],
"custom_bone_cf_j_head": [
"cf_j_head",
"head"
],
"custom_bone_cf_j_waist01": [
"cf_j_waist01",
"Hips_fk"
],
"custom_bone_cf_j_foot_L": [
"cf_j_foot_L",
"Left ankle_fk"
],
"custom_bone_cf_j_toes_L": [
"cf_j_toes_L",
"Left toe_fk"
],
"custom_bone_cf_j_thigh00_l": [
"cf_j_thigh00_L",
"Left leg_fk"
],
"custom_bone_cf_j_leg01_l": [
"cf_j_leg01_L",
"Left knee_fk"
],
"custom_bone_cf_j_foot_R": [
"cf_j_foot_R",
"Right ankle_fk"
],
"custom_bone_cf_j_toes_R": [
"cf_j_toes_R",
"Right toe_fk"
],
"custom_bone_cf_j_thigh00_r": [
"cf_j_thigh00_R",
"Right leg_fk"
],
"custom_bone_cf_j_leg01_r": [
"cf_j_leg01_R",
"Right knee_fk"
],
"custom_bone_cf_j_shoulder_L": [
"cf_j_shoulder_L",
"Left shoulder"
],
"custom_bone_cf_j_waist02": [
"cf_j_waist02",
"cf_j_waist02"
],
"custom_bone_cf_j_shoulder_R": [
"cf_j_shoulder_R",
"Right shoulder"
],
"custom_bone_cf_j_kokan": [
"cf_j_kokan",
"cf_j_kokan"
],
"custom_bone_cf_j_ana": [
"cf_j_ana",
"cf_j_ana"
],
"custom_bone_cf_d_bust00": [
"cf_d_bust00",
"Breasts handle"
],
"custom_bone_cf_j_bust01_L": [
"cf_j_bust01_L",
"Left Breast handle"
],
"custom_bone_cf_j_bust02_L": [
"cf_j_bust02_L",
"cf_j_bust02_L"
],
"custom_bone_cf_j_bust03_L": [
"cf_j_bust03_L",
"cf_j_bust03_L"
],
"custom_bone_cf_j_bnip02root_L": [
"cf_j_bnip02root_L",
"cf_j_bnip02root_L"
],
"custom_bone_cf_j_bnip02_L": [
"cf_j_bnip02_L",
"cf_j_bnip02_L"
],
"custom_bone_cf_j_bust01_R": [
"cf_j_bust01_R",
"Right Breast handle"
],
"custom_bone_cf_j_bust02_R": [
"cf_j_bust02_R",
"cf_j_bust02_R"
],
"custom_bone_cf_j_bust03_R": [
"cf_j_bust03_R",
"cf_j_bust03_R"
],
"custom_bone_cf_j_bnip02root_R": [
"cf_j_bnip02root_R",
"cf_j_bnip02root_R"
],
"custom_bone_cf_j_bnip02_R": [
"cf_j_bnip02_R",
"cf_j_bnip02_R"
],
"custom_bone_cf_j_siri_L": [
"cf_j_siri_L",
"Left Buttock handle"
],
"custom_bone_cf_j_siri_R": [
"cf_j_siri_R",
"Right Buttock handle"
]
},
"shapes": {}
}

View File

@@ -0,0 +1,284 @@
{
"rokoko_custom_names": true,
"version": 1,
"bones": {
"custom_bone_cf_j_hips": [
"cf_j_hips",
"torso"
],
"custom_bone_cf_j_spine01": [
"cf_j_spine01",
"Spine_fk"
],
"custom_bone_cf_j_spine02": [
"cf_j_spine02",
"Chest_fk"
],
"custom_bone_cf_j_spine03": [
"cf_j_spine03",
"Upper Chest_fk"
],
"custom_bone_cf_j_arm00_l": [
"cf_j_arm00_l",
"Left arm_fk"
],
"custom_bone_cf_j_forearm01_l": [
"cf_j_forearm01_l",
"Left elbow_fk"
],
"custom_bone_cf_j_hand_l": [
"cf_j_hand_l",
"Left wrist_fk"
],
"custom_bone_cf_j_index01_l": [
"cf_j_index01_l",
"IndexFinger1_L"
],
"custom_bone_cf_j_index02_l": [
"cf_j_index02_l",
"IndexFinger2_L"
],
"custom_bone_cf_j_index03_l": [
"cf_j_index03_l",
"IndexFinger3_L"
],
"custom_bone_cf_j_little01_l": [
"cf_j_little01_l",
"LittleFinger1_L"
],
"custom_bone_cf_j_little02_l": [
"cf_j_little02_l",
"LittleFinger2_L"
],
"custom_bone_cf_j_little03_l": [
"cf_j_little03_l",
"LittleFinger3_L"
],
"custom_bone_cf_j_middle01_l": [
"cf_j_middle01_l",
"MiddleFinger1_L"
],
"custom_bone_cf_j_middle02_l": [
"cf_j_middle02_l",
"MiddleFinger2_L"
],
"custom_bone_cf_j_middle03_l": [
"cf_j_middle03_l",
"MiddleFinger3_L"
],
"custom_bone_cf_j_ring01_l": [
"cf_j_ring01_l",
"RingFinger1_L"
],
"custom_bone_cf_j_ring02_l": [
"cf_j_ring02_l",
"RingFinger2_L"
],
"custom_bone_cf_j_ring03_l": [
"cf_j_ring03_l",
"RingFinger3_L"
],
"custom_bone_cf_j_thumb01_l": [
"cf_j_thumb01_l",
"Thumb0_L"
],
"custom_bone_cf_j_thumb02_l": [
"cf_j_thumb02_l",
"Thumb1_l"
],
"custom_bone_cf_j_thumb03_l": [
"cf_j_thumb03_l",
"Thumb2_L"
],
"custom_bone_cf_j_arm00_r": [
"cf_j_arm00_r",
"Right arm_fk"
],
"custom_bone_cf_j_forearm01_r": [
"cf_j_forearm01_r",
"Right elbow_fk"
],
"custom_bone_cf_j_hand_r": [
"cf_j_hand_r",
"Right wrist_fk"
],
"custom_bone_cf_j_index01_r": [
"cf_j_index01_r",
"IndexFinger1_R"
],
"custom_bone_cf_j_index02_r": [
"cf_j_index02_r",
"IndexFinger2_R"
],
"custom_bone_cf_j_index03_r": [
"cf_j_index03_r",
"IndexFinger3_R"
],
"custom_bone_cf_j_little01_r": [
"cf_j_little01_r",
"LittleFinger1_R"
],
"custom_bone_cf_j_little02_r": [
"cf_j_little02_r",
"LittleFinger2_R"
],
"custom_bone_cf_j_little03_r": [
"cf_j_little03_r",
"LittleFinger3_R"
],
"custom_bone_cf_j_middle01_r": [
"cf_j_middle01_r",
"MiddleFinger1_R"
],
"custom_bone_cf_j_middle02_r": [
"cf_j_middle02_r",
"MiddleFinger2_R"
],
"custom_bone_cf_j_middle03_r": [
"cf_j_middle03_r",
"MiddleFinger3_R"
],
"custom_bone_cf_j_ring01_r": [
"cf_j_ring01_r",
"RingFinger1_R"
],
"custom_bone_cf_j_ring02_r": [
"cf_j_ring02_r",
"RingFinger2_R"
],
"custom_bone_cf_j_ring03_r": [
"cf_j_ring03_r",
"RingFinger3_R"
],
"custom_bone_cf_j_thumb01_r": [
"cf_j_thumb01_r",
"Thumb0_R"
],
"custom_bone_cf_j_thumb02_r": [
"cf_j_thumb02_r",
"Thumb1_R"
],
"custom_bone_cf_j_thumb03_r": [
"cf_j_thumb03_r",
"Thumb2_R"
],
"custom_bone_cf_j_neck": [
"cf_j_neck",
"neck"
],
"custom_bone_cf_j_head": [
"cf_j_head",
"head"
],
"custom_bone_cf_j_waist01": [
"cf_j_waist01",
"Hips_fk"
],
"custom_bone_cf_j_thigh00_l": [
"cf_j_thigh00_l",
"Left leg_fk"
],
"custom_bone_cf_j_leg01_l": [
"cf_j_leg01_l",
"Left knee_fk"
],
"custom_bone_cf_j_foot_l": [
"cf_j_foot_l",
"Left ankle_fk"
],
"custom_bone_cf_j_toes_l": [
"cf_j_toes_l",
"Left toe_fk"
],
"custom_bone_cf_j_thigh00_r": [
"cf_j_thigh00_r",
"Right leg_fk"
],
"custom_bone_cf_j_leg01_r": [
"cf_j_leg01_r",
"Right knee_fk"
],
"custom_bone_cf_j_foot_r": [
"cf_j_foot_r",
"Right ankle_fk"
],
"custom_bone_cf_j_toes_r": [
"cf_j_toes_r",
"Right toe_fk"
],
"custom_bone_cf_j_shoulder_l": [
"cf_j_shoulder_l",
"Left shoulder"
],
"custom_bone_cf_j_waist02": [
"cf_j_waist02",
"cf_j_waist02"
],
"custom_bone_cf_j_shoulder_r": [
"cf_j_shoulder_r",
"Right shoulder"
],
"custom_bone_cf_j_kokan": [
"cf_j_kokan",
"cf_j_kokan"
],
"custom_bone_cf_j_ana": [
"cf_j_ana",
"cf_j_ana"
],
"custom_bone_cf_d_bust00": [
"cf_d_bust00",
"Breasts handle"
],
"custom_bone_cf_j_bust01_l": [
"cf_j_bust01_l",
"Left Breast handle"
],
"custom_bone_cf_j_bust02_l": [
"cf_j_bust02_l",
"cf_j_bust02_L"
],
"custom_bone_cf_j_bust03_l": [
"cf_j_bust03_l",
"cf_j_bust03_L"
],
"custom_bone_cf_j_bnip02root_l": [
"cf_j_bnip02root_l",
"cf_j_bnip02root_L"
],
"custom_bone_cf_j_bnip02_l": [
"cf_j_bnip02_l",
"cf_j_bnip02_L"
],
"custom_bone_cf_j_bust01_r": [
"cf_j_bust01_r",
"Right Breast handle"
],
"custom_bone_cf_j_bust02_r": [
"cf_j_bust02_r",
"cf_j_bust02_R"
],
"custom_bone_cf_j_bust03_r": [
"cf_j_bust03_r",
"cf_j_bust03_R"
],
"custom_bone_cf_j_bnip02root_r": [
"cf_j_bnip02root_r",
"cf_j_bnip02root_R"
],
"custom_bone_cf_j_bnip02_r": [
"cf_j_bnip02_r",
"cf_j_bnip02_R"
],
"custom_bone_cf_j_siri_l": [
"cf_j_siri_l",
"Left Buttock handle"
],
"custom_bone_cf_j_siri_r": [
"cf_j_siri_r",
"Right Buttock handle"
]
},
"shapes": {}
}

View File

@@ -0,0 +1,215 @@
{
"Rokoko_custom_names": true,
"version": 1,
"bones": {
"custom_bone_mixamorig:Hips": [
"mixamorig:Hips",
"torso"
],
"custom_bone_mixamorig:Spine": [
"mixamorig:Spine",
"Spine_fk"
],
"custom_bone_mixamorig:Spine1": [
"mixamorig:Spine1",
"Chest_fk"
],
"custom_bone_mixamorig:Spine2": [
"mixamorig:Spine2",
"Upper Chest_fk"
],
"custom_bone_mixamorig:LeftHand": [
"mixamorig:LeftHand",
"Left wrist_fk"
],
"custom_bone_mixamorig:LeftArm": [
"mixamorig:LeftArm",
"Left arm_fk"
],
"custom_bone_mixamorig:LeftForeArm": [
"mixamorig:LeftForeArm",
"Left elbow_fk"
],
"custom_bone_mixamorig:LeftHandIndex1": [
"mixamorig:LeftHandIndex1",
"IndexFinger1_L"
],
"custom_bone_mixamorig:LeftHandIndex2": [
"mixamorig:LeftHandIndex2",
"IndexFinger2_L"
],
"custom_bone_cf_j_index03_L": [
"mixamorig:LeftHandIndex3",
"IndexFinger3_L"
],
"custom_bone_cf_j_Little01_L": [
"mixamorig:LeftHandPinky1",
"LittleFinger1_L"
],
"custom_bone_cf_j_Little02_L": [
"mixamorig:LeftHandPinky2",
"LittleFinger2_L"
],
"custom_bone_cf_j_Little03_L": [
"mixamorig:LeftHandPinky3",
"LittleFinger3_L"
],
"custom_bone_cf_j_middle01_L": [
"mixamorig:LeftHandMiddle1",
"MiddleFinger1_L"
],
"custom_bone_cf_j_middle02_L": [
"mixamorig:LeftHandMiddle2",
"MiddleFinger2_L"
],
"custom_bone_cf_j_middle03_L": [
"mixamorig:LeftHandMiddle3",
"MiddleFinger3_L"
],
"custom_bone_cf_j_ring01_L": [
"mixamorig:LeftHandRing1",
"RingFinger1_L"
],
"custom_bone_cf_j_ring02_L": [
"mixamorig:LeftHandRing2",
"RingFinger2_L"
],
"custom_bone_cf_j_ring03_L": [
"mixamorig:LeftHandRing3",
"RingFinger3_L"
],
"custom_bone_cf_j_thumb01_L": [
"mixamorig:LeftHandThumb1",
"Thumb0_L"
],
"custom_bone_cf_j_thumb02_L": [
"mixamorig:LeftHandThumb2",
"Thumb1_L"
],
"custom_bone_cf_j_thumb03_L": [
"mixamorig:LeftHandThumb3",
"Thumb2_L"
],
"custom_bone_cf_j_arm_R": [
"mixamorig:RightArm",
"Right arm_fk"
],
"custom_bone_cf_j_elbow": [
"mixamorig:RightForeArm",
"Right elbow_fk"
],
"custom_bone_cf_j_hand_R": [
"mixamorig:RightHand",
"Right wrist_fk"
],
"custom_bone_cf_j_index01_R": [
"mixamorig:RightHandIndex1",
"IndexFinger1_R"
],
"custom_bone_cf_j_index02_R": [
"mixamorig:RightHandIndex2",
"IndexFinger2_R"
],
"custom_bone_cf_j_index03_R": [
"mixamorig:RightHandIndex3",
"IndexFinger3_R"
],
"custom_bone_cf_j_Little01_R": [
"mixamorig:RightHandPinky1",
"LittleFinger1_R"
],
"custom_bone_cf_j_Little02_R": [
"mixamorig:RightHandPinky2",
"LittleFinger2_R"
],
"custom_bone_cf_j_Little03_R": [
"mixamorig:RightHandPinky.",
"LittleFinger3_R"
],
"custom_bone_cf_j_middle01_R": [
"mixamorig:RightHandMiddle1",
"MiddleFinger1_R"
],
"custom_bone_cf_j_middle02_R": [
"mixamorig:RightHandMiddle2",
"MiddleFinger2_R"
],
"custom_bone_cf_j_middle03_R": [
"mixamorig:RightHandMiddle3",
"MiddleFinger3_R"
],
"custom_bone_cf_j_ring01_R": [
"mixamorig:RightHandRing1",
"RingFinger1_R"
],
"custom_bone_cf_j_ring02_R": [
"mixamorig:RightHandRing2",
"RingFinger2_R"
],
"custom_bone_cf_j_ring03_R": [
"mixamorig:RightHandRing3",
"RingFinger3_R"
],
"custom_bone_cf_j_thumb01_R": [
"mixamorig:RightHandThumb1",
"Thumb0_R"
],
"custom_bone_cf_j_thumb02_R": [
"mixamorig:RightHandThumb2",
"Thumb1_R"
],
"custom_bone_cf_j_thumb03_R": [
"mixamorig:RightHandThumb3",
"Thumb2_R"
],
"custom_bone_cf_j_neck": [
"mixamorig:Neck",
"neck"
],
"custom_bone_cf_j_head": [
"mixamorig:Head",
"head"
],
"custom_bone_mixamorig:LeftFoot": [
"mixamorig:LeftFoot",
"Left ankle_fk"
],
"custom_bone_cf_j_toes_L": [
"mixamorig:LeftToeBase",
"Left toe_fk"
],
"custom_bone_cf_j_thigh00_l": [
"mixamorig:LeftUpLeg",
"Left leg_fk"
],
"custom_bone_cf_j_leg01_l": [
"mixamorig:LeftLeg",
"Left knee_fk"
],
"custom_bone_cf_j_foot_R": [
"mixamorig:RightFoot",
"Right ankle_fk"
],
"custom_bone_cf_j_toes_R": [
"mixamorig:RightToeBase",
"Right toe_fk"
],
"custom_bone_cf_j_thigh00_r": [
"mixamorig:RightUpLeg",
"Right leg_fk"
],
"custom_bone_cf_j_leg01_r": [
"mixamorig:RightLeg",
"Right knee_fk"
],
"custom_bone_cf_j_shoulder_L": [
"mixamorig:LeftShoulder",
"Left shoulder"
],
"custom_bone_cf_j_shoulder_R": [
"mixamorig:RightShoulder",
"Right shoulder"
]
},
"shapes": {}
}

Binary file not shown.

View File

@@ -0,0 +1,105 @@
# MIT License
# Copyright (c) 2017 GiveMeAllYourCats
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the 'Software'), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Code author: Hotox
# Repo: https://github.com/michaeldegroot/cats-blender-plugin
# Edits by: GiveMeAllYourCats
import bpy, io
from contextlib import redirect_stdout
from . import common as Common
from ...common import kklog
#from .register import register_wrap
#from .translations import t
#@register_wrap
class MergeWeights(bpy.types.Operator):
bl_idname = 'kkbp.cats_merge_weights'
bl_label = 'cats merge weights'
#bl_description = t('MergeWeights.desc')
bl_options = {'REGISTER', 'UNDO'}
'''
@classmethod
def poll(cls, context):
active_obj = bpy.context.active_object
if not active_obj or not bpy.context.active_object.type == 'ARMATURE':
return False
if active_obj.mode == 'EDIT' and bpy.context.selected_editable_bones:
return True
if active_obj.mode == 'POSE' and bpy.context.selected_pose_bones:
return True
return False
'''
def execute(self, context):
saved_data = Common.SavedData()
armature = bpy.context.object
Common.switch('EDIT')
# Find which bones to work on and put their name and their parent in a list
parenting_list = {}
for bone in bpy.context.selected_editable_bones:
parent = bone.parent
while parent and parent.parent and parent in bpy.context.selected_editable_bones:
parent = parent.parent
if not parent:
continue
parenting_list[bone.name] = parent.name
# Merge all the bones in the parenting list
merge_weights(armature, parenting_list)
saved_data.load()
self.report({'INFO'}, 'cats merge weights success')
return {'FINISHED'}
def merge_weights(armature, parenting_list):
Common.switch('OBJECT')
# Merge the weights on the meshes
stdout = io.StringIO()
meshes = 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)
for mindex, mesh in enumerate(meshes):
Common.set_active(mesh)
for index, bone in enumerate(parenting_list):
parent = parenting_list[bone]
kklog('Merging bone: {} object {} / {}, bone {} / {}'.format(bone, mindex, len(meshes), index, len(parenting_list)))
if not mesh.vertex_groups.get(bone):
continue
if not mesh.vertex_groups.get(parent):
mesh.vertex_groups.new(name=parent)
with redirect_stdout(stdout):
Common.mix_weights(mesh, bone, parent)
# Select armature
Common.unselect_all()
Common.set_active(armature)
Common.switch('EDIT')
# Delete merged bones
if True:
for bone in parenting_list.keys():
armature.data.edit_bones.remove(armature.data.edit_bones.get(bone))

2290
extras/catsscripts/common.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,451 @@
#This will take a folder full of fbx animation files ripped from the game and create a blender animation library using the current model (for thumbnails)
# Usage instructions:
# Export koikatsu fbx animation files using https://www.youtube.com/watch?v=XFt12n7ByBI&t=465s
# You can also export multiple animations at the same time by shift clicking them in the window
# put all the fbx files you exported into one folder
# any subfolder names in that folder will be used to tag them
# import a character with a rigify armature
# make sure you've got a camera and light pointed at the model (put it at an angle for better thumbnails)
# make sure you've disabled the armature visibility (in the 3d view settings on the top bar, not in the outliner)
# Suggested X and Y axis lines: ON
# Suggested sun location and rotation [-1 m, 0 m, 0 m] , [78°, -40°, 27°]
# Suggested world color [0,0,0]
# Suggested camera location + rotation [-0.7 m, -1.9 m, 0.8 m] , [83.6°, -0°, -20°]
# Suggested camera resolution [150 x 150]
# Suggested Tpose model location + rotation [0,0,0], [0°,0°,0°]
# Use https://www.youtube.com/watch?v=Nyxeb48mUfs&t=713s to setup the Rokoko retargeting addon with a random koikatsu fbx animation file from your folder
# (make sure torso, torso tweak, arm fk, fingers detail, leg fk Rigify layers are visible)
# (make sure ALL rokoko remapping / naming schemes are already setup using the random file)
# (Alternatively, you can import the included "Rokoko custom target naming" .json file included in the /extras/animationlibrary/ directory to set it automatically)
# (it just needs to be done once, then you can hit the save button in the rokoko retargeting panel to use it in any other file)
# delete the random fbx animation you imported (setup is complete at this point)
# save the file
# enter rendered view
# Run the script by pressing the button in the panel
# It will take about two hours on a good CPU to generate the library (three minutes per 11 poses, or 2 hours for ~440 poses / animations which adds up to about 1.2gb of fbx files)
# You can also do it in small batches and rotate out the already imported fbx files for new ones (change your filename after each batch or the previous batches will be overwritten)
# Or you can put a large list and kill blender when you want to pause the import process (it will automatically save a new file for every subfolder, and pick up from the previous subfolder)
# when finished, open every asset file and run the script on the bottom of this file to reduce the filesize
# (You can also copy paste the script into a text editor before running the script so you can have it automatically loaded on every asset file that will be produced)
# also recall that blender resets the fps to the framerate of the fbx file when importing
import bpy, os, time, mathutils, json, pathlib
from bpy.props import StringProperty
from .. import common as c
from ..interface.dictionary_en import t
def main(folder):
#stop if the file was not saved
if not bpy.data.filepath:
raise('File must be saved first!')
kkbp_character = False
#delete the armature before starting to reduce console spam
if c.get_body() and c.get_rig():
kkbp_character = True
if bpy.data.objects.get('Armature') and kkbp_character:
n = bpy.data.objects['Armature'].data.name
bpy.data.objects.remove(bpy.data.objects['Armature'])
bpy.data.armatures.remove(bpy.data.armatures[n])
c.toggle_console() #open console for some kind of progression
start = time.time()
rigify_armature = bpy.data.objects['RIG-Armature']
bpy.context.view_layer.objects.active=rigify_armature
bpy.ops.object.mode_set(mode = 'OBJECT')
bpy.ops.rsl.clear_custom_bones()
if kkbp_character:
#disable IKs
rigify_armature.pose.bones["Right arm_parent"]["IK_FK"] = 1
rigify_armature.pose.bones["Left arm_parent"]["IK_FK"] = 1
rigify_armature.pose.bones["Right leg_parent"]["IK_FK"] = 1
rigify_armature.pose.bones["Left leg_parent"]["IK_FK"] = 1
#disable head follow
rigify_armature.pose.bones['torso']['neck_follow'] = 1.0
rigify_armature.pose.bones['torso']['head_follow'] = 1.0
'''#reset all bone transforms
for pb in bpy.context.selected_pose_bones_from_active_object:
pb.matrix_basis = mathutils.Matrix() # == Matrix.Identity(4)
'''
#import the fbx files
fbx_files = []
for subdir, dirs, files in os.walk(folder):
for file in files:
if '.fbx' in file:
fbx_files.append((subdir, file, os.path.join(subdir, file)))
actions_from_set = []
last_category = ''
first_import = True
original_file_name = bpy.data.filepath
for file in fbx_files:
category = os.path.basename(file[0])
original_file_number = os.path.basename(os.path.dirname(file[0]))
filename = file[1]
#skip this file if the animation is for larger characters, or is a partial animation
skip = False
no_use = ['L_', 'M_', 'ML_', 'SM_', 'Te_', 'Yubi_', 'Sita_', 'Denma_', 'Vibe_']
for item in no_use:
if filename.startswith(item):
skip = True
if skip:
continue
#also skip if this a file for this category already exists
if os.path.exists(bpy.data.filepath.replace('.blend', ' ' + category + ' ' + str(original_file_number) + '.blend')):
continue
#create a new file for every category because the asset browser seems to behave better with multiple small files vs one large file
if last_category != category:
#save, then create new file
if last_category: #skip save for first file
bpy.ops.wm.save_as_mainfile(filepath = bpy.data.filepath)
bpy.ops.wm.save_as_mainfile(filepath = original_file_name.replace('.blend', ' ' + category + ' ' + str(original_file_number) + '.blend'))
last_category = category
#new file, so delete the previous imported asset animations
for act in actions_from_set:
bpy.data.actions.remove(bpy.data.actions[act])
actions_from_set = []
#load a script into the layout tab
if bpy.data.screens.get('Layout'):
for area in bpy.data.screens['Layout'].areas:
if area.type == 'DOPESHEET_EDITOR':
area.ui_type = 'TEXT_EDITOR'
area.spaces[0].text = bpy.data.texts.new(name='Reduce filesize, save and close')
area.spaces[0].text.write(
'''#this script reduces the filesize of the library file to just the action data
import bpy, time
for obj in bpy.data.objects:
bpy.data.objects.remove(obj)
for arm in bpy.data.armatures:
bpy.data.armatures.remove(arm)
for mesh in bpy.data.meshes:
bpy.data.meshes.remove(mesh)
for mat in bpy.data.materials:
bpy.data.materials.remove(mat)
for img in bpy.data.images:
bpy.data.images.remove(img)
for node in bpy.data.node_groups:
bpy.data.node_groups.remove(node)
bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)
time.sleep(3)
bpy.ops.wm.quit_blender()
''')
bpy.context.scene.frame_end = 40
bpy.ops.import_scene.fbx(filepath=str(file[2]), global_scale=96)
imported_armature = bpy.context.object
'''bpy.ops.object.mode_set(mode = 'EDIT')
bpy.ops.armature.select_all(action='DESELECT')
for bone in imported_armature.data.edit_bones:
if (
'k_f_' in bone.name
or ('cf_hit_' in bone.name)
or ('a_n_' in bone.name)
):
bone.select_head, bone.select_tail, bone.select = True, True, True
bpy.ops.object.mode_set(mode = 'OBJECT')'''
#rokoko remap list still shows deleted bones for some fucking reason
#clear all animation data from source armature, then re-key all bones for just this frame
#duplicate source armature then delete the original source armature
#rokoko list now shows correct bone list on source armature.002
#save the source action name so I can delete it later
current_action_name = imported_armature.animation_data.action.name
#setup rokoko remapping
my_areas = bpy.context.workspace.screens[0].areas
for area in my_areas:
for space in area.spaces:
if space.type == 'VIEW_3D':
space.show_object_viewport_armature = True
bpy.context.scene.rsl_retargeting_armature_source = imported_armature
bpy.context.scene.rsl_retargeting_armature_target = rigify_armature
bpy.ops.rsl.build_bone_list()
#setup all remapping stuff if this is the first imported animation
if first_import and kkbp_character:
first_import = False
script_dir=pathlib.Path(__file__).parent
remap_file = os.path.join(script_dir, "animationlibrary", "Rokoko retargeting list (koikatsu fbx to KKBP Rigify).json")
json_file = open(remap_file)
data = json.load(json_file)
for row in bpy.context.scene.rsl_retargeting_bone_list:
row.bone_name_target = "" #clear all bones
for bone in data['bones']:
for row in bpy.context.scene.rsl_retargeting_bone_list:
if row.bone_name_source.lower() == data['bones'][bone][0].lower():
row.bone_name_target = data['bones'][bone][1]
bpy.ops.rsl.retarget_animation()
# then remove the fcurves of the retargeted bones that were not present in the retargeting list.
# A lot of popping and fluctuation is present if these aren't removed
retargeting_list = [
'torso',
'Spine_fk',
'Chest_fk',
'Upper Chest_fk',
'Left arm_fk',
'Left elbow_fk',
'Left wrist_fk',
'IndexFinger1_L',
'IndexFinger2_L',
'IndexFinger3_L',
'LittleFinger1_L',
'LittleFinger2_L',
'LittleFinger3_L',
'MiddleFinger1_L',
'MiddleFinger2_L',
'MiddleFinger3_L',
'RingFinger1_L',
'RingFinger2_L',
'RingFinger3_L',
'Thumb0_L',
'Thumb1_L',
'Thumb2_L',
'Right arm_fk',
'Right elbow_fk',
'Right wrist_fk',
'IndexFinger1_R',
'IndexFinger2_R',
'IndexFinger3_R',
'LittleFinger1_R',
'LittleFinger2_R',
'LittleFinger3_R',
'MiddleFinger1_R',
'MiddleFinger2_R',
'MiddleFinger3_R',
'RingFinger1_R',
'RingFinger2_R',
'RingFinger3_R',
'Thumb0_R',
'Thumb1_R',
'Thumb2_R',
'neck',
'head',
'Hips_fk',
'Left leg_fk',
'Left knee_fk',
'Left ankle_fk',
'Left toe_fk',
'Right leg_fk',
'Right knee_fk',
'Right ankle_fk',
'Right toe_fk',
'Left shoulder',
'cf_j_waist02',
'Right shoulder',
'cf_j_kokan',
#'cf_j_ana',
'Breasts handle',
'Left Breast handle',
'cf_j_bust02_l',
'cf_j_bust03_l',
'cf_j_bnip02root_l',
'cf_j_bnip02_l',
'Right Breast handle',
'cf_j_bust02_r',
'cf_j_bust03_r',
'cf_j_bnip02root_r',
'cf_j_bnip02_r',
#'Left Buttock handle',
#'Right Buttock handle',
]
if kkbp_character:
for index, action in enumerate(rigify_armature.animation_data.action.fcurves):
bone_name = action.data_path[action.data_path.find('[')+2:action.data_path.find('].')-1]
print('{} {} / {}'.format(action.data_path, index, len(rigify_armature.animation_data.action.fcurves)))
if bone_name not in retargeting_list:
rigify_armature.animation_data.action.fcurves.remove(action) #remove keyframes
if rigify_armature.pose.bones.get(bone_name):
rigify_armature.pose.bones[bone_name].matrix_basis = mathutils.Matrix() #reset rotation matrix
if bpy.context.scene.kkbp.animation_library_scale:
#also scale the arms on the y axis by 5% because it makes the animation more accurate to the in-game one for some poses
already_got_both_arms = False
if kkbp_character:
for y_scale_curve in [curve for curve in rigify_armature.animation_data.action.fcurves if (curve.array_index == 1) and ('scale' in curve.data_path)]:
bone_name = y_scale_curve.data_path[y_scale_curve.data_path.find('[')+2:y_scale_curve.data_path.find('].')-1]
if bone_name in ['Left arm_fk', 'Right arm_fk']:
keyframe_limits = [int(y_scale_curve.keyframe_points[0].co.x), int(y_scale_curve.keyframe_points[-1].co.x)]
for frame_number in range(keyframe_limits[0], keyframe_limits[1]+1):
original_scale = y_scale_curve.evaluate(frame_number)
y_scale_curve.keyframe_points.insert(frame_number, original_scale * 1.05)
if already_got_both_arms:
break #skip the rest
already_got_both_arms = True
#select all rigify armature bones and create pose asset
bpy.ops.object.select_all(action='DESELECT')
rigify_armature.select_set(True)
bpy.context.view_layer.objects.active=rigify_armature
bpy.ops.object.mode_set(mode = 'POSE')
bpy.ops.pose.select_all(action='SELECT')
bpy.data.workspaces["Layout"].asset_library_reference = 'LOCAL'
#translate name
name = filename.replace('-p_cf_body_bone-0.fbx', '')
name = name.replace('-p_cf_body_bone-1.fbx', '')
translation_dict_normal = {
'isu_':'Seated_',
'suwari':'Sitting',
'syagami':'Squat',
'sadou':'Tea',
'undou':'Excercise',
'suiei':'Swimming',
'tachi':'Standing',
'manken':'Reading',
'soine':'Laying2',
'kasa':'Umbrella',
'konbou':'Club',
'aruki':'Walking',
'haruki':'Running',
'ne_0':'Sleep',
'nugi':'Undressing',
}
translation_dict_h = {
'_A_Idle':'Crotch grope idle',
'_A_Loop':'Crotch grope',
'_A_Touch':'Crotch grope start',
'_K_Loop':'Kiss',
'_K_Touch':'Kiss start',
'_M_Idle':'Breast grope idle',
'_M_Loop':'Breast grope',
'_M_Touch':'Breast grope start',
'_MLoop1': 'Masturbate',
'_MLoop2': 'Masturbate2',
'_Orgasm-': 'Climax-',
'_Orgasm_A':'Climax end',
'_Orgasm_B':'Climax end',
'_Orgasm_Loop':'Climax',
'_Orgasm_Start':'Climax start',
'_S_Idle':'Butt grope idle',
'_S_Loop':'Butt grope',
'_S_Touch':'Butt grope start',
'_Back_Dislikes':'Embarrassed back',
'_Front_Dislikes':'Embarrassed front',
'_Oral_Idle_IN': 'Cum in mouth start',
'_Oral_Idle': 'Cum in mouth',
'_Stop_Idle': 'Idling stop',
'_InsertIdle': 'Insert idle',
'_Idle-': 'Idling-',
'_Insert-': 'Insert-',
'_M_IN_Start':'Climax inside start',
'_SF_IN_Start':'Climax inside start',
'_A_SS_IN_Start':'Climax inside start',
'_SS_IN_Start':'Climax inside start',
'_A_WF_IN_Start':'Climax inside start',
'_A_WS_IN_Start':'Climax inside start',
'_WF_IN_Start':'Climax inside start',
'_A_M_IN_Start':'Climax inside start',
'_WS_IN_Start':'Climax inside start',
'_IN_Start':'Climax inside start',
'_M_IN_Loop':'Climax inside',
'_SF_IN_Loop':'Climax inside',
'_SS_IN_Loop':'Climax inside',
'_WF_IN_Loop':'Climax inside',
'_WS_IN_Loop':'Climax inside',
'_IN_Loop':'Climax inside',
'_A_IN_A':'Climax inside end',
'_SS_IN_A':'Climax inside end',
'_A_WS_IN_A':'Climax inside end',
'_WS_IN_A':'Climax inside end',
'_IN_A':'Climax inside end',
'_Pull':'Pull out',
'_OUT_Start':'Climax outside start',
'_M_OUT_Loop':'Climax outside',
'_OUT_Loop':'Climax outside',
'_OUT_A':'Climax outside end',
'_OLoop': 'Loop',
'_SLoop':'Fast loop',
'_WLoop':'Slow loop',
'_Drink_IN':'Swallow cum start',
'_Drink_A':'Swallow cum end',
'_Drink':'Swallow cum',
'_Vomit_IN': 'Spit out cum start',
'_Vomit_A': 'Spit out cum end',
'_Vomit': 'Spit out cum',
}
for item in translation_dict_normal:
name = name.replace(item, translation_dict_normal[item])
h_dict_used = False
for item in translation_dict_h:
if item in name:
name = category + ' ' + name[1:] #add the description onto it because h animations don't have names, remove the S at the beginning of the name because all of them start with that
name = name.replace(item, translation_dict_h[item])
h_dict_used = True
break
action = rigify_armature.animation_data.action
action.asset_mark()
rigify_armature.asset_mark()
action.use_fake_user = True
#render the first frame of the animation and set it as the preview
bpy.context.scene.render.filepath = os.path.join(folder, "preview.png")
#print(file[2])
my_areas = bpy.context.workspace.screens[0].areas
for area in my_areas:
for space in area.spaces:
if space.type == 'VIEW_3D':
space.show_object_viewport_armature = False
bpy.ops.render.opengl(write_still = True)
with bpy.context.temp_override(id=action):
bpy.ops.ed.lib_id_load_custom_preview(filepath=os.path.join(folder, "preview.png"))
my_areas = bpy.context.workspace.screens[0].areas
for area in my_areas:
for space in area.spaces:
if space.type == 'VIEW_3D':
space.show_object_viewport_armature = True
action.name = name
#bpy.ops.poselib.create_pose_asset(activate_new_action=True, pose_name = name) #for pose assets instead
action.asset_data.tags.new(filename)
action.asset_data.tags.new(category)
action.asset_data.tags.new(original_file_number)
if h_dict_used:
action.asset_data.tags.new('NSFW')
action.asset_data.description = filename
actions_from_set.append(action.name)
#delete the imported action, object and armature
bpy.data.actions.remove(bpy.data.actions[current_action_name])
imported_armature_armaturename = imported_armature.data.name
bpy.data.objects.remove(imported_armature)
bpy.data.armatures.remove(bpy.data.armatures[imported_armature_armaturename])
print(file)
print(str(time.time() - start))
bpy.ops.wm.save_as_mainfile(filepath = original_file_name.replace('.blend', ' ' + category + ' ' + str(original_file_number) + '.blend'))
c.toggle_console() #close console
class anim_asset_lib(bpy.types.Operator):
bl_idname = "kkbp.createanimassetlib"
bl_label = "Create animation asset library"
bl_description = t('animation_library_tt')
bl_options = {'REGISTER', 'UNDO'}
directory : StringProperty(maxlen=1024, default='', subtype='FILE_PATH', options={'HIDDEN'})
filter_glob : StringProperty(default='', options={'HIDDEN'})
data = None
mats_uv = None
structure = None
def execute(self, context):
main(self.directory)
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}

View File

@@ -0,0 +1,355 @@
#This will take a folder full of map files ripped from the game and create a blender asset library from them
# Usage instructions:
# Export koikatsu fbx animation files using https://www.youtube.com/watch?v=PeryYTsAN6E
# put all the map folders you exported into one folder
# any subfolder names in that folder will be used to tag them
# (i.e. /Desktop/all maps/map1/map1 fbx files + textures, then /Desktop/all maps/map2/map2 fbx files + textures, etc )
# Run this button from the kkbp extras section of the panel
# You can also do it in small batches and rotate out the already imported fbx files for new ones
# Or you can put a large list and kill blender when you want to pause the import process (it will resume from where it left off next time you run the script)
# remember to save the library file when it's done
import bpy, os, time, pathlib
from bpy.props import StringProperty
from .importstudio import import_studio_objects
from .. import common as c
from ..importing.modifymaterial import modify_material
from ..interface.dictionary_en import t
def better_fbx_map_import(directory):
already_loaded_images = [image.name for image in bpy.data.images]
#import
path = pathlib.Path(directory).rglob('*')
obj_list = []
for item in path:
if '.fbx' in str(item):
obj_list.append(str(item))
for obj in obj_list:
bpy.ops.better_import.fbx(filepath = obj, my_scale = 1.0)
#if nothing was imported, skip
if len(bpy.data.objects) < 2:
return
#delete duplicate objects and sky mesh
objs_to_delete = [obj for obj in bpy.data.objects if '.001' in obj.name or '.002' in obj.name or '_koi_sky_' in obj.name]
for obj in objs_to_delete:
data_name = obj.data.name
bpy.data.objects.remove(obj)
bpy.data.meshes.remove(bpy.data.meshes[data_name])
#remove all unused slots
bpy.ops.object.select_all(action='SELECT')
bpy.context.view_layer.objects.active = bpy.data.objects[1]
bpy.ops.object.material_slot_remove_unused()
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
#setup simple toon shader for all objects
for obj in [mesh for mesh in bpy.data.objects if mesh.type == 'MESH' and mesh.material_slots[0].material]:
#if the material already exists, use that, else create it
try:
obj.material_slots[0].material = bpy.data.materials['KK ' + obj.material_slots[0].material.name]
c.kklog('Material already exists: ' + obj.material_slots[0].material.name)
except:
try:
template = bpy.data.materials['KK Simple'].copy()
except:
script_dir=pathlib.Path(__file__).parent.parent
template_path=(script_dir / '../KK Shader V8.0.blend').resolve()
filepath = str(template_path)
innerpath = 'Material'
templateList = ['KK Simple']
for template in templateList:
bpy.ops.wm.append(
filepath=os.path.join(filepath, innerpath, template),
directory=os.path.join(filepath, innerpath),
filename=template,
set_fake=False
)
template = bpy.data.materials['KK Simple'].copy()
if obj.material_slots[0].material:
template.name = 'KK ' + obj.material_slots[0].material.name
new_node = template.node_tree.nodes['Gentex'].node_tree.copy()
template.node_tree.nodes['Gentex'].node_tree = new_node
new_node.name = template.name
main_image_link = [node for node in obj.material_slots[0].material.node_tree.nodes if node.type == 'BSDF_PRINCIPLED'][0].inputs[0]
if main_image_link.is_linked:
main_image = main_image_link.links[0].from_node.image
#replace the dds image with a png version
new_path = main_image.filepath.replace(".dds", ".png").replace(".DDS", ".png")
new_image_name = main_image.name.replace(".dds", ".png").replace(".DDS", ".png")
main_image.save_render(bpy.path.abspath(new_path))
bpy.data.images.load(filepath=bpy.path.abspath(new_path))
bpy.data.images[new_image_name].pack()
#create darktex
bpy.context.scene.kkbp.import_dir = os.path.dirname(bpy.data.filepath) + '\\'
main_image = bpy.data.images[new_image_name]
dark_image = modify_material.create_darktex(main_image, [.764, .880, 1])
bpy.context.scene.kkbp.import_dir = ''
#saturate both with color code
lut_light = 'Lut_TimeDay.png'
lut_dark = 'Lut_TimeDay.png'
modify_material.load_luts(lut_light, lut_dark)
for image in [main_image, dark_image]:
print('converting ' + image.name)
image.save()
image.reload()
image.colorspace_settings.name = 'sRGB'
modify_material.image_to_KK(image, lut_light) #run twice because of bug
new_pixels, width, height = modify_material.image_to_KK(image, lut_light)
image.pixels = new_pixels
#then load it in
new_node.nodes['light'].image = main_image
new_node.nodes['dark'].image = dark_image
norm_image_link = [node for node in obj.material_slots[0].material.node_tree.nodes if node.type == 'BSDF_PRINCIPLED'][0].inputs[22]
if norm_image_link.is_linked:
norm_image = norm_image_link.links[0].from_node.inputs[2].links[0].from_node.image
new_node.nodes['norm'].image == norm_image
obj.material_slots[0].material = template
def main(folder):
#Use the Better FBX importer addon if installed, else fallback to internal fbx importer
use_better_fbx_importer = 'better_fbx' in [addon.module for addon in bpy.context.preferences.addons]
c.toggle_console()
start = time.time()
#delete the default scene if present
if len(bpy.data.objects) == 3:
for obj in ['Camera', 'Light', 'Cube']:
if bpy.data.objects.get(obj):
bpy.data.objects.remove(bpy.data.objects[obj])
#Set the view transform
bpy.context.scene.view_settings.view_transform = 'Standard'
#import the fbx files
fbx_folders = [(folder + sub) for sub in os.listdir(folder) if '.blend' not in sub]
print(fbx_folders)
for map in fbx_folders:
category = map.replace(folder, '')
filename = map
blend_filepath = map.replace(category,'') + category + '.blend'
#if a .blend file already exists, don't process this fbx file
if os.path.exists(blend_filepath):
continue
collection = bpy.data.collections.new(category)
bpy.context.scene.collection.children.link(collection)
layer_collection = bpy.context.view_layer.layer_collection
#Recursivly transverse layer_collection for a particular name
def recurLayerCollection(layerColl, collName):
found = None
if (layerColl.name == collName):
return layerColl
for layer in layerColl.children:
found = recurLayerCollection(layer, collName)
if found:
return found
layerColl = recurLayerCollection(layer_collection, collection.name)
bpy.context.view_layer.active_layer_collection = layerColl
#import map object based on Better FBX availability
if use_better_fbx_importer:
better_fbx_map_import(map)
else:
import_studio_objects(map)
#skip if this was an empty folder
if len(bpy.data.objects) < 2:
bpy.data.collections.remove(collection)
continue
#apply all armature transforms
arm = bpy.data.objects['Armature']
bpy.ops.object.select_all(action='SELECT')
bpy.context.view_layer.objects.active = arm
bpy.ops.object.transform_apply(location=True, rotation=True, scale=False)
#delete armature because the asset browser sucks
bpy.data.objects.remove(arm)
bpy.context.view_layer.objects.active = bpy.data.objects[1]
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
#mark the collection as an asset and mark each object as an asset as long as it's not a wall, floor, ceiling or sky mesh
collection.asset_mark()
collection.asset_data.description = filename
collection.asset_data.tags.new(filename)
collection.asset_data.tags.new(category)
reject_list = [
'_yuka',
'_wall',
'_kabe',
'_ten',
'_black',
'_yuka',
'Armature',
'_sotokabe',
'_yane',
'_hana',
'_kuki',
'_kusa',
'_enkei',
'fensu',
'gura',
'_kage',
'_kmichi',
'_kmichi',
'_ie0',
'_seimon',
'_shadow',
'hana_0',
'kuki_0',
'kusa_0',
'_kyusui',
'_saku',
'_kumo',
'_kaidan',
'_nav',
'nob',
'_bushitu',
'map20_kk00',
'_styuka00',
'_ueki0',
'_kousya',
'_kanten',
'map21_light',
'sky_hoshi',
'_over',
'_suido',
'_kagamiwaku',
'o_koi_map100h_00_0',
'o_koi_map100_shawer00_00_0',
'_kar0',
'm00_o_box1__0',
'_reef_tga',
'_sky_star',
'_o_tree',
'o_ikegaki_0',
'i_stu_kiha',
'kouen_kakiwari_',
'kouen_road',
'kouen_pole',
'kouen_sibahu',
'mob_swimclub_',
'map34_kk0',
'p34_pool',
'ap36_kikai',
'_shitugaiki0',
'_tatemono',
'o_koi_map36_03_0',
'i_mizukri',
'koi_map17_kk00',
'_karten',
'i_gabyou',
'_pos0',
'_highyuka',
'map70_mob',
'_tesuri',
'o_soto_doa',
'o_soto_glass',
'o_soto_mado',
'o_koi_map80_00_s_0',
]
for obj in collection.all_objects:
mark_it = True
for item in reject_list:
if item in obj.name:
mark_it = False
break
if mark_it:
#apply transform then mark as asset
#bpy.ops.object.select_all(action='DESELECT')
#obj.select_set(True)
#bpy.context.view_layer.objects.active = obj
#bpy.ops.object.transform_apply(location=True, rotation=True, scale=False)
obj.asset_mark()
obj.asset_data.description = filename
obj.asset_data.tags.new(obj.name)
obj.asset_data.tags.new(category)
#hide the collection afterwards
bpy.context.scene.view_layers[0].active_layer_collection.exclude = True
#load a script into the layout tab
# if bpy.data.screens.get('Layout'):
# for area in bpy.data.screens['Layout'].areas:
# if area.type == 'DOPESHEET_EDITOR':
# area.ui_type = 'TEXT_EDITOR'
# area.spaces[0].text = bpy.data.texts.new(name='Generate previews, save and close')
# area.spaces[0].text.write(
# '''#stolen script to generate thumbnails for all objects
# import bpy, functools, time
# assets = [o for o in bpy.data.objects if o.asset_data] # Select all object assets
# assets.extend([m for m in bpy.data.materials if m.asset_data]) # Select all material assets
# for asset in assets:
# asset.asset_generate_preview()
# while bpy.app.is_job_running('RENDER_PREVIEW') == True:
# time.sleep(5)
# bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)
# time.sleep(3)
# bpy.ops.wm.quit_blender()
# ''')
print(blend_filepath)
bpy.ops.wm.save_as_mainfile(filepath = blend_filepath)
bpy.data.collections.remove(collection)
for block in bpy.data.objects:
bpy.data.objects.remove(block)
for block in bpy.data.meshes:
bpy.data.meshes.remove(block)
for block in bpy.data.materials:
bpy.data.materials.remove(block)
for block in bpy.data.armatures:
bpy.data.armatures.remove(block)
for block in bpy.data.images:
bpy.data.images.remove(block)
for block in bpy.data.node_groups:
bpy.data.node_groups.remove(block)
print(str(time.time() - start))
c.toggle_console()
class map_asset_lib(bpy.types.Operator):
bl_idname = "kkbp.createmapassetlib"
bl_label = "Create map asset library"
bl_description = t('map_library_tt')
bl_options = {'REGISTER', 'UNDO'}
directory : StringProperty(maxlen=1024, default='', subtype='FILE_PATH', options={'HIDDEN'})
filter_glob : StringProperty(default='', options={'HIDDEN'})
data = None
mats_uv = None
structure = None
def execute(self, context):
main(self.directory)
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}
if __name__ == "__main__":
bpy.utils.register_class(map_asset_lib)
# test call
print((bpy.ops.kkbp.createmapassetlib('INVOKE_DEFAULT')))

62
extras/imageconvert.py Normal file
View File

@@ -0,0 +1,62 @@
from pathlib import Path
import bpy
from .. import common as c
from ..importing.modifymaterial import modify_material
class image_convert(bpy.types.Operator):
bl_idname = "kkbp.imageconvert"
bl_label = "Convert light image"
bl_description = "Click this to saturate the currently loaded image using the selected Koikatsu LUT"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
c.kklog("Converting image: ".format(context.space_data.image))
scene = context.scene.kkbp
lut_selection = scene.image_dropdown
if lut_selection == 'A':
lut_choice = 'Lut_TimeDay.png'
elif lut_selection == 'B':
lut_choice = 'Lut_TimeNight.png'
else:
lut_choice = 'Lut_TimeSunset.png'
image = context.space_data.image
image.reload()
image.colorspace_settings.name = 'sRGB'
# Use code from importcolors to convert the current image
modify_material.load_luts(lut_choice, lut_choice)
# Need to run image_to_KK twice for the first image due to a weird bug
modify_material.image_to_KK(image, lut_choice)
new_pixels, width, height = modify_material.image_to_KK(image, lut_choice)
image.pixels = new_pixels
#image.save()
return {'FINISHED'}
class image_dark_convert(bpy.types.Operator):
bl_idname = "kkbp.imagedarkconvert"
bl_label = "Convert dark image"
bl_description = "Click this to create a dark version of the currently loaded maintex image. The new image will end in 'MT_DT.png' The new image will be automatically loaded to the dark color node group"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
body = bpy.data.objects['Body']
c.kklog("Converting image: ".format(context.space_data.image))
image = context.space_data.image
material_name = image.name[:-10]
try:
shadow_color = [body['KKBP shadow colors'][material_name]['r'], body['KKBP shadow colors'][material_name]['g'], body['KKBP shadow colors'][material_name]['b']]
darktex = modify_material.create_darktex(bpy.data.images[image.name], shadow_color)
material_name = 'KK ' + image.name[:-10]
bpy.data.materials[material_name].node_tree.nodes['Gentex'].node_tree.nodes['Darktex'].image = darktex
bpy.data.materials[material_name].node_tree.nodes['Shader'].node_tree.nodes['colorsDark'].inputs['Use dark maintex?'].default_value = 1
bpy.data.materials[material_name].node_tree.nodes['Shader'].node_tree.nodes['colorsDark'].inputs['Ignore colormask?'].default_value = 1
except:
c.kklog('Tried to create a dark version of {} but there was no shadow color available. \nDark color conversion is only available for Koikatsu images that end in \'_MT_CT.png\''.format(image.name), type='error')
return {'FINISHED'}

315
extras/importanimation.py Normal file
View File

@@ -0,0 +1,315 @@
import bpy, os, time, mathutils, json, pathlib
from .. import common as c
from ..interface.dictionary_en import t
def main(file):
kkbp_character = False
#delete the armature before starting to reduce console spam
if c.get_body() and c.get_rig():
kkbp_character = True
c.toggle_console() #open console for some kind of progression
start = time.time()
rigify_armature = bpy.data.objects['RIG-Armature']
bpy.context.view_layer.objects.active=rigify_armature
bpy.ops.object.mode_set(mode = 'OBJECT')
bpy.ops.rsl.clear_custom_bones()
if kkbp_character:
#disable IKs
rigify_armature.pose.bones["Right arm_parent"]["IK_FK"] = 1
rigify_armature.pose.bones["Left arm_parent"]["IK_FK"] = 1
rigify_armature.pose.bones["Right leg_parent"]["IK_FK"] = 1
rigify_armature.pose.bones["Left leg_parent"]["IK_FK"] = 1
#disable head follow
rigify_armature.pose.bones['torso']['neck_follow'] = 1.0
rigify_armature.pose.bones['torso']['head_follow'] = 1.0
bpy.ops.import_scene.fbx(filepath=str(file), global_scale=96)
imported_armature = bpy.context.object
#save the source action name so I can delete it later
current_action_name = imported_armature.animation_data.action.name
#setup rokoko remapping
my_areas = bpy.context.workspace.screens[0].areas
for area in my_areas:
for space in area.spaces:
if space.type == 'VIEW_3D':
space.show_object_viewport_armature = True
bpy.context.scene.rsl_retargeting_armature_source = imported_armature
bpy.context.scene.rsl_retargeting_armature_target = rigify_armature
bpy.ops.rsl.build_bone_list()
#setup all remapping stuff if this is the first imported animation
script_dir=pathlib.Path(__file__).parent
if bpy.context.scene.kkbp.animation_import_type:
remap_file = os.path.join(script_dir, "animationlibrary", "Rokoko retargeting list (mixamo to KKBP Rigify).json")
else:
remap_file = os.path.join(script_dir, "animationlibrary", "Rokoko retargeting list (koikatsu fbx to KKBP Rigify).json")
if kkbp_character:
script_dir=pathlib.Path(__file__).parent
json_file = open(remap_file)
data = json.load(json_file)
for row in bpy.context.scene.rsl_retargeting_bone_list:
row.bone_name_target = "" #clear all bones
for bone in data['bones']:
for row in bpy.context.scene.rsl_retargeting_bone_list:
if row.bone_name_source.lower() == data['bones'][bone][0].lower():
row.bone_name_target = data['bones'][bone][1]
bpy.ops.rsl.retarget_animation()
# then remove the fcurves of the retargeted bones that were not present in the retargeting list.
# A lot of popping and fluctuation is present if these aren't removed
retargeting_list = [
'torso',
'Spine_fk',
'Chest_fk',
'Upper Chest_fk',
'Left arm_fk',
'Left elbow_fk',
'Left wrist_fk',
'IndexFinger1_L',
'IndexFinger2_L',
'IndexFinger3_L',
'LittleFinger1_L',
'LittleFinger2_L',
'LittleFinger3_L',
'MiddleFinger1_L',
'MiddleFinger2_L',
'MiddleFinger3_L',
'RingFinger1_L',
'RingFinger2_L',
'RingFinger3_L',
'Thumb0_L',
'Thumb1_L',
'Thumb2_L',
'Right arm_fk',
'Right elbow_fk',
'Right wrist_fk',
'IndexFinger1_R',
'IndexFinger2_R',
'IndexFinger3_R',
'LittleFinger1_R',
'LittleFinger2_R',
'LittleFinger3_R',
'MiddleFinger1_R',
'MiddleFinger2_R',
'MiddleFinger3_R',
'RingFinger1_R',
'RingFinger2_R',
'RingFinger3_R',
'Thumb0_R',
'Thumb1_R',
'Thumb2_R',
'neck',
'head',
'Hips_fk',
'Left leg_fk',
'Left knee_fk',
'Left ankle_fk',
'Left toe_fk',
'Right leg_fk',
'Right knee_fk',
'Right ankle_fk',
'Right toe_fk',
'Left shoulder',
'cf_j_waist02',
'Right shoulder',
'cf_j_kokan',
#'cf_j_ana',
'Breasts handle',
'Left Breast handle',
'cf_j_bust02_l',
'cf_j_bust03_l',
'cf_j_bnip02root_l',
'cf_j_bnip02_l',
'Right Breast handle',
'cf_j_bust02_r',
'cf_j_bust03_r',
'cf_j_bnip02root_r',
'cf_j_bnip02_r',
#'Left Buttock handle',
#'Right Buttock handle',
]
if kkbp_character:
for index, action in enumerate(rigify_armature.animation_data.action.fcurves):
bone_name = action.data_path[action.data_path.find('[')+2:action.data_path.find('].')-1]
print('{} {} / {}'.format(action.data_path, index, len(rigify_armature.animation_data.action.fcurves)))
if bone_name not in retargeting_list:
rigify_armature.animation_data.action.fcurves.remove(action) #remove keyframes
if rigify_armature.pose.bones.get(bone_name):
rigify_armature.pose.bones[bone_name].matrix_basis = mathutils.Matrix() #reset rotation matrix
if bpy.context.scene.kkbp.animation_library_scale:
#also scale the arms on the y axis by 5% because it makes the animation more accurate to the in-game one for some poses
already_got_both_arms = False
if kkbp_character:
for y_scale_curve in [curve for curve in rigify_armature.animation_data.action.fcurves if (curve.array_index == 1) and ('scale' in curve.data_path)]:
bone_name = y_scale_curve.data_path[y_scale_curve.data_path.find('[')+2:y_scale_curve.data_path.find('].')-1]
if bone_name in ['Left arm_fk', 'Right arm_fk']:
keyframe_limits = [int(y_scale_curve.keyframe_points[0].co.x), int(y_scale_curve.keyframe_points[-1].co.x)]
for frame_number in range(keyframe_limits[0], keyframe_limits[1]+1):
original_scale = y_scale_curve.evaluate(frame_number)
y_scale_curve.keyframe_points.insert(frame_number, original_scale * 1.05)
if already_got_both_arms:
break #skip the rest
already_got_both_arms = True
#select all rigify armature bones and create pose asset
bpy.ops.object.select_all(action='DESELECT')
rigify_armature.select_set(True)
bpy.context.view_layer.objects.active=rigify_armature
bpy.ops.object.mode_set(mode = 'POSE')
bpy.ops.pose.select_all(action='SELECT')
bpy.data.workspaces["Layout"].asset_library_reference = 'LOCAL'
#translate name
name = file.replace('-p_cf_body_bone-0.fbx', '')
name = name.replace('-p_cf_body_bone-1.fbx', '')
translation_dict_normal = {
'isu_':'Seated_',
'suwari':'Sitting',
'syagami':'Squat',
'sadou':'Tea',
'undou':'Excercise',
'suiei':'Swimming',
'tachi':'Standing',
'manken':'Reading',
'soine':'Laying2',
'kasa':'Umbrella',
'konbou':'Club',
'aruki':'Walking',
'haruki':'Running',
'ne_0':'Sleep',
'nugi':'Undressing',
}
translation_dict_h = {
'_A_Idle':'Crotch grope idle',
'_A_Loop':'Crotch grope',
'_A_Touch':'Crotch grope start',
'_K_Loop':'Kiss',
'_K_Touch':'Kiss start',
'_M_Idle':'Breast grope idle',
'_M_Loop':'Breast grope',
'_M_Touch':'Breast grope start',
'_MLoop1': 'Masturbate',
'_MLoop2': 'Masturbate2',
'_Orgasm-': 'Climax-',
'_Orgasm_A':'Climax end',
'_Orgasm_B':'Climax end',
'_Orgasm_Loop':'Climax',
'_Orgasm_Start':'Climax start',
'_S_Idle':'Butt grope idle',
'_S_Loop':'Butt grope',
'_S_Touch':'Butt grope start',
'_Back_Dislikes':'Embarrassed back',
'_Front_Dislikes':'Embarrassed front',
'_Oral_Idle_IN': 'Cum in mouth start',
'_Oral_Idle': 'Cum in mouth',
'_Stop_Idle': 'Idling stop',
'_InsertIdle': 'Insert idle',
'_Idle-': 'Idling-',
'_Insert-': 'Insert-',
'_M_IN_Start':'Climax inside start',
'_SF_IN_Start':'Climax inside start',
'_A_SS_IN_Start':'Climax inside start',
'_SS_IN_Start':'Climax inside start',
'_A_WF_IN_Start':'Climax inside start',
'_A_WS_IN_Start':'Climax inside start',
'_WF_IN_Start':'Climax inside start',
'_A_M_IN_Start':'Climax inside start',
'_WS_IN_Start':'Climax inside start',
'_IN_Start':'Climax inside start',
'_M_IN_Loop':'Climax inside',
'_SF_IN_Loop':'Climax inside',
'_SS_IN_Loop':'Climax inside',
'_WF_IN_Loop':'Climax inside',
'_WS_IN_Loop':'Climax inside',
'_IN_Loop':'Climax inside',
'_A_IN_A':'Climax inside end',
'_SS_IN_A':'Climax inside end',
'_A_WS_IN_A':'Climax inside end',
'_WS_IN_A':'Climax inside end',
'_IN_A':'Climax inside end',
'_Pull':'Pull out',
'_OUT_Start':'Climax outside start',
'_M_OUT_Loop':'Climax outside',
'_OUT_Loop':'Climax outside',
'_OUT_A':'Climax outside end',
'_OLoop': 'Loop',
'_SLoop':'Fast loop',
'_WLoop':'Slow loop',
'_Drink_IN':'Swallow cum start',
'_Drink_A':'Swallow cum end',
'_Drink':'Swallow cum',
'_Vomit_IN': 'Spit out cum start',
'_Vomit_A': 'Spit out cum end',
'_Vomit': 'Spit out cum',
}
for item in translation_dict_normal:
name = name.replace(item, translation_dict_normal[item])
h_dict_used = False
for item in translation_dict_h:
if item in name:
name = name[1:] #add the description onto it because h animations don't have names, remove the S at the beginning of the name because all of them start with that
name = name.replace(item, translation_dict_h[item])
h_dict_used = True
break
action = rigify_armature.animation_data.action
rigify_armature.asset_mark()
action.asset_mark()
action.use_fake_user = True
my_areas = bpy.context.workspace.screens[0].areas
for area in my_areas:
for space in area.spaces:
if space.type == 'VIEW_3D':
space.show_object_viewport_armature = False
my_areas = bpy.context.workspace.screens[0].areas
for area in my_areas:
for space in area.spaces:
if space.type == 'VIEW_3D':
space.show_object_viewport_armature = True
action.name = os.path.basename(str(name))
action.asset_data.tags.new(file)
if h_dict_used:
action.asset_data.tags.new('NSFW')
action.asset_data.description = file
#delete the imported action, object and armature
bpy.data.actions.remove(bpy.data.actions[current_action_name])
imported_armature_armaturename = imported_armature.data.name
bpy.data.objects.remove(imported_armature)
try:
bpy.data.objects.remove(bpy.data.objects['Beta_Joints'])
bpy.data.objects.remove(bpy.data.objects['Beta_Surface'])
except:
#oh well
pass
bpy.data.armatures.remove(bpy.data.armatures[imported_armature_armaturename])
print(file)
print(str(time.time() - start))
c.toggle_console() #close console
class anim_import(bpy.types.Operator):
bl_idname = "kkbp.importanimation"
bl_label = "Import .fbx animation file"
bl_description = t('single_animation_tt')
bl_options = {'REGISTER', 'UNDO'}
filepath : bpy.props.StringProperty(maxlen=1024, default='', options={'HIDDEN'})
filter_glob : bpy.props.StringProperty(default='*.fbx', options={'HIDDEN'})
def execute(self, context):
main(self.filepath)
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}

413
extras/importstudio.py Normal file
View File

@@ -0,0 +1,413 @@
import bpy, os, time, glob
from pathlib import Path
from bpy.props import StringProperty
from ..importing.modifymaterial import modify_material
from .. import common as c
from subprocess import Popen, PIPE
from ..interface.dictionary_en import t
def import_studio_objects(directory):
#Stop if no files were detected
def fileError(self, context):
self.layout.label(text="No fbx files were detected in the folder you selected (including subfolders)")
#Stop if no custom studio node group was detected
def nodeError(self, context):
self.layout.label(text="You need a node group named \"Custom_studio\" with at least three inputs and one output to use the Custom shader.")
self.layout.label(text="Input 1: Maintex || Input 2: Image Alpha || Input 3: Normal || Input 4 (optional): Detailmask || Input 5 (optional): Colormask")
bpy.context.scene.view_settings.view_transform = 'Standard'
scene = bpy.context.scene.kkbp
shader_type = scene.dropdown_box
shadow_type = scene.shadows_dropdown
blend_type = scene.blend_dropdown
use_lut = scene.studio_lut_bool
#shader_dict = {"A": "principled", "B": "emission", "C": "kkshader", "D": "custom"}
shadow_dict = {"A": "NONE", "B": "OPAQUE", "C": "CLIP", "D": "HASHED"}
blend_dict = {"A": "OPAQUE", "B": "CLIP", "C": "HASHED", "D": "BLEND"}
path = Path(directory).rglob('*')
fbx_list = []
image_list = []
for item in path:
if '.fbx' in str(item):
fbx_list.append(item)
if '.dds' in str(item) or '.tga' in str(item):
image_list.append(item)
#attempt to detect detail masks, color masks and main tex files from the selected folder/subfolders
#normal map will always be attached on import if it's present
conversion_image_list = []
for image in image_list:
if '_md-DXT' in str(image):
detected_detailmask = image.name
if '_mc-DXT' in str(image):
detected_colormask = image.name
if '_t-DXT' in str(image):
detected_maintex = image.name
#pack certain images for later
if '_md-DXT' in str(image) or '_mc-DXT' in str(image) or '_t-DXT' in str(image):
bpy.data.images.load(filepath=str(image))
bpy.data.images[image.name].pack()
#save the images in this directory for later
conversion_image_list.append(image.name)
if len(fbx_list) == 0:
bpy.context.window_manager.popup_menu(fileError, title="Error", icon='ERROR')
return
#get the images currently loaded into the file
already_loaded_images = [image.name for image in bpy.data.images]
for fbx in fbx_list:
bpy.ops.import_scene.fbx(filepath=str(fbx))
bpy.ops.object.mode_set(mode='OBJECT')
for object in bpy.context.selected_objects:
if object.type == 'ARMATURE':
object.scale = [1, 1, 1]
if len(object.data.bones) == 1:
bpy.ops.object.parent_clear(type='CLEAR_KEEP_TRANSFORM')
bpy.data.objects.remove(object)
elif object.type == 'MESH':
#set scale and rename the first UV map
object.scale = [1, 1, 1]
if object.data.uv_layers[0]:
object.data.uv_layers[0].name = 'UVMap'
for material_slot in object.material_slots:
material = material_slot.material
nodes = material.node_tree.nodes
material.use_backface_culling = True
material.show_transparent_back = False
material.blend_method = blend_dict[blend_type]
material.shadow_method = shadow_dict[shadow_type]
#rename all principled BSDF nodes to 'Principled BSDF' just in case
for node in nodes:
if node.type == 'BSDF_PRINCIPLED':
node.name = 'Principled BSDF'
#rename all Material Output nodes to 'Material Output' just in case
for node in nodes:
if node.type == 'OUTPUT_MATERIAL':
node.name = 'Material Output'
#Remove duplicate images if they exist
for node in nodes:
if node.type == 'TEX_IMAGE':
(base, sep, ext) = node.image.name.rpartition('.')
if ext.isnumeric():
if bpy.data.images.get(base):
node.image = bpy.data.images.get(base)
#DDS files need to be converted to pngs or tgas or the color conversion scripts won't work
#also set images to srgb
for node in nodes:
if node.type == 'TEX_IMAGE':
bpy.data.images[node.image.name].colorspace_settings.name = 'sRGB'
image = bpy.data.images[node.image.name]
if ('.dds' in image.name or '.DDS' in image.name) and image.name.replace('.dds', '.png') not in already_loaded_images:
new_path = image.filepath.replace(".dds", ".png").replace(".DDS", ".png")
new_image_name = image.name.replace(".dds", ".png").replace(".DDS", ".png")
image.colorspace_settings.name = 'sRGB'
image.save_render(bpy.path.abspath(new_path))
bpy.data.images.load(filepath=bpy.path.abspath(new_path))
bpy.data.images[new_image_name].pack()
node.image = bpy.data.images[new_image_name]
#if two objects have the same material, and the material was already operated on, skip it
if nodes.get('Principled BSDF') == None:
continue
emission_input = 26
metallic_input = 1
normal_input = 5
alpha_input = 4
#standardize dist and subsurf because the number of nodes on the principled bsdf changes with these choices
nodes['Principled BSDF'].distribution = 'GGX'
nodes['Principled BSDF'].subsurface_method = 'RANDOM_WALK'
#set emission to black
nodes['Principled BSDF'].inputs[emission_input].default_value = (0, 0, 0, 1)
#set metallic value to zero
nodes['Principled BSDF'].inputs[metallic_input].default_value = 0.0
try:
image_alpha = nodes['Principled BSDF'].inputs[alpha_input].links[0].from_node
except:
#there was no image attached to the alpha node
image_alpha = 'noalpha'
try:
image = nodes['Principled BSDF'].inputs[0].links[0].from_node
except:
image = 'noimage'
if image_alpha != 'noalpha':
material.node_tree.links.new(image_alpha.outputs[1], nodes['Principled BSDF'].inputs[alpha_input])
#if set to emission
if shader_type == 'B':
nodes.remove(nodes['Principled BSDF'])
output_node = nodes['Material Output']
transparency_mix = nodes.new('ShaderNodeMixShader')
transparency_mix.location = output_node.location[0], output_node.location[1] - 300
transparency_node = nodes.new('ShaderNodeBsdfTransparent')
transparency_node.location = transparency_mix.location[0] - 300, transparency_mix.location[1]
emissive_node = nodes.new('ShaderNodeEmission')
emissive_node.location = transparency_node.location[0], transparency_node.location[1] + 300
output_node.location = transparency_mix.location[0] + 300, transparency_mix.location[1]
if image != 'noimage':
material.node_tree.links.new(image.outputs[0], emissive_node.inputs[0])
else:
#if there's no image, try falling back to the colormask
try:
color_node = nodes.new('ShaderNodeTexImage')
color_node.image = bpy.data.images[detected_colormask]
color_node.location = emissive_node.location[0] - 300, emissive_node.location[1]
material.node_tree.links.new(color_node.outputs[0], emissive_node.inputs[0])
except:
nodes.remove(color_node)
material.node_tree.links.new(emissive_node.outputs[0], transparency_mix.inputs[2])
material.node_tree.links.new(transparency_node.outputs[0], transparency_mix.inputs[1])
if image_alpha != 'noalpha':
material.node_tree.links.new(image_alpha.outputs[1], transparency_mix.inputs[0])
elif image != 'noimage':
material.node_tree.links.new(image.outputs[1], transparency_mix.inputs[0])
material.node_tree.links.new(output_node.inputs[0], transparency_mix.outputs[0])
elif shader_type == 'C':
#if set to KK shader
if image_alpha != 'noalpha':
image_alpha = image_alpha.image
if image != 'noimage':
image = image.image
else:
#if there's no image, try to fallback to the maintex
try:
image = bpy.data.images.get(detected_maintex)
except:
pass
#get normal image
if nodes['Principled BSDF'].inputs[normal_input].links[0].from_node.inputs[1].links != ():
normal = nodes['Principled BSDF'].inputs[normal_input].links[0].from_node.inputs[1].links[0].from_node.image
else:
normal = 'nonormal'
try:
template = bpy.data.materials['KK General'].copy()
except:
script_dir=Path(__file__).parent
template_path=(script_dir / '../KK Shader V8.0.blend').resolve()
filepath = str(template_path)
innerpath = 'Material'
templateList = ['KK General']
for template in templateList:
bpy.ops.wm.append(
filepath=os.path.join(filepath, innerpath, template),
directory=os.path.join(filepath, innerpath),
filename=template,
set_fake=False
)
template = bpy.data.materials['KK General'].copy()
template.name = 'KK ' + material.name
material_slot.material = bpy.data.materials[template.name]
material = material_slot.material
nodes = material.node_tree.nodes
def image_load(group, node, image, raw = False):
try:
nodes[group].node_tree.nodes[node].image = bpy.data.images[image]
if raw:
nodes[group].node_tree.nodes[node].image.colorspace_settings.name = 'Raw'
except:
c.kklog('Image not found, skipping: ' + str(image), type = 'warn')
gen_type = material_slot.name.replace('KK ','')
#make a copy of the node group, use it to replace the current node group and rename it so each mat has a unique texture group
new_node = material_slot.material.node_tree.nodes['Gentex'].node_tree.copy()
material_slot.material.node_tree.nodes['Gentex'].node_tree = new_node
new_node.name = gen_type + ' Textures'
if image != 'noimage':
image_load('Gentex', 'Maintex', image.name)#, True)
else:
#if there's no image, fallback to the detected maintex
try:
image_load('Gentex', 'Maintex', detected_maintex)#, True)
except:
#oh well
pass
if normal != 'nonormal':
image_load('Gentex', 'MainNorm', normal.name, True)
#try importing the detail mask if there is one
try:
image_load('Gentex', 'MainDet', detected_detailmask)#, True)
except:
#or not
pass
try:
image_load('Gentex', 'MainCol', detected_colormask)#, True)
except:
#or not
pass
#Also, make a copy of the General shader node group, as it's unlikely everything using it will be the same color
new_node = material_slot.material.node_tree.nodes['Shader'].node_tree.copy()
material_slot.material.node_tree.nodes['Shader'].node_tree = new_node
new_node.name = gen_type + ' Shader'
main_image = material_slot.material.node_tree.nodes['Gentex'].node_tree.nodes['Maintex'].image
alpha_image = material_slot.material.node_tree.nodes['Gentex'].node_tree.nodes['Alphamask'].image
#If no main image was loaded in, there's no alpha channel being fed into the KK Shader.
#Unlink the input node and make the alpha channel pure white
if main_image == None:
getOut = material_slot.material.node_tree.nodes['Shader'].node_tree.nodes['alphatoggle'].inputs['maintex alpha'].links[0]
material_slot.material.node_tree.nodes['Shader'].node_tree.links.remove(getOut)
material_slot.material.node_tree.nodes['Shader'].node_tree.nodes['alphatoggle'].inputs['maintex alpha'].default_value = (1,1,1,1)
else:
#but if there is a main image, create a darktex for it and load it in
original_path = bpy.context.scene.kkbp.import_dir
bpy.context.scene.kkbp.import_dir = directory + 'saturated_files'
darktex = modify_material.create_darktex(bpy.data.images[image.name], [.764, .880, 1]) #create the darktex now and load it in later
bpy.context.scene.kkbp.import_dir = original_path
image_load('Gentex', 'Darktex', darktex.name)
material_slot.material.node_tree.nodes['Shader'].node_tree.nodes['colorsDark'].inputs['Use dark maintex?'].default_value = 1
material_slot.material.node_tree.nodes['Shader'].node_tree.nodes['colorsDark'].inputs['Ignore colormask?'].default_value = 1
material.use_backface_culling = True
material.show_transparent_back = False
material.blend_method = blend_dict[blend_type]
material.shadow_method = shadow_dict[shadow_type]
elif shader_type == 'D':
if nodes['Principled BSDF'].inputs[20].links[0].from_node.inputs[1].links != ():
normal = nodes['Principled BSDF'].inputs[20].links[0].from_node.inputs[1].links[0].from_node
else:
normal = 'nonormal'
nodes.remove(nodes['Principled BSDF'])
output_node = nodes['Material Output']
custom_group = nodes.new('ShaderNodeGroup')
try:
custom_group.node_tree = bpy.data.node_groups['Custom_studio']
except:
try:
custom_group.node_tree = bpy.data.node_groups['custom_studio']
except:
#no custom studio node group was detected
bpy.context.window_manager.popup_menu(nodeError, title="Error", icon='ERROR')
return
custom_group.location = output_node.location[0], output_node.location[1] - 300
if image != 'noimage':
material.node_tree.links.new(image.outputs[0], custom_group.inputs[0])
if image_alpha != 'noalpha':
material.node_tree.links.new(image_alpha.outputs[1], custom_group.inputs[1])
if normal != 'nonormal':
material.node_tree.links.new(normal.outputs[0], custom_group.inputs[2])
#if the custom studio node group has a fourth input, and a detail mask is available, put the detail mask in there
try:
detail_node = nodes.new('ShaderNodeTexImage')
detail_node.image = bpy.data.images[detected_detailmask]
detail_node.location = custom_group.location[0] - 300, custom_group.location[1] - 300
material.node_tree.links.new(detail_node.outputs[0], custom_group.inputs[3])
except:
nodes.remove(detail_node)
#if the custom studio node group has a fifth input, and a color mask is available, put the color mask in there
try:
color_node = nodes.new('ShaderNodeTexImage')
color_node.image = bpy.data.images[detected_colormask]
color_node.location = custom_group.location[0] - 300, custom_group.location[1] - 600
material.node_tree.links.new(color_node.outputs[0], custom_group.inputs[4])
except:
nodes.remove(color_node)
material.node_tree.links.new(output_node.inputs[0], custom_group.outputs[0])
#basic strat: load in the .dds files to this version of blender, set them to srgb, save them as .pngs and saturate the .pngs with the older blender version
if use_lut:
image_list = [image for image in bpy.data.images if (image.name not in already_loaded_images and 'Template:' not in image.name)]
def convert_and_import_textures():
c.kklog('Opening older version of Blender to convert model textures...')
time.sleep(5)
# You have to supply a blend file or it won't execute the script automatically. Choose the video editing template blend because it's the first one I tried
if 'blender.exe' in bpy.context.scene.kkbp.blender_path:
version_path = [i for i in glob.glob(os.path.dirname(bpy.context.scene.kkbp.blender_path) + '/*/')][0]
else:
bpy.context.scene.kkbp.blender_path = bpy.context.scene.kkbp.blender_path + '/blender.exe'
version_path = [i for i in glob.glob(os.path.dirname(bpy.context.scene.kkbp.blender_path) + '/*/')][0]
blender_file = os.path.join(version_path, 'scripts', 'startup', 'bl_app_templates_system', 'Video_Editing', 'startup.blend')
secondscriptname = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'importing', 'converttextures.py')
process = Popen([bpy.context.scene.kkbp.blender_path, blender_file, "-P", secondscriptname, os.path.dirname(os.path.dirname(__file__)) + r'\importing', directory, '0'], stdout=PIPE, universal_newlines=True)
r = process.stdout.readline()[:-1]
while r:
if '|' in r:
c.kklog(r.replace('|','')) # these are lines printed from the second script
r = process.stdout.readline()[:-1]
convert_and_import_textures()
#load in the saturated images and remap
for image in image_list:
if image.filepath:
try:
saturated_image = bpy.data.images.load(image.filepath.replace(image.name, 'saturated_files\\' + image.name.replace('.dds','.png').replace('.DDS','.png')))
image.user_remap(saturated_image)
except:
saturated_image = bpy.data.images.load(image.filepath.replace(image.name, image.name.replace('.dds','.png').replace('.DDS','.png')))
image.user_remap(saturated_image)
#delete orphan data
for cat in [bpy.data.armatures, bpy.data.objects, bpy.data.meshes, bpy.data.materials, bpy.data.images, bpy.data.node_groups]:
for block in cat:
if block.users == 0:
cat.remove(block)
class import_studio(bpy.types.Operator):
bl_idname = "kkbp.importstudio"
bl_label = "Import studio object"
bl_description = t('studio_object_tt')
bl_options = {'REGISTER', 'UNDO'}
directory : StringProperty(maxlen=1024, default='', subtype='FILE_PATH', options={'HIDDEN'})
filter_glob : StringProperty(default='', options={'HIDDEN'})
data = None
mats_uv = None
structure = None
def execute(self, context):
import_studio_objects(self.directory)
c.toggle_console()
c.toggle_console()
return {'FINISHED'}
def invoke(self, context, event):
context.window_manager.fileselect_add(self)
return {'RUNNING_MODAL'}

26
extras/linkhair.py Normal file
View File

@@ -0,0 +1,26 @@
#This script takes a source hair material and copies all of it's attributes to all target hair materials on the same object
# so you don't have to manually change every single hair material yourself
import bpy
from ..interface.dictionary_en import t
class link_hair(bpy.types.Operator):
bl_idname = "kkbp.linkhair"
bl_label = "Link hair"
bl_description = t('link_hair_tt')
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
#get the currently selected hair material
object = bpy.context.object
source_material = object.active_material
for type in ['light', 'dark']:
if source_material.node_tree.nodes.get(type):
for target_material in [s.material for s in object.material_slots if s.material.get('hair')]:
if target_material.node_tree.nodes.get(type):
#copy all of the hair settings for this node group to the target material
for index, input in enumerate(target_material.node_tree.nodes[type].inputs):
input.default_value = source_material.node_tree.nodes[type].inputs[index].default_value
return {'FINISHED'}

99
extras/linkshapekeys.py Normal file
View File

@@ -0,0 +1,99 @@
'''
LINK EYEBROW SHAPEKEYS TO BODY SCRIPT
Usage:
- Seperate the eyebrow mesh into its own object
- Select the eyebrows object, then shift click the body object and run the script to control the eyebrow shapekeys from the body object
Script 90% stolen from https://blender.stackexchange.com/questions/86757/python-how-to-connect-shapekeys-via-drivers
'''
import bpy
from ..interface.dictionary_en import t
from .. import common as c
def link_keys(shapekey_holder_object, objects_to_link):
shapekey_list_string = str(shapekey_holder_object.data.shape_keys.key_blocks.keys()).lower()
for obj in objects_to_link:
bpy.ops.object.select_all(action = 'DESELECT')
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.material_slot_remove_unused()
for key in obj.data.shape_keys.key_blocks:
if key.name.lower() in shapekey_list_string:
if not key.name == obj.data.shape_keys.key_blocks[0]:
skey_driver = key.driver_add('value')
skey_driver.driver.type = 'AVERAGE'
#skey_driver.driver.show_debug_info = True
if skey_driver.driver.variables:
for v in skey_driver.driver.variables:
skey_driver.driver.variables.remove(v)
newVar = skey_driver.driver.variables.new()
newVar.name = "value"
newVar.type = 'SINGLE_PROP'
newVar.targets[0].id_type = 'KEY'
newVar.targets[0].id = shapekey_holder_object.data.shape_keys
newVar.targets[0].data_path = 'key_blocks["' + key.name+ '"].value'
skey_driver = key.driver_add('mute')
skey_driver.driver.type = 'AVERAGE'
#skey_driver.driver.show_debug_info = True
if skey_driver.driver.variables:
for v in skey_driver.driver.variables:
skey_driver.driver.variables.remove(v)
newVar = skey_driver.driver.variables.new()
newVar.name = "hide"
newVar.type = 'SINGLE_PROP'
newVar.targets[0].id_type = 'KEY'
newVar.targets[0].id = shapekey_holder_object.data.shape_keys
newVar.targets[0].data_path = 'key_blocks["' + key.name+ '"].mute'
class link_shapekeys(bpy.types.Operator):
bl_idname = "kkbp.linkshapekeys"
bl_label = "Link shapekeys"
bl_description = t('sep_eye_tt')
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
#separate the eyes from the body object
body = c.get_body()
c.switch(body, 'EDIT')
def separateMaterial(matList):
for mat in matList:
try:
def moveUp():
return bpy.ops.object.material_slot_move(direction='UP')
while moveUp() != {"CANCELLED"}:
pass
bpy.context.object.active_material_index = body.data.materials.find(mat)
bpy.ops.object.material_slot_select()
except:
print('material wasn\'t found: ' + mat)
bpy.ops.mesh.separate(type='SELECTED')
eye_list = ['KK EyeR (hitomi) ' + c.get_name(),
'KK EyeL (hitomi) ' + c.get_name(),
'KK Eyewhites (sirome) ' + c.get_name(),
'KK Eyeline up ' + c.get_name(),
'KK Eyeline down ' + c.get_name()]
separateMaterial(eye_list)
eyes = bpy.data.objects[body.name + '.001']
eyes.name = 'Eyes'
if eyes.modifiers.get('Outline Modifier'):
eyes.modifiers['Outline Modifier'].show_viewport = False
eyes.modifiers['Outline Modifier'].show_render = False
#do the same for the eyebrows
separateMaterial(['KK Eyebrows (mayuge) ' + c.get_name()])
eyebrows = bpy.data.objects[body.name + '.001']
eyebrows.name = 'Eyebrows'
if eyebrows.modifiers.get('Outline Modifier'):
eyebrows.modifiers['Outline Modifier'].show_viewport = False
eyebrows.modifiers['Outline Modifier'].show_render = False
bpy.ops.object.mode_set(mode = 'OBJECT')
link_keys(body, [eyes, eyebrows])
return {'FINISHED'}

114
extras/matcombsetup.py Normal file
View File

@@ -0,0 +1,114 @@
import bpy
from ..interface.dictionary_en import t
from .. import common as c
class mat_comb_setup(bpy.types.Operator):
bl_idname = "kkbp.matcombsetup"
bl_label = "Material combiner setup"
bl_description = t('mat_comb_tt')
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
def recurLayerCollection(layerColl, collName):
found = None
if (layerColl.name == collName):
return layerColl
for layer in layerColl.children:
found = recurLayerCollection(layer, collName)
if found:
return found
def remove_orphan_data():
#revert the image back from the atlas file to the baked file
for mat in bpy.data.materials:
if mat.name[-4:] == '-ORG':
simplified_name = mat.name[:-4]
if bpy.data.materials.get(simplified_name):
simplified_mat = bpy.data.materials[simplified_name]
for bake_type in ['light', 'dark', 'normal']:
simplified_mat.node_tree.nodes['textures'].node_tree.nodes[bake_type].image = bpy.data.images.get(simplified_name + ' ' + bake_type + '.png')
#delete orphan data
for cat in [bpy.data.armatures, bpy.data.objects, bpy.data.meshes, bpy.data.materials, bpy.data.images, bpy.data.node_groups]:
for block in cat:
if block.users == 0:
cat.remove(block)
if bpy.data.collections.get(c.get_name() + ' atlas'):
c.kklog('deleting previous collection "Model with atlas" and regenerating atlas model...')
def del_collection(coll):
for c in coll.children:
del_collection(c)
bpy.data.collections.remove(coll,do_unlink=True)
del_collection(bpy.data.collections[c.get_name() + ' atlas'])
remove_orphan_data()
#show the original collection again
layer_collection = bpy.context.view_layer.layer_collection
layerColl = recurLayerCollection(layer_collection, 'Scene Collection')
bpy.context.view_layer.active_layer_collection = layerColl
bpy.context.scene.view_layers[0].active_layer_collection.children[0].exclude = False
#Change the Active LayerCollection to 'My Collection'
layer_collection = bpy.context.view_layer.layer_collection
layerColl = recurLayerCollection(layer_collection, c.get_name())
bpy.context.view_layer.active_layer_collection = layerColl
# https://blender.stackexchange.com/questions/157828/how-to-duplicate-a-certain-collection-using-python
from collections import defaultdict
def copy_objects(from_col, to_col, linked, dupe_lut):
for o in from_col.objects:
dupe = o.copy()
if not linked and o.data:
dupe.data = dupe.data.copy()
to_col.objects.link(dupe)
dupe_lut[o] = dupe
def copy(parent, collection, linked=False):
dupe_lut = defaultdict(lambda : None)
def _copy(parent, collection, linked=False):
if collection.name == 'Bone Widgets':
return
cc = bpy.data.collections.new(collection.name)
copy_objects(collection, cc, linked, dupe_lut)
for c in collection.children:
_copy(cc, c, linked)
parent.children.link(cc)
_copy(parent, collection, linked)
for o, dupe in tuple(dupe_lut.items()):
parent = dupe_lut[o.parent]
if parent:
dupe.parent = parent
context = bpy.context
scene = context.scene
col = context.collection
assert(col is not scene.collection)
copy(scene.collection, col)
#setup materials for the combiner script
for obj in [o for o in bpy.data.collections[c.get_name() + '.001'].all_objects if not o.hide_get() and o.type == 'MESH']:
for mat in [mat_slot.material for mat_slot in obj.material_slots if mat_slot.material.get('simple')]:
nodes = mat.node_tree.nodes
links = mat.node_tree.links
emissive_node = nodes.new('ShaderNodeEmission')
image_node = nodes.new('ShaderNodeTexImage')
links.new(emissive_node.inputs[0], image_node.outputs[0])
image_node.image = nodes['textures'].node_tree.nodes['light'].image
context.view_layer.objects.active = obj
bpy.ops.object.material_slot_remove_unused()
#update the modifiers
for mod in obj.modifiers:
if mod.type == 'ARMATURE':
#fix the armature modifier to use the copied aramture
copied_armature = [o for o in bpy.data.collections[c.get_name() + '.001'].all_objects if o.type == 'ARMATURE'][0]
mod.object = copied_armature
elif mod.type == 'SOLIDIFY':
#disable the outline on the atlased object because I don't feel like fixing it
obj.modifiers['Outline Modifier'].show_render = False
obj.modifiers['Outline Modifier'].show_viewport = False
elif mod.type == 'UV_WARP':
#fix the UV warp modifier to use the copied armature
copied_armature = [o for o in bpy.data.collections[c.get_name() + '.001'].all_objects if o.type == 'ARMATURE'][0]
mod.object_from = copied_armature
mod.object_to = copied_armature
return {'FINISHED'}

27
extras/matcombswitch.py Normal file
View File

@@ -0,0 +1,27 @@
import bpy
from ..interface.dictionary_en import t
from .. import common as c
class mat_comb_switch(bpy.types.Operator):
bl_idname = "kkbp.matcombswitch"
bl_label = "Material combiner switch"
bl_description = t('mat_comb_switch_tt')
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
#toggle textures for the combiner script
for obj in [o for o in bpy.data.collections[c.get_name() + '.001'].all_objects if not o.hide_get() and o.type == 'MESH']:
for mat in [mat_slot.material for mat_slot in obj.material_slots if mat_slot.material.get('simple')]:
nodes = mat.node_tree.nodes
#find the image node
image_node = None
for node in nodes:
if node.type == 'TEX_IMAGE':
image_node = node
if image_node:
#toggle light / dark state
image_node.image = nodes['textures'].node_tree.nodes['dark' if image_node.image == nodes['textures'].node_tree.nodes['light'].image else 'light'].image
context.view_layer.objects.active = obj
bpy.ops.object.material_slot_remove_unused()
return {'FINISHED'}

18
extras/resetmaterials.py Normal file
View File

@@ -0,0 +1,18 @@
import bpy
from ..interface.dictionary_en import t
from .. import common as c
class reset_materials(bpy.types.Operator):
bl_idname = "kkbp.resetmaterials"
bl_label = "Reset KKBP materials"
bl_description = t('reset_mats_tt')
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
#setup materials for the combiner script
for obj in [o for o in bpy.data.collections[c.get_name()].all_objects if not o.hide_get() and o.type == 'MESH']:
for mat in [mat_slot.material for mat_slot in obj.material_slots if mat_slot.material.get('simple')]:
if bpy.data.materials.get(mat.name + '-ORG'):
org_mat = bpy.data.materials.get(mat.name + '-ORG')
mat.user_remap(org_mat)
return {'FINISHED'}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,312 @@
#Switch to Object Mode and select Generated Rig
import bpy
import traceback
import sys
import math
from math import radians
from . import commons as koikatsuCommons
def main():
generatedRig = bpy.context.active_object
assert generatedRig.mode == "OBJECT", 'assert generated_rig.mode == "OBJECT"'
assert generatedRig.type == "ARMATURE", 'assert generatedRig.type == "ARMATURE"'
generatedRig.show_in_front = True
generatedRig.display_type = 'TEXTURED'
"""
for i in range(32):
if i == 0 or i == 1:
generatedRig.data.layers[i] = True
else:
generatedRig.data.layers[i] = False
"""
hasRiggedTongue = generatedRig.pose.bones.get(koikatsuCommons.riggedTongueBone1Name)
hasBetterPenetrationMod = generatedRig.pose.bones.get(koikatsuCommons.betterPenetrationRootCrotchBoneName)
metarig = None
for bone in generatedRig.pose.bones:
if bone.name.startswith(koikatsuCommons.metarigIdBonePrefix):
generatedRigIdBoneName = bone.name
for object in bpy.data.objects:
if object != generatedRig and object.type == "ARMATURE":
for objectBone in object.pose.bones:
if objectBone.name == generatedRigIdBoneName:
metarig = object
break
break
if metarig:
metarig.hide_set(True)
for object in bpy.data.objects:
if object.type == "MESH":
if object.parent == metarig:
object.parent = generatedRig
for modifier in object.modifiers:
if modifier.type == "ARMATURE" and modifier.object == metarig:
modifier.object = generatedRig
if modifier.type == "UV_WARP" and modifier.object_from == metarig:
if modifier.name == "Left Eye UV warp":
modifier.object_from = generatedRig
modifier.bone_from = koikatsuCommons.eyesXBoneName
modifier.object_to = generatedRig
modifier.bone_to = koikatsuCommons.leftEyeBoneName
elif modifier.name == "Right Eye UV warp":
modifier.object_from = generatedRig
modifier.bone_from = koikatsuCommons.eyesXBoneName
modifier.object_to = generatedRig
modifier.bone_to = koikatsuCommons.rightEyeBoneName
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.eyesTrackTargetBoneName, 1.5)
if hasRiggedTongue:
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.riggedTongueLeftBone3Name, 0.25)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.riggedTongueRightBone3Name, 0.25)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.riggedTongueLeftBone4Name, 0.25)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.riggedTongueRightBone4Name, 0.25)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.riggedTongueLeftBone5Name, 0.25)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.riggedTongueRightBone5Name, 0.25)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.headTweakBoneName, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.crotchBoneName, 2)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.anusBoneName, 0.25)
if hasBetterPenetrationMod:
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationRootCrotchBoneName, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationFrontCrotchBoneName, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationLeftCrotchBone1Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationRightCrotchBone1Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationLeftCrotchBone2Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationRightCrotchBone2Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationLeftCrotchBone3Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationRightCrotchBone3Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationLeftCrotchBone4Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationRightCrotchBone4Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationLeftCrotchBone5Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationRightCrotchBone5Name, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.betterPenetrationBackCrotchBoneName, 0.09)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastDeformBone1Name, 0.4)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastDeformBone1Name, 0.4)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastBone2Name, 3)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastBone2Name, 3)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastDeformBone2Name, 0.3)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastDeformBone2Name, 0.3)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastBone3Name, 2)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastBone3Name, 2)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftBreastDeformBone3Name, 0.2)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightBreastDeformBone3Name, 0.2)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftNippleBone1Name, 1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightNippleBone1Name, 1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftNippleDeformBone1Name, 0.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightNippleDeformBone1Name, 0.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftNippleBone2Name, 0.5)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightNippleBone2Name, 0.5)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftNippleDeformBone2Name, 0.05)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightNippleDeformBone2Name, 0.05)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftShoulderBoneName, 1.25)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightShoulderBoneName, 1.25)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftThumbBone1Name, 1.3)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightThumbBone1Name, 1.3)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftThumbBone2Name, 1.3)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightThumbBone2Name, 1.3)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftThumbBone3Name, 1.2)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightThumbBone3Name, 1.2)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftIndexFingerBone2Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightIndexFingerBone2Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftIndexFingerBone3Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightIndexFingerBone3Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftMiddleFingerBone2Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightMiddleFingerBone2Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftMiddleFingerBone3Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightMiddleFingerBone3Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftRingFingerBone2Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightRingFingerBone2Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftRingFingerBone3Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightRingFingerBone3Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftLittleFingerPalmBoneName, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightLittleFingerPalmBoneName, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftLittleFingerPalmFkBoneName, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightLittleFingerPalmFkBoneName, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftLittleFingerBone2Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightLittleFingerBone2Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.leftLittleFingerBone3Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rightLittleFingerBone3Name, 1.1)
koikatsuCommons.setBoneCustomShapeScale(generatedRig, koikatsuCommons.rootBoneName, 0.35)
for bone in generatedRig.pose.bones:
if bpy.app.version[0] == 3:
if generatedRig.data.bones[bone.name].layers[koikatsuCommons.getRigifyLayerIndexByName(koikatsuCommons.faceLayerName + koikatsuCommons.mchLayerSuffix)] == True:
koikatsuCommons.setBoneCustomShapeScale(generatedRig, bone.name, 0.15)
else:
if generatedRig.data.bones[bone.name].collections.get(str(koikatsuCommons.getRigifyLayerIndexByName(koikatsuCommons.faceLayerName + koikatsuCommons.mchLayerSuffix))):
koikatsuCommons.setBoneCustomShapeScale(generatedRig, bone.name, 0.15)
headBone = generatedRig.pose.bones[koikatsuCommons.originalBonePrefix + koikatsuCommons.headBoneName]
koikatsuCommons.changeConstraintIndex(generatedRig, headBone.name, koikatsuCommons.transformationConstraintBaseName + koikatsuCommons.headConstraintSuffix + koikatsuCommons.rotationConstraintSuffix, len(headBone.constraints) - 1)
koikatsuCommons.changeConstraintIndex(generatedRig, headBone.name, koikatsuCommons.limitRotationConstraintBaseName + koikatsuCommons.headConstraintSuffix, len(headBone.constraints) - 1)
headTweakLimitRotationConstraint = koikatsuCommons.addLimitRotationConstraint(generatedRig, koikatsuCommons.headTweakBoneName, None, 'LOCAL', koikatsuCommons.limitRotationConstraintBaseName + koikatsuCommons.headConstraintSuffix,
True, math.radians(-180), math.radians(180), True, math.radians(-180), math.radians(180), True, math.radians(-180), math.radians(180))
for driver in generatedRig.animation_data.drivers:
if driver.data_path.startswith("pose.bones"):
driverOwnerName = driver.data_path.split('"')[1]
driverProperty = driver.data_path.rsplit('.', 1)[1]
if driverOwnerName == headBone.name:
constraintName = driver.data_path.split('"')[3]
if constraintName == koikatsuCommons.limitRotationConstraintBaseName + koikatsuCommons.headConstraintSuffix:
variable = driver.driver.variables[0]
newVariable = koikatsuCommons.DriverVariable(variable.name, variable.type, generatedRig, variable.targets[0].bone_target, None, None, None, None, variable.targets[0].data_path, None, None)
koikatsuCommons.addDriver(headTweakLimitRotationConstraint, driverProperty, None, driver.driver.type, [newVariable],
driver.driver.expression)
for bone in generatedRig.pose.bones:
for constraint in bone.constraints:
if constraint.type == 'ARMATURE':
for target in constraint.targets:
if target.subtarget:
target.target = generatedRig
if target.subtarget.endswith(koikatsuCommons.placeholderBoneSuffix):
target.subtarget = target.subtarget[:-len(koikatsuCommons.placeholderBoneSuffix)]
for driver in bpy.data.objects[koikatsuCommons.bodyName()].data.shape_keys.animation_data.drivers:
if driver.data_path.startswith("key_blocks"):
ownerName = driver.data_path.split('"')[1]
if ownerName == koikatsuCommons.eyelidsShapeKeyCopyName:
for variable in driver.driver.variables:
for target in variable.targets:
if target.id == metarig:
target.id = generatedRig
for driver in generatedRig.animation_data.drivers:
if driver.data_path.startswith("pose.bones"):
driverOwnerName = driver.data_path.split('"')[1]
driverProperty = driver.data_path.rsplit('.', 1)[1]
if driverOwnerName in koikatsuCommons.bonesWithDrivers and driverProperty == "location":
for variable in driver.driver.variables:
for target in variable.targets:
for targetToChange in koikatsuCommons.targetsToChange:
if target.bone_target == koikatsuCommons.originalBonePrefix + targetToChange:
target.bone_target = koikatsuCommons.deformBonePrefix + targetToChange
bpy.ops.object.mode_set(mode='EDIT')
for bone in generatedRig.data.edit_bones:
if bpy.app.version[0] == 3:
if generatedRig.data.bones[bone.name].layers[koikatsuCommons.getRigifyLayerIndexByName(koikatsuCommons.junkLayerName)] == True:
koikatsuCommons.deleteBone(generatedRig, bone.name)
continue
if generatedRig.data.bones[bone.name].layers[koikatsuCommons.defLayerIndex] == True:
bone.use_deform = True
else:
if generatedRig.data.bones[bone.name].collections.get(str(koikatsuCommons.getRigifyLayerIndexByName(koikatsuCommons.junkLayerName))):
koikatsuCommons.deleteBone(generatedRig, bone.name)
continue
if generatedRig.data.bones[bone.name].collections.get(str(koikatsuCommons.defLayerIndex)):
bone.use_deform = True
generatedRig.data.edit_bones[koikatsuCommons.eyesTrackTargetParentBoneName].parent = None
generatedRig.data.edit_bones[koikatsuCommons.headTrackTargetParentBoneName].parent = None
bpy.ops.object.mode_set(mode='OBJECT')
#koikatsuCommons.setBoneManagerLayersFromRigifyLayers(generatedRig)
#move some bones to the correct layer at the end because I don't feel like figuring out what went wrong
theme_dict = {
'purple':[1.000000, 0.266356, 0.955974],
'red':(0.8, 0, 0),
'yellow':(0.956863, 0.788235, 0.047059),
'blue':(0.005605, 0.104617, 0.947307),
'green':(0.026241, 0.693872, 0.004025),
'orange':(0.968628, 0.250980, 0.094118)
}
for bone in ['cf_j_tang_02', 'cf_j_tang_03', 'cf_j_tang_04', 'cf_j_tang_05', 'cf_j_tang_02.001']:
koikatsuCommons.assignSingleBoneLayer_except(generatedRig, bone, 3)
if generatedRig.data.bones.get(bone) and bpy.app.version[0] != 3:
generatedRig.data.bones[bone].color.custom.normal = theme_dict['green']
generatedRig.pose.bones[bone].color.custom.normal = theme_dict['green']
#Torso detail
for bone in ['tweak_Neck', 'Upper Chest_fk', 'tweak_Upper Chest', 'Chest_fk', 'tweak_Upper Chest', 'Chest_fk','tweak_Chest','Spine_fk','tweak_Spine','Hips_fk','tweak_Hips']:
koikatsuCommons.assignSingleBoneLayer_except(generatedRig, bone, 8)
if generatedRig.data.bones.get(bone) and bpy.app.version[0] != 3:
generatedRig.data.bones[bone].color.custom.normal = theme_dict['blue']
generatedRig.pose.bones[bone].color.custom.normal = theme_dict['blue']
for bone in ['Left wrist_fk', 'Left elbow_fk', 'Left arm_fk', 'Right arm_fk', 'Right elbow_fk', 'Right wrist_fk']:
koikatsuCommons.assignSingleBoneLayer_except(generatedRig, bone, 10 if 'Left' in bone else 13)
if generatedRig.data.bones.get(bone) and bpy.app.version[0] != 3:
generatedRig.data.bones[bone].color.custom.normal = theme_dict['green']
generatedRig.pose.bones[bone].color.custom.normal = theme_dict['green']
for bone in ['Left arm_tweak', 'Left arm_tweak.001', 'Left arm_tweak.002', 'Left elbow_tweak', 'Left elbow_tweak.001', 'Left elbow_tweak.002', 'Left wrist_tweak',
'Right arm_tweak', 'Right arm_tweak.001', 'Right arm_tweak.002', 'Right elbow_tweak', 'Right elbow_tweak.001', 'Right elbow_tweak.002', 'Right wrist_tweak']:
koikatsuCommons.assignSingleBoneLayer_except(generatedRig, bone, 11 if 'Left' in bone else 14)
if generatedRig.data.bones.get(bone) and bpy.app.version[0] != 3:
generatedRig.data.bones[bone].color.custom.normal = theme_dict['blue']
generatedRig.pose.bones[bone].color.custom.normal = theme_dict['blue']
for bone in ['Thumb0_L', 'Thumb1_L', 'Thumb2_L', 'Thumb0_L.001', 'IndexFinger1_L', 'IndexFinger2_L', 'IndexFinger3_L', 'IndexFinger1_L.001', 'MiddleFinger1_L', 'MiddleFinger2_L', 'MiddleFinger3_L', 'MiddleFinger1_L.001', 'RingFinger1_L', 'RingFinger2_L', 'RingFinger3_L', 'RingFinger1_L.001', 'LittleFinger1_L', 'LittleFinger2_L', 'LittleFinger3_L', 'LittleFinger1_L.001', 'Thumb0_R', 'Thumb1_R', 'Thumb2_R', 'Thumb0_R.001', 'IndexFinger1_R', 'IndexFinger2_R', 'IndexFinger3_R', 'IndexFinger1_R.001', 'MiddleFinger1_R', 'MiddleFinger2_R', 'MiddleFinger3_R', 'MiddleFinger1_R.001', 'RingFinger1_R', 'RingFinger2_R', 'RingFinger3_R', 'RingFinger1_R.001', 'LittleFinger1_R', 'LittleFinger2_R', 'LittleFinger3_R', 'LittleFinger1_R.001']:
koikatsuCommons.assignSingleBoneLayer_except(generatedRig, bone, 16)
if generatedRig.data.bones.get(bone) and bpy.app.version[0] != 3:
generatedRig.data.bones[bone].color.custom.normal = theme_dict['green']
generatedRig.pose.bones[bone].color.custom.normal = theme_dict['green']
for bone in ['Left leg_fk', 'Left knee_fk', 'Left ankle_fk', 'Left toe_fk', 'Right leg_fk', 'Right knee_fk', 'Right ankle_fk', 'Right toe_fk']:
koikatsuCommons.assignSingleBoneLayer_except(generatedRig, bone, 18 if 'Left' in bone else 21)
if generatedRig.data.bones.get(bone) and bpy.app.version[0] != 3:
generatedRig.data.bones[bone].color.custom.normal = theme_dict['green']
generatedRig.pose.bones[bone].color.custom.normal = theme_dict['green']
for bone in ['Left leg_tweak', 'Left leg_tweak.001', 'Left leg_tweak.002', 'Left knee_tweak', 'Left knee_tweak.001', 'Left knee_tweak.002', 'Left ankle_tweak', 'Right leg_tweak', 'Right leg_tweak.001', 'Right leg_tweak.002', 'Right knee_tweak', 'Right knee_tweak.001', 'Right knee_tweak.002', 'Right ankle_tweak']:
koikatsuCommons.assignSingleBoneLayer_except(generatedRig, bone, 19 if 'Left' in bone else 22)
if generatedRig.data.bones.get(bone) and bpy.app.version[0] != 3:
generatedRig.data.bones[bone].color.custom.normal = theme_dict['blue']
generatedRig.pose.bones[bone].color.custom.normal = theme_dict['blue']
for bone in ['cf_j_sk_00_00_ik', 'cf_j_sk_01_00_ik', 'cf_j_sk_02_00_ik', 'cf_j_sk_03_00_ik', 'cf_j_sk_04_00_ik', 'cf_j_sk_05_00_ik', 'cf_j_sk_06_00_ik', 'cf_j_sk_07_00_ik', 'cf_j_sk_00_00_master', 'cf_j_sk_01_00_master', 'cf_j_sk_02_00_master', 'cf_j_sk_03_00_master', 'cf_j_sk_04_00_master', 'cf_j_sk_05_00_master', 'cf_j_sk_06_00_master', 'cf_j_sk_07_00_master']:
koikatsuCommons.assignSingleBoneLayer_except(generatedRig, bone, 23)
if generatedRig.data.bones.get(bone) and bpy.app.version[0] != 3:
generatedRig.data.bones[bone].color.custom.normal = theme_dict['red']
generatedRig.pose.bones[bone].color.custom.normal = theme_dict['red']
if bpy.app.version[0] != 3:
bpy.context.object.pose.bones["root"].color.palette = 'CUSTOM'
generatedRig.data.bones['root'].color.custom.normal = theme_dict['yellow']
generatedRig.pose.bones['root'].color.custom.normal = theme_dict['yellow']
generatedRig.data.bones['root'].color.custom.select = (0.313989, 0.783538, 1.000000)
generatedRig.pose.bones['root'].color.custom.select = (0.313989, 0.783538, 1.000000)
generatedRig.data.bones['root'].color.custom.active = (0.552011, 1.000000, 1.000000)
generatedRig.pose.bones['root'].color.custom.active = (0.552011, 1.000000, 1.000000)
#set layer visibility
try:
for layer in ['None']:
generatedRig.data.collections_all[str(layer)].is_visible = False
except:
pass
for layer in [7,9,12,17,20]:
generatedRig.data.collections_all[str(layer)].is_visible = True
else:
#set layer visibility
generatedRig.data.layers[0] = True
for layer in range(1,32):
generatedRig.data.layers[layer] = False
for layer in [7,9,12,17,20,28]:
generatedRig.data.layers[layer] = True
generatedRig.data.layers[0] = False
class rigify_after(bpy.types.Operator):
bl_idname = "kkbp.rigafter"
bl_label = "After Each Rigify Generate - Public"
bl_description = 'Performs cleanup after a Rigify generation'
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
main()
return {'FINISHED'}

File diff suppressed because it is too large Load Diff

27
extras/rigifywrapper.py Normal file
View File

@@ -0,0 +1,27 @@
'''
Wrapper for the rigifyscripts folder
'''
import bpy, traceback
from .. import common as c
from ..importing.postoperations import post_operations
from ..interface.dictionary_en import t
class rigify_convert(bpy.types.Operator):
bl_idname = "kkbp.rigifyconvert"
bl_label = "Convert for rigify"
bl_description = t('rigify_convert_tt')
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
try:
bpy.context.scene.kkbp.armature_dropdown = 'B'
post_operations.retreive_stored_tags()
post_operations.apply_rigify()
return {'FINISHED'}
except:
c.kklog('Unknown python error occurred', type = 'error')
c.kklog(traceback.format_exc())
self.report({'ERROR'}, traceback.format_exc())
return {"CANCELLED"}

35
extras/updatebones.py Normal file
View File

@@ -0,0 +1,35 @@
import bpy
from ..interface.dictionary_en import t
from .. import common as c
class update_bones(bpy.types.Operator):
bl_idname = "kkbp.updatebones"
bl_label = "Update bones"
bl_description = t('bone_visibility_tt')
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
arm = c.get_rig() if c.get_rig() else c.get_armature()
arm = bpy.data.armatures[arm.data.name]
#check if the outfit linked to accessory bones on the armature is visible or not, then update the bone visibility
for bone in arm.bones:
if bone.get('id'):
hide_this_bone = True
for outfit_number in bone['id']:
matching_outfit = bpy.data.objects.get('Outfit ' + outfit_number + ' ' + c.get_name())
if matching_outfit:
print(matching_outfit.users_collection[0].name)
#also check if the collection this outfit belongs to is enabled in the viewlayer
outfit_collection = c.get_layer_collection_state(matching_outfit.users_collection[0].name)
if not outfit_collection and not matching_outfit.hide_get():
print("Enabling bone {} for outfit {}".format(bone.name, bone['id']))
hide_this_bone = False
break
bone.hide = hide_this_bone
return {'FINISHED'}
if __name__ == "__main__":
bpy.utils.register_class(update_bones)