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:
BIN
extras/__pycache__/createanimationlibrary.cpython-311.pyc
Normal file
BIN
extras/__pycache__/createanimationlibrary.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/createmapassetlibrary.cpython-311.pyc
Normal file
BIN
extras/__pycache__/createmapassetlibrary.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/imageconvert.cpython-311.pyc
Normal file
BIN
extras/__pycache__/imageconvert.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/importanimation.cpython-311.pyc
Normal file
BIN
extras/__pycache__/importanimation.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/importstudio.cpython-311.pyc
Normal file
BIN
extras/__pycache__/importstudio.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/linkhair.cpython-311.pyc
Normal file
BIN
extras/__pycache__/linkhair.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/linkshapekeys.cpython-311.pyc
Normal file
BIN
extras/__pycache__/linkshapekeys.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/matcombsetup.cpython-311.pyc
Normal file
BIN
extras/__pycache__/matcombsetup.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/matcombswitch.cpython-311.pyc
Normal file
BIN
extras/__pycache__/matcombswitch.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/resetmaterials.cpython-311.pyc
Normal file
BIN
extras/__pycache__/resetmaterials.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/rigifywrapper.cpython-311.pyc
Normal file
BIN
extras/__pycache__/rigifywrapper.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/__pycache__/updatebones.cpython-311.pyc
Normal file
BIN
extras/__pycache__/updatebones.cpython-311.pyc
Normal file
Binary file not shown.
@@ -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": {}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
BIN
extras/catsscripts/__pycache__/armature_manual.cpython-311.pyc
Normal file
BIN
extras/catsscripts/__pycache__/armature_manual.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/catsscripts/__pycache__/common.cpython-311.pyc
Normal file
BIN
extras/catsscripts/__pycache__/common.cpython-311.pyc
Normal file
Binary file not shown.
105
extras/catsscripts/armature_manual.py
Normal file
105
extras/catsscripts/armature_manual.py
Normal 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
2290
extras/catsscripts/common.py
Normal file
File diff suppressed because it is too large
Load Diff
451
extras/createanimationlibrary.py
Normal file
451
extras/createanimationlibrary.py
Normal 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'}
|
||||
355
extras/createmapassetlibrary.py
Normal file
355
extras/createmapassetlibrary.py
Normal 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
62
extras/imageconvert.py
Normal 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
315
extras/importanimation.py
Normal 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
413
extras/importstudio.py
Normal 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
26
extras/linkhair.py
Normal 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
99
extras/linkshapekeys.py
Normal 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
114
extras/matcombsetup.py
Normal 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
27
extras/matcombswitch.py
Normal 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
18
extras/resetmaterials.py
Normal 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'}
|
||||
BIN
extras/rigifyscripts/__pycache__/commons.cpython-311.pyc
Normal file
BIN
extras/rigifyscripts/__pycache__/commons.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/rigifyscripts/__pycache__/rigify_after.cpython-311.pyc
Normal file
BIN
extras/rigifyscripts/__pycache__/rigify_after.cpython-311.pyc
Normal file
Binary file not shown.
BIN
extras/rigifyscripts/__pycache__/rigify_before.cpython-311.pyc
Normal file
BIN
extras/rigifyscripts/__pycache__/rigify_before.cpython-311.pyc
Normal file
Binary file not shown.
1474
extras/rigifyscripts/commons.py
Normal file
1474
extras/rigifyscripts/commons.py
Normal file
File diff suppressed because it is too large
Load Diff
312
extras/rigifyscripts/rigify_after.py
Normal file
312
extras/rigifyscripts/rigify_after.py
Normal 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'}
|
||||
|
||||
2164
extras/rigifyscripts/rigify_before.py
Normal file
2164
extras/rigifyscripts/rigify_before.py
Normal file
File diff suppressed because it is too large
Load Diff
27
extras/rigifywrapper.py
Normal file
27
extras/rigifywrapper.py
Normal 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
35
extras/updatebones.py
Normal 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)
|
||||
Reference in New Issue
Block a user