Files
KKBP_Importer/importing/modifyarmature.py
小煜 63e9a6fb00 feat(importing): Add better FBX bone orientations operator and accessory bone deletion
- Add new `apply_better_fbx_orientations` operator as standalone UI action for applying bone orientations
- Add "Delete Accessory Bones" property and UI toggle in EXTRAS panel for optional bone cleanup
- Move bone orientation logic from `modify_armature` execute method to new dedicated operator
- Refactor accessory bone deletion into separate method callable from new operator
- Update panel UI to show better FBX bones operator with delete accessory bones toggle option
- Register new operator in `__init__.py` and export from modifyarmature module
- Update interface dictionaries (English, Japanese, Chinese) with new UI strings
- Enable better FBX orientations and accessory deletion only for Koikatsu armature (C) selection
- Add detailed logging for bone operation counts and status tracking
2025-12-07 12:47:55 +08:00

2421 lines
120 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# This file performs the following operations
# Removes empties and parents all objects to the Body armature
# Removes mmd bone constraints and bone drivers, unlocks all bones
# Scales armature bones down by a factor of 12
# Reparents the leg03 bone and p_cf_body_bone to match koikatsu armature
# Deletes all bones not parented to the cf_n_height bone
# Move and rotate finger bones to match koikatsu in game armature
# Set bone roll data to match koikatsu in game armature
# Slightly bend some bones outward to better support IKs
# Delete empty vertex groups on the body as long as it's not a bone on the armature
# Places each bone type (core bones, skirt bones, cf_s_ bones, etc) onto different armature layers
# Identifies accessory bones and moves them to their own armature layer
# Also sets the outfit ID for each accessory bone (not all accessory bones need to be visible at all times,
# so only the current outfit bones will be shown at the end)
# (KKBP armature) Visually connects all toe bones
# (KKBP armature) Scales all skirt / face / eye / BP bones, connects all skirt bones
# (KKBP Armature) Creates a reference bone for the eye uv warp modifier (Eyesx)
# (KKBP armature) Creates an eye controller bone for the eye uv warp modifier (Eye Controller)
# (KKBP armature) shortens kokan bone
# (KKBP armature) resizes skirt and face bones
# (KKBP armature) Repurposes pv bones for IK functionality
# (KKBP armature) Creates a foot IK, hand IK, heel controller
# (KKBP armature) Setup bone drivers for correction bones
# (KKBP armature) Moves new bones for IK / eyes to correct armature layers
# (KKBP armature) Adds bones to bone groups to give them colors
# (KKBP armature) Renames some core bones to be more readable
# Set mmd bone names for each bone
# (KKBP armature) Load custom bone widgets from the KK Shader file to apply to the armature
# (KKBP armature) Hide everything but the core bones
# (KKBP armature) Hide bone widgets collection
# Survey code was taken from MediaMoots here https://github.com/FlailingFog/KK-Blender-Shader-Pack/issues/29
# Majority of the joint driver corrections were taken from a blend file by johnbbob_la_petite on the koikatsu discord
import bpy, math
from .. import common as c
from mathutils import Vector
# Better FBX 骨骼尾部偏移数据 - 用于设置更自然的骨骼方向
# 这些数据从 better_fbx 导入的模型中提取,可以让骨骼沿着肢体方向延伸
# 而不是所有骨骼都朝上的小线段
# 包含所有 181 个骨骼的完整数据
better_fbx_bone_tails = {
'p_cf_body_00': (0.0000000000, 0.0000000000, 0.5717499852),
'cf_o_root': (0.0000000000, 0.0000000000, 0.0214350224),
'n_body': (0.0000000000, 0.0000000000, 0.0214350224),
'o_body_a': (0.0000000000, 0.0000000000, 0.0214350224),
'cf_j_root': (0.0000000000, 0.0000000000, 0.0099999998),
'cf_n_height': (0.0000000000, 0.0000000000, 1.1434999704),
'cf_j_hips': (0.0000000000, 0.0000000000, 0.0214350224),
'cf_j_waist01': (0.0000000050, 0.0082969246, -0.0676312447),
'cf_j_waist02': (0.0000061035, 0.0022072941, -0.0269574523),
'cf_d_ana': (0.0000000011, -0.0037057437, 0.0183483958),
'cf_j_ana': (0.0000000000, 0.0004356094, -0.0009342432),
'cf_s_ana': (0.0000000000, 0.0004356094, -0.0009342432),
'cf_d_kokan': (0.0000000003, -0.0000340615, 0.0193923712),
'cf_j_kokan': (0.0000000003, -0.0000340852, 0.0193923712),
'cf_d_siri_L': (0.0006995648, 0.0001653936, 0.0200324059),
'cf_d_siri01_L': (-0.0000022426, 0.0499999970, 0.0000028610),
'cf_j_siri_L': (0.0006995425, 0.0006653816, 0.0200324059),
'cf_s_siri_L': (0.0006995425, 0.0006653816, 0.0200324059),
'cf_d_siri_R': (-0.0007000044, 0.0001655202, 0.0200324059),
'cf_d_siri01_R': (0.0000000000, 0.0499999858, 0.0000020266),
'cf_j_siri_R': (-0.0007000044, 0.0006655231, 0.0200324059),
'cf_s_siri_R': (-0.0007000044, 0.0006655082, 0.0200324059),
'cf_j_thigh00_L': (0.0000000000, 0.0045367014, -0.2599085569),
'cf_d_thigh01_L': (0.0008304268, -0.0000085849, 0.0197808743),
'cf_s_thigh01_L': (0.0008304268, -0.0000085849, 0.0197808743),
'cf_d_thigh02_L': (0.0008304268, 0.0000311676, 0.0175034404),
'cf_s_thigh02_L': (0.0008304268, 0.0000311676, 0.0175034404),
'cf_d_thigh03_L': (0.0008304268, 0.0000528507, 0.0162612200),
'cf_s_thigh03_L': (0.0008304268, 0.0000528507, 0.0162612200),
'cf_j_leg01_L': (-0.0000001565, -0.0001882389, -0.2238392234),
'cf_d_kneeF_L': (-0.0000010356, -0.0399756283, -0.0013961792),
'cf_d_leg02_L': (0.0008304194, -0.0000160560, 0.0126936734),
'cf_s_leg02_L': (0.0004141256, 0.0006808266, -0.0072855055),
'cf_d_leg03_L': (0.0008304194, 0.0000490360, 0.0108296350),
'cf_s_leg03_L': (0.0008304194, 0.0000490360, 0.0108296350),
'cf_j_leg03_L': (0.0000000000, 0.0117738210, -0.0496065579),
'cf_j_foot_L': (-0.0000000075, -0.1070634499, -0.0073633678),
'cf_j_toes_L': (-0.0000000075, -0.1070634536, -0.0073633678),
'cf_s_leg01_L': (0.0000015497, 0.0599634517, 0.0020940304),
'cf_s_kneeB_L': (0.0000015497, 0.0599634424, 0.0020940304),
'cf_j_thigh00_R': (0.0000000149, 0.0045371708, -0.2599344850),
'cf_d_thigh01_R': (-0.0008299947, -0.0000085682, 0.0197798610),
'cf_s_thigh01_R': (-0.0008299947, -0.0000085682, 0.0197798610),
'cf_d_thigh02_R': (-0.0008299947, 0.0000311676, 0.0175034404),
'cf_s_thigh02_R': (-0.0008299947, 0.0000311676, 0.0175034404),
'cf_d_thigh03_R': (-0.0008299947, 0.0000528507, 0.0162612200),
'cf_s_thigh03_R': (-0.0008299947, 0.0000528507, 0.0162612200),
'cf_j_leg01_R': (0.0000000000, -0.0001882352, -0.2238395214),
'cf_d_kneeF_R': (-0.0000000894, -0.0399756199, -0.0013961196),
'cf_d_leg02_R': (-0.0008300021, -0.0000160560, 0.0126936436),
'cf_s_leg02_R': (-0.0008300021, -0.0000160560, 0.0126936436),
'cf_d_leg03_R': (-0.0008300021, 0.0000490360, 0.0108296126),
'cf_s_leg03_R': (-0.0008300021, 0.0000490360, 0.0108296126),
'cf_j_leg03_R': (0.0000000745, 0.0117738321, -0.0495891199),
'cf_j_foot_R': (0.0000000447, -0.1070637517, -0.0073810909),
'cf_j_toes_R': (0.0000000447, -0.1070637591, -0.0073810909),
'cf_s_leg01_R': (0.0000001490, 0.0599634480, 0.0020939708),
'cf_s_kneeB_R': (0.0000001490, 0.0599634498, 0.0020939708),
'cf_s_waist02': (0.0000041698, 0.0000045616, 0.0999919176),
'cf_s_leg_L': (0.1000121087, 0.0000034906, 0.0999964476),
'cf_s_leg_R': (-0.1000037491, 0.0000056308, 0.0999873877),
'cf_s_waist01': (0.0000000001, 0.0000000000, 0.0213849545),
'cf_j_spine01': (0.0000000000, 0.0032969250, 0.0450000763),
'cf_s_spine01': (0.0000000000, 0.0000000000, 0.0214849710),
'cf_j_spine02': (0.0000000000, 0.0050841947, 0.0449999571),
'cf_s_spine02': (0.0000000000, 0.0000659386, 0.0223850012),
'cf_j_spine03': (0.0000064438, 0.0028000008, 0.0566434860),
'cf_j_neck': (0.0000000000, 0.0000000000, 0.0325000286),
'cf_j_head': (0.0000000000, 0.0001676232, 0.0251541138),
'cf_s_head': (0.0000000000, 0.0001676232, 0.0251541138),
'cf_s_neck': (0.0000000000, 0.0001676232, 0.0245041847),
'cf_s_spine03': (0.0000000000, 0.0001676232, 0.0232850313),
'cf_d_shoulder_L': (0.0001563374, 0.0002376232, 0.0241320133),
'cf_j_shoulder_L': (0.0940132719, -0.0000001993, 0.0000000000),
'cf_d_shoulder02_L': (0.0010964721, 0.0002376176, 0.0241320133),
'cf_s_shoulder02_L': (0.0010964721, 0.0002376176, 0.0241320133),
'cf_j_arm00_L': (0.1489044800, 0.0066715311, 0.0000000000),
'cf_d_arm01_L': (0.0012971163, 0.0002268590, 0.0241320133),
'cf_s_arm01_L': (0.0012968481, 0.0002330560, 0.0241320133),
'cf_d_arm02_L': (0.0022885203, 0.0002723690, 0.0241320133),
'cf_s_arm02_L': (0.0022885203, 0.0002723690, 0.0241320133),
'cf_d_arm03_L': (0.0031349957, 0.0003089495, 0.0241320133),
'cf_s_arm03_L': (0.0031350553, 0.0003072731, 0.0241320133),
'cf_j_forearm01_L': (0.1090970039, 0.0050626770, 0.0000000000),
'cf_d_forearm02_L': (0.0046593249, 0.0003493950, 0.0241320133),
'cf_s_forearm02_L': (0.0046591461, 0.0003536306, 0.0241320133),
'cf_d_wrist_L': (0.0055440068, 0.0003493391, 0.0241320133),
'cf_s_wrist_L': (0.0250180364, -0.0004227310, 0.0000002384),
'cf_d_hand_L': (0.0250180364, -0.0004227310, 0.0000002384),
'cf_j_hand_L': (0.0061240196, 0.0003494397, 0.0241320133),
'cf_s_hand_L': (0.0434948206, -0.0036587194, -0.0018885136),
'cf_j_index01_L': (0.0308244824, -0.0027071098, -0.0026963949),
'cf_j_index02_L': (0.0205492973, -0.0018050103, -0.0018019676),
'cf_j_index03_L': (0.0205492973, -0.0018050103, -0.0018019676),
'cf_j_little01_L': (0.0280875564, 0.0039625615, -0.0024572611),
'cf_j_little02_L': (0.0152931809, 0.0021575317, -0.0013381243),
'cf_j_little03_L': (0.0152931809, 0.0021575317, -0.0013381243),
'cf_j_middle01_L': (0.0340363383, 0.0000000112, -0.0029777288),
'cf_j_middle02_L': (0.0226911902, 0.0000002403, -0.0019830465),
'cf_j_middle03_L': (0.0226911902, 0.0000002403, -0.0019830465),
'cf_j_ring01_L': (0.0324101448, 0.0022749901, -0.0028355122),
'cf_j_ring02_L': (0.0216068029, 0.0015166700, -0.0018904209),
'cf_j_ring03_L': (0.0216068029, 0.0015166700, -0.0018904209),
'cf_j_thumb01_L': (0.0274280310, -0.0195015902, -0.0048363209),
'cf_j_thumb02_L': (0.0201677084, -0.0143394098, -0.0035561323),
'cf_j_thumb03_L': (0.0201677084, -0.0143394079, -0.0035561323),
'cf_s_elbo_L': (-0.0000313818, 0.0248819850, 0.0000001192),
'cf_s_forearm01_L': (0.0004531145, -0.0307315015, 0.0000000000),
'cf_s_elboback_L': (0.0004531145, -0.0307315006, 0.0000000000),
'cf_d_bust00': (0.0000000000, 0.0001676232, 0.0232039690),
'cf_s_bust00_R': (-0.0575999990, -0.0454922598, 0.0026999712),
'cf_d_bust01_R': (-0.0002007224, 0.0006882958, 0.0231761932),
'cf_j_bust01_R': (-0.0051772669, -0.0134591460, 0.0015155077),
'cf_d_bust02_R': (-0.0003042668, 0.0004191138, 0.0232065916),
'cf_j_bust02_R': (-0.0046416745, -0.0120668225, 0.0013588667),
'cf_d_bust03_R': (-0.0003971010, 0.0001777783, 0.0232336521),
'cf_j_bust03_R': (-0.0017852709, -0.0046411008, 0.0005227327),
'cf_d_bnip01_R': (-0.0006545857, -0.0017017275, 0.0001918077),
'cf_j_bnip02root_R': (-0.0048966482, -0.0061617792, 0.0016967058),
'cf_s_bnip02_R': (-0.0004578009, 0.0000199825, 0.0232515335),
'cf_j_bnip02_R': (-0.0004578009, 0.0000199825, 0.0232515335),
'cf_s_bnip025_R': (-0.0083650872, -0.0086106881, 0.0029753447),
'cf_s_bnip01_R': (-0.0004328042, 0.0000849515, 0.0232441425),
'cf_s_bnip015_R': (-0.0008926094, -0.0023205206, 0.0002615452),
'cf_s_bust03_R': (-0.0003971010, 0.0001777783, 0.0232336521),
'cf_s_bust02_R': (-0.0003042668, 0.0004191138, 0.0232064724),
'cf_s_bust01_R': (-0.0002007224, 0.0006882958, 0.0231761932),
'cf_s_bust00_L': (0.0575999990, -0.0454922598, 0.0026999712),
'cf_d_bust01_L': (0.0002007224, 0.0006882958, 0.0231761932),
'cf_j_bust01_L': (0.0051772669, -0.0134591460, 0.0015155077),
'cf_d_bust02_L': (0.0003042668, 0.0004191138, 0.0232065916),
'cf_j_bust02_L': (0.0046416819, -0.0120668299, 0.0013588667),
'cf_d_bust03_L': (0.0003971010, 0.0001777783, 0.0232336521),
'cf_j_bust03_L': (0.0017852709, -0.0046411008, 0.0005227327),
'cf_d_bnip01_L': (0.0006546006, -0.0017017350, 0.0001916885),
'cf_j_bnip02root_L': (0.0049032941, -0.0061767399, 0.0016988516),
'cf_s_bnip02_L': (0.0004578009, 0.0000199825, 0.0232515335),
'cf_j_bnip02_L': (0.0004578009, 0.0000199825, 0.0232515335),
'cf_s_bnip025_L': (0.0083783790, -0.0086406097, 0.0029795170),
'cf_s_bnip01_L': (0.0004328042, 0.0000849515, 0.0232441425),
'cf_s_bnip015_L': (0.0008926317, -0.0023205578, 0.0002613068),
'cf_s_bust03_L': (0.0003971010, 0.0001777783, 0.0232336521),
'cf_s_bust02_L': (0.0003042668, 0.0004191138, 0.0232064724),
'cf_s_bust01_L': (0.0002007224, 0.0006882958, 0.0231761932),
'cf_d_shoulder_R': (-0.0001560142, 0.0002376232, 0.0241320133),
'cf_j_shoulder_R': (-0.0939531336, -0.0000126194, 0.0000000000),
'cf_d_shoulder02_R': (-0.0010954589, 0.0002373699, 0.0241320133),
'cf_s_shoulder02_R': (-0.0010954589, 0.0002373699, 0.0241320133),
'cf_j_arm00_R': (-0.1495516151, 0.0065441933, 0.0000001192),
'cf_d_arm01_R': (-0.0012957752, 0.0002367441, 0.0241320133),
'cf_s_arm01_R': (-0.0012955368, 0.0002426561, 0.0241320133),
'cf_d_arm02_R': (-0.0022943318, 0.0002824105, 0.0241320133),
'cf_s_arm02_R': (-0.0022938997, 0.0002823994, 0.0241320133),
'cf_d_arm03_R': (-0.0031512678, 0.0003276281, 0.0241320133),
'cf_s_arm03_R': (-0.0031512082, 0.0003276169, 0.0241320133),
'cf_j_forearm01_R': (-0.1111739874, 0.0051015355, -0.0000002384),
'cf_s_elbo_R': (0.0000000298, 0.0251077451, 0.0000021458),
'cf_s_forearm01_R': (-0.0000276864, -0.0304288915, -0.0000027418),
'cf_s_elboback_R': (-0.0000276864, -0.0304288925, -0.0000027418),
'cf_d_forearm02_R': (-0.0047625899, 0.0003447086, 0.0241320133),
'cf_s_forearm02_R': (-0.0047624111, 0.0003491975, 0.0241320133),
'cf_d_wrist_R': (-0.0055437088, 0.0003451817, 0.0241320133),
'cf_s_wrist_R': (-0.0250178576, -0.0004224181, 0.0000003576),
'cf_d_hand_R': (-0.0250178576, -0.0004224181, 0.0000003576),
'cf_j_hand_R': (-0.0061238408, 0.0003451258, 0.0241320133),
'cf_s_hand_R': (-0.0434947014, -0.0036587510, -0.0018894672),
'cf_j_little01_R': (-0.0281152725, 0.0039664730, -0.0024597645),
'cf_j_little02_R': (-0.0152906775, 0.0021577775, -0.0013365746),
'cf_j_little03_R': (-0.0152906775, 0.0021577775, -0.0013365746),
'cf_j_middle01_R': (-0.0340698957, -0.0000000205, -0.0029808283),
'cf_j_middle02_R': (-0.0227140188, 0.0000003595, -0.0019767284),
'cf_j_middle03_R': (-0.0227140188, 0.0000003595, -0.0019767284),
'cf_j_ring01_R': (-0.0323968530, 0.0022740699, -0.0028343201),
'cf_j_ring02_R': (-0.0215651989, 0.0015144981, -0.0018799305),
'cf_j_ring03_R': (-0.0215651989, 0.0015144981, -0.0018799305),
'cf_j_thumb01_R': (-0.0274280310, -0.0195016302, -0.0048363209),
'cf_j_thumb02_R': (-0.0201677680, -0.0143393399, -0.0035561323),
'cf_j_thumb03_R': (-0.0201677680, -0.0143393390, -0.0035561323),
'cf_j_index01_R': (-0.0308635831, -0.0027105501, -0.0027000904),
'cf_j_index02_R': (-0.0205416679, -0.0018021995, -0.0018149614),
'cf_j_index03_R': (-0.0205416679, -0.0018021995, -0.0018149614),
# 头部骨骼 (p_cf_head_bone 及其子骨骼共74个)
'p_cf_head_bone': (0.0, 0.0, 0.0099999998),
'cf_J_N_FaceRoot': (-4e-10, 0.0, 0.0099999998),
'cf_J_FaceRoot': (0.0, -0.0060000001, 0.0199999996),
'cf_J_FaceBase': (6.7e-09, -0.0321580991, 0.0155400224),
'cf_J_FaceUp_ty': (-5e-09, -0.0012981864, 0.0153640173),
'cf_J_EarBase_ry_L': (0.0040000007, 0.0065659345, 0.0013576597),
'cf_J_EarLow_L': (-0.0119999945, 0.0036456594, -0.0224846825),
'cf_J_EarUp_L': (0.0200000033, 0.0094862096, 0.0251999944),
'cf_J_EarBase_ry_R': (-0.0040000007, 0.0065659294, 0.0010771602),
'cf_J_EarLow_R': (0.0120000094, 0.0036456697, -0.0230684578),
'cf_J_EarUp_R': (-0.0200000033, 0.0094861891, 0.0252227858),
'cf_J_FaceUp_tz': (1.5e-08, -0.0822834074, 0.0294848457),
'cf_J_Eye_tz': (1.74653e-05, 7.5e-09, 0.0040000007),
'cf_J_Eye_txdam_L': (0.0005303472, -0.0006456077, 0.0108191594),
'cf_J_Eye_tx_L': (0.0005303472, -0.0006456077, 0.0108191594),
'cf_J_Eye_rz_L': (0.0020232424, -0.0143185034, -0.0075755417),
'cf_J_CheekUp2_L': (0.0024154074, 0.0097409822, -0.0335302614),
'cf_J_Eye01_s_L': (-0.0192517117, -0.0222310573, -0.0043519735),
'cf_J_Eye02_s_L': (-0.0125349201, -0.0245135501, 0.0108480155),
'cf_J_Eye03_s_L': (0.0034999885, -0.0231375471, 0.0147431493),
'cf_J_Eye04_s_L': (0.0165117607, -0.0177816749, 0.008518368),
'cf_J_Eye05_s_L': (0.0244611353, -0.0106068701, -0.0038318336),
'cf_J_Eye06_s_L': (0.0179830194, -0.0083362609, -0.0178645924),
'cf_J_Eye07_s_L': (0.0006974302, -0.0135568678, -0.0231180415),
'cf_J_Eye08_s_L': (-0.0155729428, -0.0184436664, -0.0195927024),
'cf_J_hitomi_tx_L': (0.0, 0.0, 0.01), # 左眼瞳控制骨骼
'cf_J_Eye_txdam_R': (-0.0005300008, -0.0006456077, 0.0108191594),
'cf_J_Eye_tx_R': (-0.0005300008, -0.0006456077, 0.0108191594),
'cf_J_Eye_rz_R': (-0.0018717796, -0.0143385679, -0.007591337),
'cf_J_CheekUp2_R': (-0.0024154112, 0.0097409934, -0.03353028),
'cf_J_Eye01_s_R': (0.0192652196, -0.0221798792, -0.0043867007),
'cf_J_Eye02_s_R': (0.012559589, -0.0245160758, 0.0108663738),
'cf_J_Eye03_s_R': (-0.00215628, -0.0233592987, 0.0145589486),
'cf_J_Eye04_s_R': (-0.0165314898, -0.017822504, 0.0085391998),
'cf_J_Eye05_s_R': (-0.0245011002, -0.0106083676, -0.0038220957),
'cf_J_Eye06_s_R': (-0.0179587901, -0.0083584338, -0.0178358555),
'cf_J_Eye07_s_R': (-0.0006720722, -0.0135397911, -0.0230915993),
'cf_J_Eye08_s_R': (0.0155643187, -0.018403694, -0.0196200199),
'cf_J_hitomi_tx_R': (0.0, 0.0, 0.01), # 右眼瞳控制骨骼
'cf_J_Mayu_ty': (1.36853e-05, 0.0106744692, 0.0),
'cf_J_Mayumoto_L': (0.0005722754, -0.0010105595, 0.0111785308),
'cf_J_Mayu_L': (0.0005722754, -0.0010105595, 0.0111785308),
'cf_J_MayuMid_s_L': (-0.0148523003, -0.0054057986, -3.12626e-05),
'cf_J_MayuTip_s_L': (0.0133357644, 0.0048538223, 0.0),
'cf_J_Mayumoto_R': (-0.0005719997, -0.0010160729, 0.0111785308),
'cf_J_Mayu_R': (-0.0005719997, -0.0010160729, 0.0111785308),
'cf_J_MayuMid_s_R': (0.0140953921, -0.0051302984, -3.12626e-05),
'cf_J_MayuTip_s_R': (-0.0125368983, 0.0045630634, -3.12626e-05),
'cf_J_FaceLow_tz': (5e-09, -0.025674995, -0.0074298549),
'cf_J_MouthBase_ty': (-2e-10, -0.0008716434, 0.0101031829),
'cf_J_MouthBase_rx': (-5e-09, 0.0124999955, 0.0),
'cf_J_MouthCavity': (-1e-08, 0.0249999985, 0.0),
'cf_J_MouthMove': (0.0, -0.0028492734, -0.0003052),
'cf_J_Mouth_L': (0.0060000005, -0.0007560551, 0.0),
'cf_J_Mouth_R': (-0.0059999996, -0.0007560551, 0.0),
'cf_J_MouthLow': (0.0, -0.0030992031, -0.0026312196),
'cf_J_Mouthup': (0.0, -0.0067858025, 0.0014104201),
'cf_J_FaceLow_sx': (0.0, -0.0006982945, -0.0140105523),
'cf_J_Chin_Base': (2e-10, -0.0075257719, -0.0307464823),
'cf_J_CheekLow_s_L': (0.0259999987, 7.5e-09, -0.0293771196),
'cf_J_CheekLow_s_R': (-0.0259999968, 0.0, -0.0293771196),
'cf_J_Chin_s': (1e-08, -0.0174226761, -0.0165147893),
'cf_J_ChinTip_Base': (1e-08, -0.0174226761, -0.0165147875),
'cf_J_ChinLow': (0.0, -0.0020948797, -0.0420316607),
'cf_J_CheekUpBase': (1.2e-09, -0.0249999985, 0.0044998601),
'cf_J_CheekUp_s_L': (0.0459999964, -0.0249999911, 0.0045103002),
'cf_J_CheekUp_s_R': (-0.0460000038, -0.0250000022, 0.00448942),
'cf_J_NoseBase': (5e-09, -0.0072341338, 0.012515638),
'cf_J_NoseBase_rx': (1e-08, -0.0150000006, 0.0),
'cf_J_Nose_rx': (0.0, -0.0091642886, 0.0),
'cf_J_Nose_tip': (0.0, -0.0091642886, 0.0),
'cf_J_NoseBridge_ty': (-2e-10, -0.0008712783, 0.010564737),
'cf_J_NoseBridge_rx': (-2e-10, -0.0008714795, 0.010564737),
'N_tonn_face': (0.0, 0.0, 0.0099999998),
'N_cf_haed': (0.0, 0.0, 0.0099999998),
'cf_O_face_atari': (0.0, 0.0, 0.0099999998),
}
class modify_armature(bpy.types.Operator):
bl_idname = "kkbp.modifyarmature"
bl_label = bl_idname
bl_description = bl_idname
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
try:
self.reparent_all_objects()
self.remove_bone_locks_and_modifiers()
self.scale_armature_bones_down()
self.reparent_leg_and_body_bone()
self.delete_non_height_bones()
self.modify_finger_bone_orientations()
self.set_bone_roll_data()
self.bend_bones_for_iks()
self.remove_empty_vertex_groups()
self.reorganize_armature_layers()
self.move_accessory_bones_to_layer10()
self.create_eye_reference_bone()
self.create_eye_controller_bone()
self.shorten_kokan_bone()
self.scale_skirt_and_face_bones()
self.prepare_ik_bones()
self.create_ik_bones()
self.create_joint_drivers()
self.categorize_bones()
self.rename_bones_for_clarity()
self.rename_mmd_bones()
self.apply_bone_widgets()
self.hide_widgets()
return {'FINISHED'}
except Exception as error:
c.handle_error(self, error)
return {"CANCELLED"}
# %% Main functions
def reparent_all_objects(self):
'''Reparents all objects to the main armature'''
armature = c.get_armature()
outfits = c.get_outfits()
body = c.get_body()
c.switch(armature, 'object')
armature.parent = None
armature.name = 'Armature'
#edit armature modifier on body
body.modifiers[0].show_in_editmode = True
body.modifiers[0].show_on_cage = True
body.modifiers[0].show_expanded = False
body.modifiers[0].name = 'Armature modifier'
#reparent the outfit meshes as well
for outfit in outfits:
outfit_armature = outfit.parent.name
outfit.parent = armature
outfit.modifiers[0].object = armature
outfit.modifiers[0].show_in_editmode = True
outfit.modifiers[0].show_on_cage = True
outfit.modifiers[0].show_expanded = False
outfit.modifiers[0].name = 'Armature modifier'
bpy.data.objects.remove(bpy.data.objects[outfit_armature])
#remove the empties
empties = c.get_empties()
for empty in empties:
bpy.data.objects.remove(empty)
#reparent the alts and hairs to the main outfit object
for alt in c.get_alts():
alt.parent = armature
alt.modifiers[0].object = armature
alt.modifiers[0].show_in_editmode = True
alt.modifiers[0].show_on_cage = True
alt.modifiers[0].show_expanded = False
alt.modifiers[0].name = 'Armature modifier'
for hair in c.get_hairs():
hair.parent = armature
hair.modifiers[0].object = armature
hair.modifiers[0].show_in_editmode = True
hair.modifiers[0].show_on_cage = True
hair.modifiers[0].show_expanded = False
hair.modifiers[0].name = 'Armature modifier'
#reparent the tongue, tears and gag eyes if they exist
objects = []
if c.get_tongue():
objects.append(c.get_tongue())
if c.get_tears():
objects.append(c.get_tears())
if c.get_gags():
objects.append(c.get_gags())
for object in objects:
object.parent = body
#reparent hitboxes if they exist
for hb in c.get_hitboxes():
hb.parent = armature
c.print_timer('reparent_all_objects')
def scale_armature_bones_down(self):
'''scale all bone sizes down by a factor of 12. (all armature bones must be sticking upwards)'''
c.switch(c.get_armature(), 'edit')
for bone in c.get_armature().data.edit_bones:
bone.tail.z = bone.head.z + (bone.tail.z - bone.head.z)/12
c.print_timer('scale_armature_bones_down')
def remove_bone_locks_and_modifiers(self):
'''Removes mmd bone constraints and bone drivers, unlocks all bones'''
#remove all constraints from all bones
armature = c.get_armature()
c.switch(armature, 'pose')
for bone in armature.pose.bones:
for constraint in bone.constraints:
bone.constraints.remove(constraint)
#remove all drivers from all armature bones
#animation_data is nonetype if no drivers have been created yet
if armature.animation_data:
drivers_data = armature.animation_data.drivers
for driver in drivers_data:
armature.driver_remove(driver.data_path, -1)
#unlock the armature and all bones
armature.lock_location = [False, False, False]
armature.lock_rotation = [False, False, False]
armature.lock_scale = [False, False, False]
for bone in armature.pose.bones:
bone.lock_location = [False, False, False]
c.print_timer('remove_bone_locks_and_modifiers')
def reparent_leg_and_body_bone(self):
'''Reparent the leg bone to match the koikatsu armature. Unparent the body_bone bone to match koikatsu armature'''
if bpy.context.scene.kkbp.armature_dropdown != 'D':
armature = c.get_armature()
#reparent foot to leg03
armature.data.edit_bones['cf_j_foot_R'].parent = armature.data.edit_bones['cf_j_leg03_R']
armature.data.edit_bones['cf_j_foot_L'].parent = armature.data.edit_bones['cf_j_leg03_L']
#unparent body bone to match KK
armature.data.edit_bones['p_cf_body_bone'].parent = None
c.print_timer('reparent_leg_and_body_bone')
def delete_non_height_bones(self):
'''delete bones not under the cf_n_height bone. Deletes bones not under the BodyTop bone if PMX armature was selected'''
armature = c.get_armature()
def select_children(parent):
try:
parent.select = True
parent.select_head = True
parent.select_tail = True
for child in parent.children:
select_children(child)
except:
#This is the last bone in the chain
return
if bpy.context.scene.kkbp.armature_dropdown == 'D':
select_children(armature.data.edit_bones['BodyTop'])
else:
select_children(armature.data.edit_bones['cf_n_height'])
#make sure these bones aren't deleted
for preserve_bone in ['cf_j_root', 'p_cf_body_bone', 'cf_n_height']:
armature.data.edit_bones[preserve_bone].select = True
armature.data.edit_bones[preserve_bone].select_head = True
armature.data.edit_bones[preserve_bone].select_tail = True
bpy.ops.armature.select_all(action='INVERT')
bpy.ops.armature.delete()
c.print_timer('delete_non_height_bones')
def delete_accessory_bones(self):
'''删除不在 better_fbx_bone_tails 字典中的骨骼,只保留核心人体骨骼'''
armature = c.get_armature()
c.switch(armature, 'edit')
# 定义必须保留的骨骼(即使不在 better_fbx_bone_tails 中)
# 注意:在 C 模式下,不保留 IK 控制骨骼cf_pv_*
must_keep_bones = [
'cf_hit_head', # 头部碰撞
'p_cf_body_bone', # 身体骨骼
'p_cf_head_bone', # 头部骨骼
'a_n_back', # 背部锚点
'a_n_headside', # 头部侧面锚点
'cf_J_hitomi_tx_R', # 右眼瞳控制
'cf_J_hitomi_tx_L', # 左眼瞳控制
]
# 收集所有需要删除的骨骼(不在 better_fbx_bone_tails 中且不在必须保留列表中的)
bones_to_delete = []
for bone in armature.data.edit_bones:
bone_name = bone.name
# 如果骨骼不在 better_fbx_bone_tails 字典中,且不在必须保留列表中,标记为删除
if bone_name not in better_fbx_bone_tails and bone_name not in must_keep_bones:
bones_to_delete.append(bone_name)
# 删除标记的骨骼
deleted_count = 0
for bone_name in bones_to_delete:
if bone_name in armature.data.edit_bones:
armature.data.edit_bones.remove(armature.data.edit_bones[bone_name])
deleted_count += 1
print(f"[KKBP] 已删除 {deleted_count} 个配饰骨骼(包括 IK 控制骨骼)")
print(f"[KKBP] 保留了 {len(better_fbx_bone_tails)} 个核心骨骼 + {len(must_keep_bones)} 个必需骨骼")
c.switch(armature, 'object')
def modify_finger_bone_orientations(self):
'''Reorient the finger bones to match the in game koikatsu armature'''
armature = c.get_armature()
c.switch(armature, 'edit')
height_adjust = Vector((0,0,0.1))
#all finger bones need to be rotated a specific direction
def rotate_thumb(bone):
bpy.ops.armature.select_all(action='DESELECT')
armature.data.edit_bones[bone].select = True
armature.data.edit_bones[bone].select_head = True
armature.data.edit_bones[bone].select_tail = True
parent = armature.data.edit_bones[bone].parent
armature.data.edit_bones[bone].parent = None
#right thumbs face towards hand center
#left thumbs face away from hand center
angle = -math.pi/2
s = math.sin(angle)
c = math.cos(angle)
# translate point to origin:
armature.data.edit_bones[bone].tail.x -= armature.data.edit_bones[bone].head.x
armature.data.edit_bones[bone].tail.y -= armature.data.edit_bones[bone].head.y
# rotate point around origin
xnew = armature.data.edit_bones[bone].tail.x * c - armature.data.edit_bones[bone].tail.y * s
ynew = armature.data.edit_bones[bone].tail.x * s + armature.data.edit_bones[bone].tail.y * c
# translate point back to original position:
armature.data.edit_bones[bone].tail.x = xnew + armature.data.edit_bones[bone].head.x
armature.data.edit_bones[bone].tail.y = ynew + armature.data.edit_bones[bone].head.y
armature.data.edit_bones[bone].roll = 0
armature.data.edit_bones[bone].parent = parent
rotate_thumb('cf_j_thumb03_L')
rotate_thumb('cf_j_thumb02_L')
rotate_thumb('cf_j_thumb01_L')
rotate_thumb('cf_j_thumb03_R')
rotate_thumb('cf_j_thumb02_R')
rotate_thumb('cf_j_thumb01_R')
height_adjust = Vector((0,0,0.05))
def flip_finger(bone):
parent = armature.data.edit_bones[bone].parent
armature.data.edit_bones[bone].parent = None
armature.data.edit_bones[bone].tail = armature.data.edit_bones[bone].head - height_adjust
armature.data.edit_bones[bone].parent = parent
finger_list = (
'cf_j_index03_R', 'cf_j_index02_R', 'cf_j_index01_R',
'cf_j_middle03_R', 'cf_j_middle02_R', 'cf_j_middle01_R',
'cf_j_ring03_R', 'cf_j_ring02_R', 'cf_j_ring01_R',
'cf_j_little03_R', 'cf_j_little02_R', 'cf_j_little01_R'
)
for finger in finger_list:
flip_finger(finger)
height_adjust = Vector((0,0,0.05))
def resize_finger(bone):
parent = armature.data.edit_bones[bone].parent
armature.data.edit_bones[bone].parent = None
armature.data.edit_bones[bone].tail = armature.data.edit_bones[bone].head + height_adjust
armature.data.edit_bones[bone].parent = parent
finger_list = (
'cf_j_index03_L', 'cf_j_index02_L', 'cf_j_index01_L',
'cf_j_middle03_L', 'cf_j_middle02_L', 'cf_j_middle01_L',
'cf_j_ring03_L', 'cf_j_ring02_L', 'cf_j_ring01_L',
'cf_j_little03_L', 'cf_j_little02_L', 'cf_j_little01_L'
)
for finger in finger_list:
resize_finger(finger)
# 对于非 Koikatsu armature (A/B/D),使用原始的骨骼重定向逻辑
# Koikatsu armature (C) 会在最后统一应用 better_fbx 骨骼方向
if bpy.context.scene.kkbp.armature_dropdown != 'C':
height_adjust = Vector((0,0,0.1))
def reorient(bone):
armature.data.edit_bones[bone].tail = armature.data.edit_bones[bone].head + height_adjust
reorient_list = [
'cf_j_thigh00_R', 'cf_j_thigh00_L',
'cf_j_leg01_R', 'cf_j_leg01_L',
'cf_j_leg03_R', 'cf_j_leg03_L',
'cf_j_foot_R', 'cf_j_foot_L',
'cf_d_arm01_R', 'cf_d_arm01_L',
'cf_d_shoulder02_R', 'cf_d_shoulder02_L',]
for bone in reorient_list:
reorient(bone)
c.print_timer('modify_finger_bone_orientations')
def set_bone_roll_data(self):
'''Use roll data from a reference armature dump to set the roll for each bone'''
reroll_data = {
'BodyTop':0.0,
'p_cf_body_bone':0.0,
'cf_j_root':0.0,
'cf_n_height':0.0,
'cf_j_hips':0.0,
'cf_j_spine01':0.0,
'cf_j_spine02':0.0,
'cf_j_spine03':0.0,
'cf_d_backsk_00':0.0,
'cf_j_backsk_C_01':-1.810556946517264e-23,
'cf_j_backsk_C_02':-1.1667208633608472e-15,
'cf_j_backsk_L_01':-0.001851903973147273,
'cf_j_backsk_L_02':-0.0034122250508517027,
'cf_j_backsk_R_01':0.0018519698642194271,
'cf_j_backsk_R_02':0.003412271151319146,
'cf_d_bust00':0.0,
'cf_s_bust00_L':0.0,
'cf_d_bust01_L':0.4159948229789734,
'cf_j_bust01_L':0.4159948229789734,
'cf_d_bust02_L':0.4159948229789734,
'cf_j_bust02_L':0.4151105582714081,
'cf_d_bust03_L':0.4151104986667633,
'cf_j_bust03_L':0.4151104986667633,
'cf_d_bnip01_L':0.4151104986667633,
'cf_j_bnip02root_L':0.4151104986667633,
'cf_s_bnip02_L':0.4151104986667633,
'cf_j_bnip02_L':0.4154767096042633,
'cf_s_bnip025_L':0.4154742360115051,
'cf_s_bnip01_L':0.41547420620918274,
'cf_s_bnip015_L':0.41547420620918274,
'cf_s_bust03_L':0.4153861403465271,
'cf_s_bust02_L':0.38631150126457214,
'cf_s_bust01_L':0.4159947633743286,
'cf_s_bust00_R':0.0,
'cf_d_bust01_R':-0.4159948527812958,
'cf_j_bust01_R':-0.4159948229789734,
'cf_d_bust02_R':-0.41599488258361816,
'cf_j_bust02_R':-0.41511064767837524,
'cf_d_bust03_R':-0.41511061787605286,
'cf_j_bust03_R':-0.41511064767837524,
'cf_d_bnip01_R':-0.41511061787605286,
'cf_j_bnip02root_R':-0.41511061787605286,
'cf_s_bnip02_R':-0.41511061787605286,
'cf_j_bnip02_R':-0.41547682881355286,
'cf_s_bnip025_R':-0.4154742360115051,
'cf_s_bnip01_R':-0.4154742956161499,
'cf_s_bnip015_R':-0.4154742956161499,
'cf_s_bust03_R':-0.41538622975349426,
'cf_s_bust02_R':-0.38631150126457214,
'cf_s_bust01_R':-0.4159948229789734,
'cf_d_shoulder_L':0.0,
'cf_j_shoulder_L':0.0,
'cf_d_shoulder02_L':4.2021918488899246e-05,
'cf_s_shoulder02_L':4.202192212687805e-05,
'cf_j_arm00_L':0.0,
'cf_d_arm01_L':-0.0012054670369252563,
'cf_s_arm01_L':0.009222406893968582,
'cf_d_arm02_L':-0.0012054670369252563,
'cf_s_arm02_L':0.004008470103144646,
'cf_d_arm03_L':-0.0012054670369252563,
'cf_s_arm03_L':-0.0012097591534256935,
'cf_j_forearm01_L':0.0,
'cf_d_forearm02_L':0.0,
'cf_s_forearm02_L':0.0,
'cf_d_wrist_L':0.0,
'cf_s_wrist_L':0.0,
'cf_d_hand_L':0.0,
'cf_j_hand_L':-6.776263578034403e-21,
'cf_s_hand_L':-6.776263578034403e-21,
'cf_j_index01_L':math.radians(-11),
'cf_j_index02_L':math.radians(-5),
'cf_j_index03_L':math.radians(0),
'cf_j_little01_L':math.radians(30),
'cf_j_little02_L':math.radians(11),
'cf_j_little03_L':math.radians(30),
'cf_j_middle01_L':math.radians(3),
'cf_j_middle02_L':math.radians(3),
'cf_j_middle03_L':math.radians(3),
'cf_j_ring01_L':math.radians(15),
'cf_j_ring02_L':math.radians(7),
'cf_j_ring03_L':math.radians(15),
'cf_j_thumb01_L':math.pi,
'cf_j_thumb02_L':math.pi,
'cf_j_thumb03_L':math.pi,
'cf_s_elbo_L':0.0,
'cf_s_forearm01_L':0.0,
'cf_s_elboback_L':0.0,
'cf_d_shoulder_R':0.0,
'cf_j_shoulder_R':0.0,
'cf_d_shoulder02_R':-4.355472628958523e-05,
'cf_s_shoulder02_R':-4.355472628958523e-05,
'cf_j_arm00_R':0.0,
'cf_d_arm01_R':0.0009736516512930393,
'cf_s_arm01_R':0.0009736517095007002,
'cf_d_arm02_R':0.0009736516512930393,
'cf_s_arm02_R':-0.004238337744027376,
'cf_d_arm03_R':0.0009736516512930393,
'cf_s_arm03_R':0.0009736517095007002,
'cf_j_forearm01_R':0.0,
'cf_d_forearm02_R':2.6637668270268477e-05,
'cf_s_forearm02_R':2.6637668270268477e-05,
'cf_d_wrist_R':1.9139706637361087e-05,
'cf_s_wrist_R':1.9139704818371683e-05,
'cf_d_hand_R':1.9139704818371683e-05,
'o_brac':0.0,
'cf_j_hand_R':-6.776470373187541e-21,
'cf_s_hand_R':-6.776470373187541e-21,
'cf_j_index01_R':math.radians(-11),
'cf_j_index02_R':math.radians(-5),
'cf_j_index03_R':math.radians(0),
'cf_j_little01_R':math.radians(30),
'cf_j_little02_R':math.radians(11),
'cf_j_little03_R':math.radians(30),
'cf_j_middle01_R':math.radians(3),
'cf_j_middle02_R':math.radians(3),
'cf_j_middle03_R':math.radians(3),
'cf_j_ring01_R':math.radians(15),
'cf_j_ring02_R':math.radians(7),
'cf_j_ring03_R':math.radians(15),
'cf_j_thumb01_R':math.radians(0),
'cf_j_thumb02_R':math.radians(0),
'cf_j_thumb03_R':math.radians(0),
'cf_s_elbo_R':0.0,
'cf_s_forearm01_R':0.0,
'cf_s_elboback_R':4.203895392974451e-44,
'cf_d_spinesk_00':0.0,
'cf_j_spinesk_00':5.2774636787104646e-23,
'cf_j_spinesk_01':-0.01019450556486845,
'cf_j_spinesk_02':0.0032659571152180433,
'cf_j_spinesk_03':-0.001969193108379841,
'cf_j_spinesk_04':-0.001969192875549197,
'cf_j_spinesk_05':-0.00196919240988791,
'cf_j_neck':0.0,
'cf_j_head':0.0,
'cf_s_head':0.0,
'p_cf_head_bone':0.0,
'cf_J_N_FaceRoot':2.1210576051089447e-07,
'cf_J_FaceRoot':2.1210573208918504e-07,
'cf_J_FaceBase':2.1210573208918504e-07,
'cf_J_FaceLow_tz':2.1210573208918504e-07,
'cf_J_FaceLow_sx':2.1210573208918504e-07,
'cf_J_CheekUpBase':2.1210573208918504e-07,
'cf_J_CheekUp_s_L':2.1210573208918504e-07,
'cf_J_CheekUp_s_R':2.1210573208918504e-07,
'cf_J_Chin_Base':2.1210574630003975e-07,
'cf_J_CheekLow_s_L':2.1210573208918504e-07,
'cf_J_CheekLow_s_R':2.1210573208918504e-07,
'cf_J_Chin_s':2.1210573208918504e-07,
'cf_J_ChinTip_Base':2.1210574630003975e-07,
'cf_J_ChinLow':2.1210573208918504e-07,
'cf_J_MouthBase_ty':2.1210573208918504e-07,
'cf_J_MouthBase_rx':2.1210573208918504e-07,
'cf_J_MouthCavity':2.1210571787833032e-07,
'cf_J_MouthMove':2.1210571787833032e-07,
'cf_J_Mouth_L':2.1210573208918504e-07,
'cf_J_Mouth_R':2.1210573208918504e-07,
'cf_J_MouthLow':2.1210573208918504e-07,
'cf_J_Mouthup':2.1210573208918504e-07,
'cf_J_FaceUp_ty':2.1210573208918504e-07,
'a_n_headside':2.1210574630003975e-07,
'cf_J_EarBase_ry_L':-0.1504509449005127,
'cf_J_EarLow_L':-0.1504509449005127,
'cf_J_EarUp_L':-0.1504509449005127,
'cf_J_EarBase_ry_R':0.15762563049793243,
'cf_J_EarLow_R':0.15762564539909363,
'cf_J_EarUp_R':0.15762564539909363,
'cf_J_FaceUp_tz':2.1210574630003975e-07,
'cf_J_Eye_tz':2.1210574630003975e-07,
'cf_J_Eye_txdam_L':2.1210574630003975e-07,
'cf_J_Eye_tx_L':2.1210573208918504e-07,
'cf_J_Eye_rz_L':0.20466913282871246,
'cf_J_CheekUp2_L':0.004773593973368406,
'cf_J_Eye01_s_L':0.20466917753219604,
'cf_J_Eye02_s_L':0.20466917753219604,
'cf_J_Eye03_s_L':0.20466917753219604,
'cf_J_Eye04_s_L':0.20466917753219604,
'cf_J_Eye05_s_L':0.20466917753219604,
'cf_J_Eye06_s_L':0.20466917753219604,
'cf_J_Eye07_s_L':0.20466917753219604,
'cf_J_Eye08_s_L':0.20466917753219604,
'cf_J_hitomi_tx_L':0.18981075286865234,
'cf_J_Eye_txdam_R':2.1210574630003975e-07,
'cf_J_Eye_tx_R':2.1210576051089447e-07,
'cf_J_Eye_rz_R':-0.2046687752008438,
'cf_J_CheekUp2_R':-0.0047732163220644,
'cf_J_Eye01_s_R':-0.2046687752008438,
'cf_J_Eye02_s_R':-0.2046687752008438,
'cf_J_Eye03_s_R':-0.2046687752008438,
'cf_J_Eye04_s_R':-0.2046687752008438,
'cf_J_Eye05_s_R':-0.2046687752008438,
'cf_J_Eye06_s_R':-0.2046687752008438,
'cf_J_Eye07_s_R':-0.2046687752008438,
'cf_J_Eye08_s_R':-0.2046687752008438,
'cf_J_hitomi_tx_R':-0.2046687752008438,
'cf_J_Mayu_ty':2.1210574630003975e-07,
'cf_J_Mayumoto_L':0.3458269536495209,
'cf_J_Mayu_L':0.34616410732269287,
'cf_J_MayuMid_s_L':0.34626930952072144,
'cf_J_MayuTip_s_L':0.3462893068790436,
'cf_J_Mayumoto_R':-0.34582653641700745,
'cf_J_Mayu_R':-0.34616369009017944,
'cf_J_MayuMid_s_R':-0.3462689220905304,
'cf_J_MayuTip_s_R':-0.34628885984420776,
'cf_J_NoseBase':2.1210573208918504e-07,
'cf_J_NoseBase_rx':2.1210573208918504e-07,
'cf_J_Nose_rx':2.1210573208918504e-07,
'cf_J_Nose_tip':2.1210571787833032e-07,
'cf_J_NoseBridge_ty':2.1210573208918504e-07,
'cf_J_NoseBridge_rx':2.1210573208918504e-07,
'cf_s_neck':0.0,
'cf_s_spine03':0.0,
'a_n_back':-3.1415927410125732,
'cf_s_spine02':0.0,
'cf_s_spine01':0.0,
'cf_j_waist01':0.0,
'cf_d_sk_top':0.0,
'cf_d_sk_00_00':-3.1415927410125732,
'cf_j_sk_00_00':-3.1415927410125732,
'cf_j_sk_00_01':-3.1415927410125732,
'cf_j_sk_00_02':3.1415293216705322,
'cf_j_sk_00_03':3.1415293216705322,
'cf_j_sk_00_04':3.1415293216705322,
'cf_j_sk_00_05':3.1415293216705322,
'cf_d_sk_01_00':2.3621666431427,
'cf_j_sk_01_00':2.3621666431427,
'cf_j_sk_01_01':2.364142656326294,
'cf_j_sk_01_02':2.3684122562408447,
'cf_j_sk_01_03':2.3684122562408447,
'cf_j_sk_01_04':2.3684122562408447,
'cf_j_sk_01_05':2.3684122562408447,
'cf_d_sk_02_00':1.5806118249893188,
'cf_j_sk_02_00':1.5808137655258179,
'cf_j_sk_02_01':1.5806832313537598,
'cf_j_sk_02_02':1.5820348262786865,
'cf_j_sk_02_03':1.5820348262786865,
'cf_j_sk_02_04':1.5820348262786865,
'cf_j_sk_02_05':1.5820348262786865,
'cf_d_sk_03_00':0.7112568616867065,
'cf_j_sk_03_00':0.7112571597099304,
'cf_j_sk_03_01':0.7112568020820618,
'cf_j_sk_03_02':0.709623396396637,
'cf_j_sk_03_03':0.7096233367919922,
'cf_j_sk_03_04':0.7096233367919922,
'cf_j_sk_03_05':0.7096234560012817,
'cf_d_sk_04_00':3.4498308600352867e-17,
'cf_j_sk_04_00':0.00037256989162415266,
'cf_j_sk_04_01':0.00012998198508284986,
'cf_j_sk_04_02':0.0001299990399274975,
'cf_j_sk_04_03':0.0001299990399274975,
'cf_j_sk_04_04':0.0001299990399274975,
'cf_j_sk_04_05':0.0001299990399274975,
'cf_d_sk_05_00':-0.7112577557563782,
'cf_j_sk_05_00':-0.7112579345703125,
'cf_j_sk_05_01':-0.7112577557563782,
'cf_j_sk_05_02':-0.7096185088157654,
'cf_j_sk_05_03':-0.7096185088157654,
'cf_j_sk_05_04':-0.7096185088157654,
'cf_j_sk_05_05':-0.7096185088157654,
'cf_d_sk_06_00':-1.5806118249893188,
'cf_j_sk_06_00':-1.5808138847351074,
'cf_j_sk_06_01':-1.5806833505630493,
'cf_j_sk_06_02':-1.5820401906967163,
'cf_j_sk_06_03':-1.5820401906967163,
'cf_j_sk_06_04':-1.5820401906967163,
'cf_j_sk_06_05':-1.5820401906967163,
'cf_d_sk_07_00':-2.3621666431427,
'cf_j_sk_07_00':-2.3621666431427,
'cf_j_sk_07_01':-2.3649401664733887,
'cf_j_sk_07_02':-2.3683762550354004,
'cf_j_sk_07_03':-2.3683762550354004,
'cf_j_sk_07_04':-2.3683762550354004,
'cf_j_sk_07_05':-2.3683762550354004,
'cf_j_waist02':0.0,
'cf_d_siri_L':4.435180380824022e-05,
'cf_d_siri01_L':4.484502278501168e-05,
'cf_j_siri_L':4.435180744621903e-05,
'cf_s_siri_L':4.607543087331578e-05,
'cf_d_ana':4.053832753925235e-07,
'cf_j_ana':4.053832753925235e-07,
'cf_s_ana':4.0538333223594236e-07,
'cf_d_kokan':0.0,
'cf_j_kokan':7.531064056820469e-07,
'cf_d_siri_R':-5.766015220842746e-08,
'cf_d_siri01_R':-5.766015220842746e-08,
'cf_j_siri_R':-5.766015220842746e-08,
'cf_s_siri_R':-5.766015576114114e-08,
'cf_j_thigh00_L':0.0,
'cf_d_thigh01_L':0.0,
'cf_s_thigh01_L':0.0,
'cf_d_thigh02_L':0.0,
'cf_s_thigh02_L':0.0,
'cf_d_thigh03_L':0.0,
'cf_s_thigh03_L':0.0,
'cf_j_leg01_L':0.0,
'cf_d_kneeF_L':0.0,
'cf_d_leg02_L':-8.435114585980674e-12,
'cf_s_leg02_L':0.0019489085534587502,
'cf_d_leg03_L':2.6021072699222714e-05,
'cf_s_leg03_L':2.6021054509328678e-05,
'cf_j_leg03_L':-1.7005811689396744e-11,
'cf_j_foot_L':-1.6870217028897017e-11,
'cf_j_toes_L':-1.6870217028897017e-11,
'cf_s_leg01_L':1.5783663344534925e-25,
'cf_s_kneeB_L':1.5783659646749431e-25,
'cf_j_thigh00_R':0.0,
'cf_d_thigh01_R':-2.9915531455925155e-27,
'cf_s_thigh01_R':-2.3654518046693106e-22,
'cf_d_thigh02_R':0.0,
'cf_s_thigh02_R':0.0,
'cf_d_thigh03_R':0.0,
'cf_s_thigh03_R':0.0,
'cf_j_leg01_R':0.0,
'cf_d_kneeF_R':0.0,
'cf_d_leg02_R':-3.092561655648751e-07,
'cf_s_leg02_R':-3.092561655648751e-07,
'cf_d_leg03_R':-4.125216790384911e-08,
'cf_s_leg03_R':-4.125216790384911e-08,
'cf_j_leg03_R':-6.69778160045098e-07,
'cf_j_foot_R':-6.185123311297502e-07,
'cf_j_toes_R':-6.185122742863314e-07,
'cf_s_leg01_R':-8.901179133911008e-16,
'cf_s_kneeB_R':-8.901180192702192e-16,
'cf_s_waist02':0.0,
'cf_s_leg_L':-0.004678195342421532,
'cf_s_leg_R':0.004779986571520567,
'cf_s_waist01':0.0,
}
armature = c.get_armature()
c.switch(armature, 'edit')
for bone in reroll_data:
if armature.data.edit_bones.get(bone):
armature.data.edit_bones[bone].roll = reroll_data[bone]
c.print_timer('set_bone_roll_data')
def bend_bones_for_iks(self):
'''slightly modify the armature to support IKs'''
if not bpy.context.scene.kkbp.armature_dropdown in ['A', 'B']:
return
armature = c.get_armature()
c.switch(armature, 'edit')
armature.data.edit_bones['cf_n_height'].parent = None
armature.data.edit_bones['cf_j_root'].parent = armature.data.edit_bones['cf_pv_root']
armature.data.edit_bones['p_cf_body_bone'].parent = armature.data.edit_bones['cf_pv_root']
#relocate the tail of some bones to make IKs easier
def relocate_tail(bone1, bone2, direction):
if direction == 'leg':
armature.data.edit_bones[bone1].tail.z = armature.data.edit_bones[bone2].head.z
armature.data.edit_bones[bone1].roll = 0
#move the bone forward a bit or the ik bones might not bend correctly
armature.data.edit_bones[bone1].head.y += -0.01
elif direction == 'arm':
armature.data.edit_bones[bone1].tail.x = armature.data.edit_bones[bone2].head.x
armature.data.edit_bones[bone1].tail.z = armature.data.edit_bones[bone2].head.z
armature.data.edit_bones[bone1].roll = -math.pi/2
elif direction == 'hand':
armature.data.edit_bones[bone1].tail = armature.data.edit_bones[bone2].tail
#make hand bone shorter so you can easily click the hand and the pv bone
armature.data.edit_bones[bone1].tail.z += .01
armature.data.edit_bones[bone1].head = armature.data.edit_bones[bone2].head
else:
armature.data.edit_bones[bone1].tail.y = armature.data.edit_bones[bone2].head.y
armature.data.edit_bones[bone1].tail.z = armature.data.edit_bones[bone2].head.z
armature.data.edit_bones[bone1].roll = 0
relocate_tail('cf_j_leg01_R', 'cf_j_foot_R', 'leg')
relocate_tail('cf_j_leg01_L', 'cf_j_foot_L', 'leg')
relocate_tail('cf_j_forearm01_R', 'cf_j_hand_R', 'arm')
relocate_tail('cf_j_forearm01_L', 'cf_j_hand_L', 'arm')
relocate_tail('cf_pv_hand_R', 'cf_j_hand_R', 'hand')
relocate_tail('cf_pv_hand_L', 'cf_j_hand_L', 'hand')
relocate_tail('cf_j_foot_R', 'cf_j_toes_R', 'foot')
relocate_tail('cf_j_foot_L', 'cf_j_toes_L', 'foot')
c.print_timer('bend_bones_for_iks')
def remove_empty_vertex_groups(self):
'''check body for groups with no vertexes. Delete if the group is not a bone on the armature'''
body = c.get_body()
vertexWeightMap = self.survey_vertexes(body)
bones_in_armature = [bone.name for bone in c.get_armature().data.bones]
for group in vertexWeightMap:
if group not in bones_in_armature and vertexWeightMap[group] == False and 'cf_J_Vagina' not in group:
body.vertex_groups.remove(body.vertex_groups[group])
c.print_timer('remove_empty_vertex_groups')
def reorganize_armature_layers(self):
'''Moves all bones to different armature layers'''
armature = c.get_armature()
if bpy.app.version[0] == 3:
c.switch(armature, 'pose')
else:
c.switch(armature, 'object')
core_list = self.get_bone_list('core_list')
non_ik = self.get_bone_list('non_ik')
toe_list = self.get_bone_list('toe_list')
bp_list = self.get_bone_list('bp_list')
eye_list = self.get_bone_list('eye_list')
mouth_list = self.get_bone_list('mouth_list')
skirt_list = self.get_bone_list('skirt_list')
tongue_list = self.get_bone_list('tongue_list')
#throw all bones to armature layer 11
for bone in bpy.data.armatures[0].bones:
self.set_armature_layer(bone.name, show_layer = 10)
#reshow cf_hit_ bones on layer 12
for bone in [bones for bones in bpy.data.armatures[0].bones if 'cf_hit_' in bones.name]:
self.set_armature_layer(bone.name, show_layer = 11)
#reshow k_f_ bones on layer 13
for bone in [bones for bones in bpy.data.armatures[0].bones if 'k_f_' in bones.name]:
self.set_armature_layer(bone.name, show_layer = 12)
#reshow core bones on layer 1
for bone in core_list:
self.set_armature_layer(bone, show_layer = 0)
#reshow non_ik bones on layer 2
for bone in non_ik:
self.set_armature_layer(bone, show_layer = 1)
#Put the charamaker bones on layer 3
for bone in [bones for bones in bpy.data.armatures[0].bones if 'cf_s_' in bones.name]:
self.set_armature_layer(bone.name, show_layer = 2)
#Put the deform bones on layer 4
for bone in [bones for bones in bpy.data.armatures[0].bones if 'cf_d_' in bones.name]:
self.set_armature_layer(bone.name, show_layer = 3)
try:
#Put the better penetration bones on layer 5
for bone in bp_list:
self.set_armature_layer(bone, show_layer = 4)
#rename the bones so you can mirror them over the x axis in pose mode
if 'Vagina_L_' in bone or 'Vagina_R_' in bone:
bpy.data.armatures[0].bones[bone].name = 'Vagina' + bone[8:] + '_' + bone[7]
#Put the toe bones on layer 5
for bone in toe_list:
self.set_armature_layer(bone, show_layer = 4)
except:
#this armature isn't a BP armature
pass
#Put the upper eye bones on layer 17
for bone in eye_list:
self.set_armature_layer(bone, show_layer = 16)
#Put the lower mouth bones on layer 18
for bone in mouth_list:
self.set_armature_layer(bone, show_layer = 17)
#Put the tongue rig bones on layer 19
for bone in tongue_list:
self.set_armature_layer(bone, show_layer = 18)
#Put the skirt bones on layer 9
for bone in skirt_list:
self.set_armature_layer(bone, show_layer = 8)
#put accessory bones on layer 10 during reshow_accessory_bones() later on
#Make all bone layers visible for now
all_layers = [
True, True, True, True, True, False, False, False, #body
True, True, True, False, False, False, False, False, #clothes
True, True, False, False, False, False, False, False, #face
False, False, False, False, False, False, False, False]
if bpy.app.version[0] == 3:
bpy.ops.armature.armature_layers(layers=all_layers)
else:
for index, show_layer in enumerate(all_layers):
if armature.data.collections.get(str(index)):
armature.data.collections.get(str(index)).is_visible = show_layer
armature.data.display_type = 'STICK'
c.switch(armature, 'object')
c.print_timer('reorganize_armature_layers')
def move_accessory_bones_to_layer10(self):
'''Moves the accessory bones that have weight to them to armature layer 10'''
armature = c.get_armature()
c.switch(armature, 'object')
#go through each outfit and move ALL accessory bones to layer 10
dont_move_these = [
'cf_pv', 'Eyesx',
'cf_J_hitomi_tx_', 'cf_J_FaceRoot', 'cf_J_FaceUp_t',
'n_cam', 'EyesLookTar', 'N_move', 'a_n_', 'cf_hit',
'cf_j_bnip02', 'cf_j_kokan', 'cf_j_ana']
outfits = c.get_outfits()
outfits.extend(c.get_alts())
outfits.extend(c.get_hairs())
for outfit_or_hair in outfits:
# Find empty vertex groups
vertexWeightMap = self.survey_vertexes(outfit_or_hair)
#add outfit id to all accessory bones used by that outfit in an array
if bpy.app.version[0] == 3:
for bone in [bone for bone in armature.data.bones if bone.layers[10]]:
no_move_bone = False
for this_prefix in dont_move_these:
if this_prefix in bone.name:
no_move_bone = True
if not no_move_bone and vertexWeightMap.get(bone.name):
try:
outfit_id_array = bone['id'].to_list()
outfit_id_array.append(outfit_or_hair['id'])
bone['id'] = outfit_id_array
except:
bone['id'] = [outfit_or_hair['id']]
else:
for bone in [bone for bone in armature.data.bones if bone.collections.get('10')]:
no_move_bone = False
for this_prefix in dont_move_these:
if this_prefix in bone.name:
no_move_bone = True
if not no_move_bone and vertexWeightMap.get(bone.name):
try:
outfit_id_array = bone['id'].to_list()
outfit_id_array.append(outfit_or_hair['id'])
bone['id'] = outfit_id_array
except:
bone['id'] = [outfit_or_hair['id']]
#move accessory bones to armature layer 10
for bone in [bone for bone in armature.data.bones if bone.get('id')]:
self.set_armature_layer(bone.name, show_layer = 9)
c.print_timer('move_accessory_bones_to_layer10')
def rename_mmd_bones(self):
'''renames japanese name field for importing vmds via mmd tools
these names are separate from Blender's bone names'''
pmx_rename_dict = {
'全ての親':'p_cf_body_bone',
'センター':'cf_j_hips',
'上半身':'cf_j_spine01',
'上半身2':'cf_j_spine02',
'':'cf_j_neck',
'':'cf_j_head',
'両目':'Eyesx',
'左目':'cf_J_hitomi_tx_L',
'右目':'cf_J_hitomi_tx_R',
'左腕':'cf_j_arm00_L',
'右腕':'cf_j_arm00_R',
'左ひじ':'cf_j_forearm01_L',
'右ひじ':'cf_j_forearm01_R',
'左肩':'cf_j_shoulder_L',
'右肩':'cf_j_shoulder_R',
'左手首':'cf_j_hand_L',
'右手首':'cf_j_hand_R',
'左親指0':'cf_j_thumb01_L',
'左親指1':'cf_j_thumb02_L',
'左親指2':'cf_j_thumb03_L',
'左薬指1':'cf_j_ring01_L',
'左薬指2':'cf_j_ring02_L',
'左薬指3':'cf_j_ring03_L',
'左中指1':'cf_j_middle01_L',
'左中指2':'cf_j_middle02_L',
'左中指3':'cf_j_middle03_L',
'左小指1':'cf_j_little01_L',
'左小指2':'cf_j_little02_L',
'左小指3':'cf_j_little03_L',
'左人指1':'cf_j_index01_L',
'左人指2':'cf_j_index02_L',
'左人指3':'cf_j_index03_L',
'右親指0':'cf_j_thumb01_R',
'右親指1':'cf_j_thumb02_R',
'右親指2':'cf_j_thumb03_R',
'右薬指1':'cf_j_ring01_R',
'右薬指2':'cf_j_ring02_R',
'右薬指3':'cf_j_ring03_R',
'右中指1':'cf_j_middle01_R',
'右中指2':'cf_j_middle02_R',
'右中指3':'cf_j_middle03_R',
'右小指1':'cf_j_little01_R',
'右小指2':'cf_j_little02_R',
'右小指3':'cf_j_little03_R',
'右人指1':'cf_j_index01_R',
'右人指2':'cf_j_index02_R',
'右人指3':'cf_j_index03_R',
'下半身':'cf_j_waist01',
'左足':'cf_j_thigh00_L',
'右足':'cf_j_thigh00_R',
'左ひざ':'cf_j_leg01_L',
'右ひざ':'cf_j_leg01_R',
'左足首':'cf_j_leg03_L',
'右足首':'cf_j_leg03_R',
}
armature = c.get_armature()
for bone in pmx_rename_dict:
if armature.pose.bones.get(pmx_rename_dict[bone]):
armature.pose.bones[pmx_rename_dict[bone]].mmd_bone.name_j = bone
c.print_timer('rename_mmd_bones')
def visually_connect_bones(self):
'''make sure certain bones are visually connected'''
if not bpy.context.scene.kkbp.armature_dropdown in ['A', 'B']:
return
armature = c.get_armature()
c.switch(armature, 'edit')
# Make sure all toe bones are visually correct if using the better penetration armature
try:
armature.data.edit_bones['Toes4_L'].tail.y = armature.data.edit_bones['Toes30_L'].head.y
armature.data.edit_bones['Toes4_L'].tail.z = armature.data.edit_bones['Toes30_L'].head.z*.8
armature.data.edit_bones['Toes0_L'].tail.y = armature.data.edit_bones['Toes10_L'].head.y
armature.data.edit_bones['Toes0_L'].tail.z = armature.data.edit_bones['Toes30_L'].head.z*.9
armature.data.edit_bones['Toes30_L'].tail.z = armature.data.edit_bones['Toes30_L'].head.z*0.8
armature.data.edit_bones['Toes30_L'].tail.y = armature.data.edit_bones['Toes30_L'].head.y*1.2
armature.data.edit_bones['Toes20_L'].tail.z = armature.data.edit_bones['Toes20_L'].head.z*0.8
armature.data.edit_bones['Toes20_L'].tail.y = armature.data.edit_bones['Toes20_L'].head.y*1.2
armature.data.edit_bones['Toes10_L'].tail.z = armature.data.edit_bones['Toes10_L'].head.z*0.8
armature.data.edit_bones['Toes10_L'].tail.y = armature.data.edit_bones['Toes10_L'].head.y*1.2
armature.data.edit_bones['Toes4_R'].tail.y = armature.data.edit_bones['Toes30_R'].head.y
armature.data.edit_bones['Toes4_R'].tail.z = armature.data.edit_bones['Toes30_R'].head.z*.8
armature.data.edit_bones['Toes0_R'].tail.y = armature.data.edit_bones['Toes10_R'].head.y
armature.data.edit_bones['Toes0_R'].tail.z = armature.data.edit_bones['Toes30_R'].head.z*.9
armature.data.edit_bones['Toes30_R'].tail.z = armature.data.edit_bones['Toes30_R'].head.z*0.8
armature.data.edit_bones['Toes30_R'].tail.y = armature.data.edit_bones['Toes30_R'].head.y*1.2
armature.data.edit_bones['Toes20_R'].tail.z = armature.data.edit_bones['Toes20_R'].head.z*0.8
armature.data.edit_bones['Toes20_R'].tail.y = armature.data.edit_bones['Toes20_R'].head.y*1.2
armature.data.edit_bones['Toes10_R'].tail.z = armature.data.edit_bones['Toes10_R'].head.z*0.8
armature.data.edit_bones['Toes10_R'].tail.y = armature.data.edit_bones['Toes10_R'].head.y*1.2
except:
#this character isn't using the BP/toe control armature
c.kklog('No toe bones detected. Skipping...', type = 'warn')
pass
c.switch(armature, 'object')
c.print_timer('visually_connect_bones')
def shorten_kokan_bone(self):
'''make the kokan bone shorter if it's on the armature'''
if not bpy.context.scene.kkbp.armature_dropdown in ['A', 'B']:
return
armature = c.get_armature()
c.switch(armature, 'edit')
if armature.data.edit_bones.get('cf_j_kokan'):
armature.data.edit_bones['cf_j_kokan'].tail.z = armature.data.edit_bones['cf_s_waist02'].head.z
c.print_timer('shorten_kokan_bone')
def scale_skirt_and_face_bones(self):
'''scales skirt bones and face bones down. Scales BP bones down if exists'''
#skip this operation if this is the pmx or koikatsu armature
if not bpy.context.scene.kkbp.armature_dropdown in ['A', 'B']:
return
armature = c.get_armature()
c.switch(armature, 'pose')
def shorten_bone(bone, scale):
c.switch(armature, 'edit')
armature.data.edit_bones[bone].select_head = True
armature.data.edit_bones[bone].select_tail = True
previous_roll = armature.data.edit_bones[bone].roll + 1 #roll doesn't save if you don't add a number at the end
armature.data.edit_bones[bone].tail = (armature.data.edit_bones[bone].tail+armature.data.edit_bones[bone].head)/2
armature.data.edit_bones[bone].tail = (armature.data.edit_bones[bone].tail+armature.data.edit_bones[bone].head)/2
armature.data.edit_bones[bone].tail = (armature.data.edit_bones[bone].tail+armature.data.edit_bones[bone].head)/2
armature.data.edit_bones[bone].select_head = False
armature.data.edit_bones[bone].select_tail = False
armature.data.edit_bones[bone].roll = previous_roll - 1 #subtract the number at the end to save roll
c.switch(armature, 'pose')
def connect_bone(root, chain):
bone = 'cf_j_sk_0'+str(root)+'_0'+str(chain)
child_bone = 'cf_j_sk_0'+str(root)+'_0'+str(chain+1)
#first connect tail to child bone to keep head in place during connection
c.switch(armature, 'edit')
if armature.data.edit_bones.get(bone) and armature.data.edit_bones.get(child_bone) and chain <= 4:
armature.data.edit_bones[bone].tail = armature.data.edit_bones[child_bone].head
#then connect child head to parent tail (both are at the same position, so head doesn't move)
armature.data.edit_bones[child_bone].use_connect = True
skirtchain = [0,1,2,3,4,5,6,7]
skirtchild = [0,1,2,3,4]
try:
for root in skirtchain:
for chain in skirtchild:
connect_bone(root, chain)
except:
c.kklog('No skirt bones detected. Skipping...', type = 'warn')
#scale eye bones, mouth bones, eyebrow bones
c.switch(armature, 'pose')
eyebones = [1,2,3,4,5,6,7,8]
for piece in eyebones:
bpy.ops.pose.select_all(action='DESELECT')
left = 'cf_J_Eye0'+str(piece)+'_s_L'
right = 'cf_J_Eye0'+str(piece)+'_s_R'
shorten_bone(left, 0.1)
shorten_bone(right, 0.1)
restOfFace = [
'cf_J_Mayu_R', 'cf_J_MayuMid_s_R', 'cf_J_MayuTip_s_R',
'cf_J_Mayu_L', 'cf_J_MayuMid_s_L', 'cf_J_MayuTip_s_L',
'cf_J_Mouth_R', 'cf_J_Mouth_L',
'cf_J_Mouthup', 'cf_J_MouthLow', 'cf_J_MouthMove', 'cf_J_MouthCavity']
for bone in restOfFace:
bpy.ops.pose.select_all(action='DESELECT')
shorten_bone(bone, 0.1)
#move eye bone location
c.switch(armature, 'edit')
for eyebone in ['Eyesx', 'Eye Controller']:
armature.data.edit_bones[eyebone].head.y = armature.data.edit_bones['cf_d_bust02_R'].tail.y
armature.data.edit_bones[eyebone].tail.y = armature.data.edit_bones['cf_d_bust02_R'].tail.y*1.5
armature.data.edit_bones[eyebone].tail.z = armature.data.edit_bones['cf_J_Nose_tip'].tail.z
armature.data.edit_bones[eyebone].head.z = armature.data.edit_bones['cf_J_Nose_tip'].tail.z
#scale BP bones if they exist
BPList = ['cf_j_kokan', 'cf_j_ana', 'Vagina_Root', 'Vagina_B', 'Vagina_F', 'Vagina_001_L', 'Vagina_002_L', 'Vagina_003_L', 'Vagina_004_L', 'Vagina_005_L', 'Vagina_001_R', 'Vagina_002_R', 'Vagina_003_R', 'Vagina_004_R', 'Vagina_005_R']
for bone in BPList:
if armature.data.edit_bones.get(bone):
armature.data.edit_bones[bone].tail.z = armature.data.edit_bones[bone].tail.z*.95
c.print_timer('scale_skirt_and_face_bones')
def create_eye_reference_bone(self):
'''Create a bone called "Eyesx that will act as a fixed reference bone for the Eye controller" '''
if not bpy.context.scene.kkbp.armature_dropdown in ['A', 'B']:
return
armature = c.get_armature()
c.switch(armature, 'edit')
new_bone = armature.data.edit_bones.new('Eyesx')
new_bone.head = armature.data.edit_bones['cf_hit_head'].tail
new_bone.head.y = new_bone.head.y + 0.05
new_bone.tail = armature.data.edit_bones['cf_J_Mayu_R'].tail
new_bone.tail.x = new_bone.head.x
new_bone.tail.y = new_bone.head.y
new_bone.parent = armature.data.edit_bones['cf_j_head']
c.print_timer('create_eye_reference_bone')
def create_eye_controller_bone(self):
if not bpy.context.scene.kkbp.armature_dropdown in ['A', 'B']:
return
armature = c.get_armature()
c.switch(armature, 'edit')
#roll the eye bone based on armature, create a copy and name it eye controller
armature_data = armature.data
armature_data.edit_bones['Eyesx'].roll = -math.pi/2
copy = self.new_bone('Eye Controller')
copy.head = armature_data.edit_bones['Eyesx'].head/2
copy.tail = armature_data.edit_bones['Eyesx'].tail/2
copy.matrix = armature_data.edit_bones['Eyesx'].matrix
copy.parent = armature_data.edit_bones['cf_j_head']
armature_data.edit_bones['Eye Controller'].roll = -math.pi/2
c.switch(armature, 'pose')
#Lock y location at zero
armature.pose.bones['Eye Controller'].lock_location[1] = True
#Hide the original Eyesx bone
armature.data.bones['Eyesx'].hide = True
self.set_armature_layer('Eye Controller', 0)
c.switch(armature, 'object')
#Create a UV warp modifier for the eyes. Controlled by the Eye controller bone
def eyeUV(modifiername, eyevertexgroup):
mod = c.get_body().modifiers.new(modifiername, 'UV_WARP')
mod.axis_u = 'Z'
mod.axis_v = 'X'
mod.object_from = armature
mod.bone_from = armature.data.bones['Eyesx'].name
mod.object_to = armature
mod.bone_to = armature.data.bones['Eye Controller'].name
mod.vertex_group = eyevertexgroup
mod.uv_layer = 'UVMap'
mod.show_expanded = False
eyeUV("Left Eye UV warp", 'Left Eye')
eyeUV("Right Eye UV warp", 'Right Eye')
c.print_timer('create_eye_controller_bone')
def prepare_ik_bones(self):
'''reparents some bones to work for IK'''
if not bpy.context.scene.kkbp.armature_dropdown in ['A','B']:
return
#Select the armature and make it active
armature = c.get_armature()
c.switch(armature, 'edit')
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
#separate the PV bones, so the elbow IKs rotate with the spine
pvrootupper = self.new_bone('cf_pv_root_upper')
pvrootupper.tail = armature.data.edit_bones['cf_pv_root'].tail
pvrootupper.head = armature.data.edit_bones['cf_pv_root'].head
#reparent things
def reparent(bone,newparent):
#refresh armature by going to object mode then back to edit mode?
c.switch(armature, 'edit')
armature.data.edit_bones[bone].parent = armature.data.edit_bones[newparent]
reparent('cf_pv_root_upper', 'cf_j_spine01')
reparent('cf_pv_elbo_R', 'cf_pv_root_upper')
reparent('cf_pv_elbo_L', 'cf_pv_root_upper')
c.print_timer('prepare_ik_bones')
def create_ik_bones(self):
'''give the leg a foot IK, the foot a heel controller, and the arm a hand IK'''
if not bpy.context.scene.kkbp.armature_dropdown in ['A','B']:
return
def legIK(legbone, IKtarget, IKpole, IKpoleangle, footIK, kneebone, toebone, footbone):
bone = c.get_armature().pose.bones[legbone]
#Make IK
ik = bone.constraints.new("IK")
ik.name = "IK"
#Set target and subtarget
bone.constraints["IK"].target = c.get_armature()
bone.constraints["IK"].subtarget = c.get_armature().data.bones[IKtarget].name
#Set pole and subpole and pole angle
bone.constraints["IK"].pole_target = c.get_armature()
bone.constraints["IK"].pole_subtarget = c.get_armature().data.bones[IKpole].name
bone.constraints["IK"].pole_angle = IKpoleangle
#Set chain length
bone.constraints["IK"].chain_count=2
#Flip foot IK to match foot bone
bone = c.get_armature().data.edit_bones[footIK]
bone = c.get_armature().data.edit_bones[footIK]
bone.head.y = c.get_armature().data.edit_bones[kneebone].tail.y
bone = c.get_armature().data.edit_bones[footIK]
bone.tail.z = c.get_armature().data.edit_bones[toebone].head.z
bone = c.get_armature().data.edit_bones[footIK]
bone.head.z = c.get_armature().data.edit_bones[footbone].head.z
bone = c.get_armature().data.edit_bones[footIK]
bone.head.x = c.get_armature().data.edit_bones[kneebone].tail.x
bone = c.get_armature().data.edit_bones[footIK]
bone.tail.x = bone.head.x
#unparent the bone
center_bone = c.get_armature().data.edit_bones['cf_n_height']
bone = c.get_armature().data.edit_bones[footIK]
bone.parent = center_bone
#Run for each side
legIK('cf_j_leg01_R', 'cf_pv_foot_R', 'cf_pv_knee_R', math.pi/2, 'cf_pv_foot_R', 'cf_j_leg01_R', 'cf_j_toes_R', 'cf_j_foot_R')
legIK('cf_j_leg01_L', 'cf_pv_foot_L', 'cf_pv_knee_L', math.pi/2, 'cf_pv_foot_L', 'cf_j_leg01_L', 'cf_j_toes_L', 'cf_j_foot_L')
#adds an IK for the toe bone, moves the knee IKs a little closer to the body
def footIK(footbone, toebone, footIK, kneebone, legbone):
bone = c.get_armature().pose.bones[footbone]
#Make Copy rotation
bone.constraints.new("COPY_ROTATION")
#Set target and subtarget
bone.constraints[0].target=c.get_armature()
bone.constraints[0].subtarget = c.get_armature().data.bones[footIK].name
#Set the rotation to local space
bone.constraints[0].target_space = 'LOCAL_WITH_PARENT'
bone.constraints[0].owner_space = 'LOCAL_WITH_PARENT'
# move knee IKs closer to body
kneedist = round((c.get_armature().pose.bones[footbone].head - c.get_armature().pose.bones[footbone].tail).length,2)
c.get_armature().data.edit_bones[kneebone].head.y = kneedist * -5
c.get_armature().data.edit_bones[kneebone].tail.y = kneedist * -5
# make toe bone shorter
c.get_armature().data.edit_bones[toebone].tail.z = c.get_armature().data.edit_bones[legbone].head.z * 0.2
#Run for each side
footIK('cf_j_foot_R', 'cf_j_toes_R', 'cf_pv_foot_R', 'cf_pv_knee_R', 'cf_j_leg01_R')
footIK('cf_j_foot_L', 'cf_j_toes_L', 'cf_pv_foot_L', 'cf_pv_knee_L', 'cf_j_leg01_L')
#Add a heel controller to the foot
#this fucking thing keeps crashing so retreive_stored_tags is called after most operations
def heelController(footbone, footIK, toebone):
#duplicate the foot IK. This is the new master bone
c.switch(c.get_armature(), 'edit')
masterbone = self.new_bone('MasterFootIK.' + footbone[-1])
masterbone = c.get_armature().data.edit_bones['MasterFootIK.' + footbone[-1]]
masterbone.head = c.get_armature().data.edit_bones[footbone].head
masterbone = c.get_armature().data.edit_bones['MasterFootIK.' + footbone[-1]]
masterbone.tail = c.get_armature().data.edit_bones[footbone].tail
masterbone = c.get_armature().data.edit_bones['MasterFootIK.' + footbone[-1]]
masterbone.matrix = c.get_armature().data.edit_bones[footbone].matrix
masterbone = c.get_armature().data.edit_bones['MasterFootIK.' + footbone[-1]]
masterbone.parent = c.get_armature().data.edit_bones['cf_n_height']
#Create the heel controller
heelIK = self.new_bone('HeelIK.' + footbone[-1])
heelIK = c.get_armature().data.edit_bones['HeelIK.' + footbone[-1]]
heelIK.head = c.get_armature().data.edit_bones[footbone].tail
heelIK = c.get_armature().data.edit_bones['HeelIK.' + footbone[-1]]
heelIK.tail = c.get_armature().data.edit_bones[footbone].head
heelIK = c.get_armature().data.edit_bones['HeelIK.' + footbone[-1]]
heelIK.parent = masterbone
heelIK = c.get_armature().data.edit_bones['HeelIK.' + footbone[-1]]
heelIK = c.get_armature().data.edit_bones['HeelIK.' + footbone[-1]]
heelIK.tail.y *= .5
#parent footIK to heel controller
c.get_armature().data.edit_bones[footIK].parent = heelIK
#make a bone to pin the foot
footPin = self.new_bone('FootPin.' + footbone[-1])
footPin = c.get_armature().data.edit_bones['FootPin.' + footbone[-1]]
footPin.head = c.get_armature().data.edit_bones[toebone].head
footPin = c.get_armature().data.edit_bones['FootPin.' + footbone[-1]]
footPin.tail = c.get_armature().data.edit_bones[toebone].tail
footPin = c.get_armature().data.edit_bones['FootPin.' + footbone[-1]]
footPin.parent = masterbone
footPin = c.get_armature().data.edit_bones['FootPin.' + footbone[-1]]
footPin.tail.z*=.8
#make a bone to allow rotation of the toe along an arc
toeRotator = self.new_bone('ToeRotator.' + footbone[-1])
toeRotator = c.get_armature().data.edit_bones['ToeRotator.' + footbone[-1]]
toeRotator.head = c.get_armature().data.edit_bones[toebone].head
toeRotator = c.get_armature().data.edit_bones['ToeRotator.' + footbone[-1]]
toeRotator.tail = c.get_armature().data.edit_bones[toebone].tail
toeRotator = c.get_armature().data.edit_bones['ToeRotator.' + footbone[-1]]
toeRotator.parent = masterbone
#make a bone to pin the toe
toePin = self.new_bone('ToePin.' + footbone[-1])
toePin = c.get_armature().data.edit_bones['ToePin.' + footbone[-1]]
toePin.head = c.get_armature().data.edit_bones[toebone].tail
toePin = c.get_armature().data.edit_bones['ToePin.' + footbone[-1]]
toePin.tail = c.get_armature().data.edit_bones[toebone].tail
toePin = c.get_armature().data.edit_bones['ToePin.' + footbone[-1]]
toePin.parent = toeRotator
toePin = c.get_armature().data.edit_bones['ToePin.' + footbone[-1]]
toePin.tail.z *=1.2
#pin the foot
c.switch(c.get_armature(), 'pose')
bone = c.get_armature().pose.bones[footbone]
ik = bone.constraints.new("IK")
ik.name = "IK"
bone = c.get_armature().pose.bones[footbone]
bone.constraints["IK"].target = c.get_armature()
bone = c.get_armature().pose.bones[footbone]
bone.constraints["IK"].subtarget = c.get_armature().data.bones['FootPin.' + footbone[-1]].name
bone = c.get_armature().pose.bones[footbone]
bone.constraints["IK"].chain_count=1
#pin the toe
bone = c.get_armature().pose.bones[toebone]
ik = bone.constraints.new("IK")
ik.name = "IK"
bone = c.get_armature().pose.bones[toebone]
bone.constraints["IK"].target = c.get_armature()
bone = c.get_armature().pose.bones[toebone]
bone.constraints["IK"].subtarget = c.get_armature().data.bones['ToePin.' + footbone[-1]].name
bone = c.get_armature().pose.bones[toebone]
bone.constraints["IK"].chain_count=1
#move these bones to armature layer 2
bpy.ops.object.mode_set(mode='POSE') #use this instead of c.switch to prevent crashing
layer2 = (False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False)
bpy.ops.pose.select_all(action='DESELECT')
if bpy.app.version[0] == 3:
c.get_armature().data.bones['FootPin.' + footbone[-1]].select = True
c.get_armature().data.bones['ToePin.' + footbone[-1]].select = True
c.get_armature().data.bones[toebone].select = True
bpy.ops.pose.bone_layers(layers=layer2)
c.get_armature().data.bones[footIK].select = True
else:
c.get_armature().data.bones['FootPin.' + footbone[-1]].collections.clear()
self.set_armature_layer('FootPin.' + footbone[-1], 2)
c.get_armature().data.bones['ToePin.' + footbone[-1]].collections.clear()
self.set_armature_layer('ToePin.' + footbone[-1], 2)
c.get_armature().data.bones[toebone].collections.clear()
self.set_armature_layer(toebone, 2)
c.get_armature().data.bones[footIK].collections.clear()
self.set_armature_layer(footIK, 2)
heelController('cf_j_foot_L', 'cf_pv_foot_L', 'cf_j_toes_L')
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
heelController('cf_j_foot_R', 'cf_pv_foot_R', 'cf_j_toes_R')
#Give the new foot IKs an mmd bone name
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
c.get_armature().pose.bones['MasterFootIK.L'].mmd_bone.name_j = '左足IK'
c.get_armature().pose.bones['MasterFootIK.R'].mmd_bone.name_j = '右足IK'
#add an IK to the arm, makes the wrist bone copy the hand IK's rotation, moves elbow IKs a little closer to the body
def armhandIK(elbowbone, handcontroller, elbowcontroller, IKangle, wristbone):
#Set IK bone
bone = c.get_armature().pose.bones[elbowbone]
#Add IK
bone.constraints.new("IK")
#Set target and subtarget
bone.constraints["IK"].target = c.get_armature()
bone.constraints["IK"].subtarget = c.get_armature().data.bones[handcontroller].name
#Set pole and subpole and pole angle
bone.constraints["IK"].pole_target = c.get_armature()
bone.constraints["IK"].pole_subtarget = c.get_armature().data.bones[elbowcontroller].name
bone.constraints["IK"].pole_angle= IKangle
#Set chain length
bone.constraints["IK"].chain_count=2
#unparent the bone
c.switch(c.get_armature(), 'edit')
bone = c.get_armature().data.edit_bones[handcontroller]
bone.parent = c.get_armature().data.edit_bones['cf_n_height']
c.get_armature().data.bones[wristbone].hide = True
# move elbow IKs closer to body
elbowdist = round((c.get_armature().data.edit_bones[elbowbone].head - c.get_armature().data.edit_bones[elbowbone].tail).length,2)
c.get_armature().data.edit_bones[elbowcontroller].head.y = elbowdist*2
c.get_armature().data.edit_bones[elbowcontroller].tail.y = elbowdist*2
c.switch(c.get_armature(), 'pose')
# Set hand rotation then hide it
bone = c.get_armature().pose.bones[wristbone]
bone.constraints.new("COPY_ROTATION")
bone.constraints[0].target = c.get_armature()
bone.constraints[0].subtarget = c.get_armature().data.bones[handcontroller].name
#Run for each side
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
armhandIK('cf_j_forearm01_R', 'cf_pv_hand_R', 'cf_pv_elbo_R', 0, 'cf_j_hand_R')
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
armhandIK('cf_j_forearm01_L', 'cf_pv_hand_L', 'cf_pv_elbo_L', 180, 'cf_j_hand_L')
#move newly created bones to correct armature layers
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
self.set_armature_layer('MasterFootIK.L', 0)
self.set_armature_layer('MasterFootIK.R', 0)
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
self.set_armature_layer('HeelIK.L', 0)
self.set_armature_layer('HeelIK.R', 0)
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
self.set_armature_layer('ToeRotator.L', 0)
self.set_armature_layer('ToeRotator.R', 0)
self.set_armature_layer('cf_d_bust00', 0)
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
c.get_armature().data.bones['cf_pv_root_upper'].hide = True
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
c.switch(c.get_armature(), 'object')
def create_joint_drivers(self):
'''There are several joint corrections that use the cf_d_ and cf_s_ bones on the armature. This function attempts to replicate them using blender drivers and bone constraints'''
if not bpy.context.scene.kkbp.armature_dropdown in ['A','B']:
return
armature = c.get_armature()
c.switch(armature, 'pose')
#generic function to set a copy rotation modifier
def set_copy(bone, bonetarget, influence, axis = 'all', mix = 'replace', space = 'LOCAL'):
constraint = armature.pose.bones[bone].constraints.new("COPY_ROTATION")
constraint.target = armature
constraint.subtarget = bonetarget
constraint.influence = influence
constraint.target_space = space
constraint.owner_space = space
if axis == 'X':
constraint.use_y = False
constraint.use_z = False
elif axis == 'Y':
constraint.use_x = False
constraint.use_z = False
elif axis == 'antiX':
constraint.use_y = False
constraint.use_z = False
constraint.invert_x = True
elif axis == 'Z':
constraint.use_x = False
constraint.use_y = False
if mix == 'add':
constraint.mix_mode = 'ADD'
#setup most of the drivers with this
set_copy('cf_d_shoulder02_L', 'cf_j_arm00_L', 0.5)
set_copy('cf_d_arm01_L', 'cf_j_arm00_L', 0.75, axis = 'X')
set_copy('cf_d_arm02_L', 'cf_j_arm00_L', 0.5, axis = 'X')
set_copy('cf_d_arm03_L', 'cf_j_arm00_L', 0.25, axis = 'X')
set_copy('cf_d_forearm02_L', 'cf_j_hand_L', 0.33, axis = 'X')
set_copy('cf_d_wrist_L', 'cf_j_hand_L', 0.33, axis = 'X', )
set_copy('cf_d_kneeF_L', 'cf_j_leg01_L', 0.5, axis = 'antiX', mix = 'add')
set_copy('cf_d_siri_L', 'cf_j_thigh00_L', 0.33)
set_copy('cf_d_thigh02_L', 'cf_j_thigh00_L', 0.25, axis='Y')
set_copy('cf_d_thigh03_L', 'cf_j_thigh00_L', 0.25, axis='Y')
set_copy('cf_d_leg02_L', 'cf_j_leg01_L', 0.33, axis='Y')
set_copy('cf_d_leg03_L', 'cf_j_leg01_L', 0.66, axis='Y')
set_copy('cf_d_shoulder02_R', 'cf_j_arm00_R', 0.5)
set_copy('cf_d_arm01_R', 'cf_j_arm00_R', 0.75, axis = 'X')
set_copy('cf_d_arm02_R', 'cf_j_arm00_R', 0.5, axis = 'X')
set_copy('cf_d_arm03_R', 'cf_j_arm00_R', 0.25, axis = 'X')
set_copy('cf_d_forearm02_R', 'cf_j_hand_R', 0.33, axis = 'X')
set_copy('cf_d_wrist_R', 'cf_j_hand_R', 0.33, axis = 'X')
set_copy('cf_d_kneeF_R', 'cf_j_leg01_R', 0.5, axis = 'antiX', mix = 'add')
set_copy('cf_d_siri_R', 'cf_j_thigh00_R', 0.33)
set_copy('cf_d_thigh02_R', 'cf_j_thigh00_R', 0.25, axis='Y')
set_copy('cf_d_thigh03_R', 'cf_j_thigh00_R', 0.25, axis='Y')
set_copy('cf_d_leg02_R', 'cf_j_leg01_R', 0.33, axis='Y')
set_copy('cf_d_leg03_R', 'cf_j_leg01_R', 0.66, axis='Y')
#move the waist some if only one leg is rotated
set_copy('cf_s_waist02', 'cf_j_thigh00_L', 0.1, mix = 'add')
set_copy('cf_s_waist02', 'cf_j_thigh00_R', 0.1, mix = 'add')
#set_copy('cf_s_waist02', 'cf_j_thigh00_R', 0.1, mix = 'add')
#set_copy('cf_s_waist02', 'cf_j_thigh00_L', 0.1, mix = 'add')
set_copy('cf_s_waist02', 'cf_j_waist02', 0.5, axis = 'antiX')
#this rotation helps when doing a split
set_copy('cf_s_leg_L', 'cf_j_thigh00_L', .9, axis = 'Z', mix = 'add')
set_copy('cf_s_leg_R', 'cf_j_thigh00_R', .9, axis = 'Z', mix = 'add')
#generic function for creating a driver
def setDriver (bone, drivertype, drivertypeselect, drivertarget, drivertt, drivermult, expresstype = 'move'):
#add driver to first component
#drivertype is the kind of driver you want to be applied to the bone and can be location/rotation
#drivertypeselect is the component of the bone you want the driver to be applied to
# for location it's (0 is x component, y is 1, z is 2)
# for rotation it's (0 is w, 1 is x, etc)
# for scale it's (0 is x, 1 is y, 2 is z)
driver = armature.pose.bones[bone].driver_add(drivertype, drivertypeselect)
#add driver variable
vari = driver.driver.variables.new()
vari.name = 'var'
vari.type = 'TRANSFORMS'
#set the target and subtarget
target = vari.targets[0]
target.id = armature
target.bone_target = armature.pose.bones[drivertarget].name
#set the transforms for the target. this can be rotation or location
target.transform_type = drivertt
#set the transform space. can be world space too
target.transform_space = 'LOCAL_SPACE'
target.rotation_mode = 'QUATERNION' if expresstype in ['scale', 'quat'] else 'AUTO'
#use the distance to the target bone's parent to make results consistent for different sized bones
targetbonelength = str(round((armature.pose.bones[drivertarget].head - armature.pose.bones[drivertarget].parent.head).length,3))
#driver expression is the rotation value of the target bone multiplied by a percentage of the driver target bone's length
if expresstype in ['move', 'quat']:
driver.driver.expression = vari.name + '*' + targetbonelength + '*' + drivermult
#move but only during positive rotations
elif expresstype == 'movePos':
driver.driver.expression = vari.name + '*' + targetbonelength + '*' + drivermult + ' if ' + vari.name + ' > 0 else 0'
#move but only during negative rotations
elif expresstype == 'moveNeg':
driver.driver.expression = vari.name + '*' + targetbonelength + '*' + drivermult + ' if ' + vari.name + ' < 0 else 0'
#move but the ABS value
elif expresstype == 'moveABS':
driver.driver.expression = 'abs(' + vari.name + '*' + targetbonelength + '*' + drivermult +')'
#move but the negative ABS value
elif expresstype == 'moveABSNeg':
driver.driver.expression = '-abs(' + vari.name + '*' + targetbonelength + '*' + drivermult +')'
#move but exponentially
elif expresstype == 'moveexp':
driver.driver.expression = vari.name + '*' + vari.name + '*' + targetbonelength + '*' + drivermult
elif expresstype == 'scale':
driver.driver.expression = '1 + ' + vari.name + '*' + targetbonelength + '*' + drivermult
elif expresstype == 'rotation':
driver.driver.expression = vari.name + '*' + targetbonelength + '*' + drivermult
#Set the remaining joint correction drivers
#set knee joint corrections. These go in toward the body and down toward the foot at an exponential rate
setDriver('cf_s_kneeB_R', 'location', 1, 'cf_j_leg01_R', 'ROT_X', '-0.2', expresstype = 'moveexp')
setDriver('cf_s_kneeB_R', 'location', 2, 'cf_j_leg01_R', 'ROT_X', '-0.08')
setDriver('cf_s_kneeB_L', 'location', 1, 'cf_j_leg01_L', 'ROT_X', '-0.2', expresstype = 'moveexp')
setDriver('cf_s_kneeB_L', 'location', 2, 'cf_j_leg01_L', 'ROT_X', '-0.08')
#knee correction to thicken the knee in a kneeling pose if the rigify armature is being used
# if bpy.context.scene.kkbp.armature_dropdown == 'B' and bpy.context.scene.kkbp.categorize_dropdown in ['A', 'B', 'C']:
# setDriver('cf_s_leg01_R', 'scale', 2, 'cf_j_leg01_R', 'ROT_X', '1', expresstype = 'scale')
# setDriver('cf_s_leg01_R', 'scale', 0, 'cf_j_leg01_R', 'ROT_X', '-2', expresstype = 'scale')
# setDriver('cf_s_leg01_R', 'location', 2, 'cf_j_leg01_R', 'ROT_X', '0.05', expresstype='quat')
# setDriver('cf_d_thigh03_R', 'location', 2, 'cf_j_leg01_R', 'ROT_X', '.015')
# setDriver('cf_s_leg01_L', 'scale', 2, 'cf_j_leg01_L', 'ROT_X', '1', expresstype = 'scale')
# setDriver('cf_s_leg01_L', 'scale', 0, 'cf_j_leg01_L', 'ROT_X', '-2', expresstype = 'scale')
# setDriver('cf_s_leg01_L', 'location', 2, 'cf_j_leg01_L', 'ROT_X', '0.05', expresstype='quat')
# setDriver('cf_d_thigh03_L', 'location', 2, 'cf_j_leg01_L', 'ROT_X', '-.015')
#knee tip corrections go up toward the waist and in toward the body, also rotate a bit
setDriver('cf_d_kneeF_R', 'location', 1, 'cf_j_leg01_R', 'ROT_X', '0.02')
setDriver('cf_d_kneeF_R', 'location', 2, 'cf_j_leg01_R', 'ROT_X', '-0.04')
setDriver('cf_d_kneeF_L', 'location', 1, 'cf_j_leg01_L', 'ROT_X', '0.02')
setDriver('cf_d_kneeF_L', 'location', 2, 'cf_j_leg01_L', 'ROT_X', '-0.04')
#butt corrections go slightly up to the spine and in to the waist
setDriver('cf_d_siri_R', 'location', 1, 'cf_j_thigh00_R', 'ROT_X', '0.02')
setDriver('cf_d_siri_R', 'location', 2, 'cf_j_thigh00_R', 'ROT_X', '0.02')
setDriver('cf_d_siri_L', 'location', 1, 'cf_j_thigh00_L', 'ROT_X', '0.02')
setDriver('cf_d_siri_L', 'location', 2, 'cf_j_thigh00_L', 'ROT_X', '0.02')
#hand corrections go up to the head and in towards the elbow
setDriver('cf_d_hand_R', 'location', 0, 'cf_j_hand_R', 'ROT_Z', '-0.4', expresstype = 'moveNeg')
setDriver('cf_d_hand_R', 'location', 1, 'cf_j_hand_R', 'ROT_Z', '-0.4', expresstype = 'moveNeg')
setDriver('cf_d_hand_L', 'location', 0, 'cf_j_hand_L', 'ROT_Z', '-0.4', expresstype = 'movePos')
setDriver('cf_d_hand_L', 'location', 1, 'cf_j_hand_L', 'ROT_Z', '0.4', expresstype = 'movePos')
#elboback goes out to the chest and into the shoulder
#elbo goes does the opposite
setDriver('cf_s_elboback_R', 'location', 0, 'cf_j_forearm01_R', 'ROT_X', '-0.7')
setDriver('cf_s_elboback_R', 'location', 2, 'cf_j_forearm01_R', 'ROT_X', '0.6')
setDriver('cf_s_elbo_R', 'location', 0, 'cf_j_forearm01_R', 'ROT_X', '0.025')
setDriver('cf_s_elbo_R', 'location', 2, 'cf_j_forearm01_R', 'ROT_X', '0.025')
setDriver('cf_s_elboback_L', 'location', 0, 'cf_j_forearm01_L', 'ROT_X', '-0.7')
setDriver('cf_s_elboback_L', 'location', 2, 'cf_j_forearm01_L', 'ROT_X', '-0.6')
setDriver('cf_s_elbo_L', 'location', 0, 'cf_j_forearm01_L', 'ROT_X', '0.025')
setDriver('cf_s_elbo_L', 'location', 2, 'cf_j_forearm01_L', 'ROT_X', '-0.025')
#shoulder bones have a few corrections as well
setDriver('cf_d_shoulder02_R', 'location', 1, 'cf_j_arm00_R', 'ROT_Z', '-0.1', expresstype = 'moveNeg')
setDriver('cf_d_shoulder02_R', 'location', 0, 'cf_j_arm00_R', 'ROT_Y', '0.1', expresstype = 'moveABSNeg')
setDriver('cf_d_shoulder02_R', 'location', 2, 'cf_j_arm00_R', 'ROT_Y', '-0.1')
setDriver('cf_d_shoulder02_L', 'location', 1, 'cf_j_arm00_L', 'ROT_Z', '0.1', expresstype = 'movePos')
setDriver('cf_d_shoulder02_L', 'location', 0, 'cf_j_arm00_L', 'ROT_Y', '-0.1', expresstype = 'moveABS')
setDriver('cf_d_shoulder02_L', 'location', 2, 'cf_j_arm00_L', 'ROT_Y', '0.1')
#leg corrections go up to the head and slightly forwards/backwards
setDriver('cf_s_leg_R', 'location', 1, 'cf_j_thigh00_R', 'ROT_X', '1', expresstype = 'moveexp')
setDriver('cf_s_leg_R', 'location', 2, 'cf_j_thigh00_R', 'ROT_X', '-1.5')
setDriver('cf_s_leg_L', 'location', 1, 'cf_j_thigh00_L', 'ROT_X', '1', expresstype = 'moveexp')
setDriver('cf_s_leg_L', 'location', 2, 'cf_j_thigh00_L', 'ROT_X', '-1.5')
#waist correction slightly moves out to chest when lower waist rotates
setDriver('cf_s_waist02', 'location', 2, 'cf_j_waist02', 'ROT_X', '0.2', expresstype='moveABS')
def categorize_bones(self):
'''Add some bones to bone groups to give them colors'''
if bpy.context.scene.kkbp.armature_dropdown in ['A','B']:
armature = c.get_armature()
c.switch(armature, 'pose')
if bpy.app.version[0] == 3:
bpy.ops.pose.group_add()
group_index = len(armature.pose.bone_groups)-1
group = armature.pose.bone_groups[group_index]
group.name = 'IK controllers'
armature.data.bones['cf_pv_hand_L'].select = True
armature.data.bones['cf_pv_hand_R'].select = True
armature.data.bones['MasterFootIK.L'].select = True
armature.data.bones['MasterFootIK.R'].select = True
bpy.ops.pose.group_assign(type=group_index+1)
group.color_set = 'THEME01'
c.switch(armature, 'pose')
bpy.ops.pose.group_add()
group_index = len(armature.pose.bone_groups)-1
group = armature.pose.bone_groups[group_index]
group.name = 'IK poles'
armature.pose.bone_groups.active_index = 1
armature.data.bones['cf_pv_elbo_R'].select = True
armature.data.bones['cf_pv_elbo_L'].select = True
armature.data.bones['cf_pv_knee_R'].select = True
armature.data.bones['cf_pv_knee_L'].select = True
bpy.ops.pose.group_assign(type=group_index+1)
group.color_set = 'THEME09'
else:
group_name = 'IK controllers'
for bone in ['cf_pv_hand_L', 'cf_pv_hand_R', 'MasterFootIK.L', 'MasterFootIK.R']:
self.set_armature_layer(bone, group_name)
armature.data.bones[bone].color.palette = 'THEME01'
group_name = 'IK poles'
for bone in ['cf_pv_elbo_R', 'cf_pv_elbo_L', 'cf_pv_knee_R', 'cf_pv_knee_L']:
self.set_armature_layer(bone, group_name)
armature.data.bones[bone].color.palette = 'THEME09'
def rename_bones_for_clarity(self):
'''rename core bones for easier identification. Also allows Unity to automatically detect each bone in a humanoid armature'''
if bpy.context.scene.kkbp.armature_dropdown in ['A','B']:
unity_rename_dict = {
'cf_n_height':'Center',
'cf_j_hips':'Hips',
'cf_j_waist01':'Pelvis',
'cf_j_spine01':'Spine',
'cf_j_spine02':'Chest',
'cf_j_spine03':'Upper Chest',
'cf_j_neck':'Neck',
'cf_j_head':'Head',
'cf_j_shoulder_L':'Left shoulder',
'cf_j_shoulder_R':'Right shoulder',
'cf_j_arm00_L':'Left arm',
'cf_j_arm00_R':'Right arm',
'cf_j_forearm01_L':'Left elbow',
'cf_j_forearm01_R':'Right elbow',
'cf_j_hand_R':'Right wrist',
'cf_j_hand_L':'Left wrist',
'cf_J_hitomi_tx_L':'Left Eye',
'cf_J_hitomi_tx_R':'Right Eye',
'cf_j_thumb01_L':'Thumb0_L',
'cf_j_thumb02_L':'Thumb1_L',
'cf_j_thumb03_L':'Thumb2_L',
'cf_j_ring01_L':'RingFinger1_L',
'cf_j_ring02_L':'RingFinger2_L',
'cf_j_ring03_L':'RingFinger3_L',
'cf_j_middle01_L':'MiddleFinger1_L',
'cf_j_middle02_L':'MiddleFinger2_L',
'cf_j_middle03_L':'MiddleFinger3_L',
'cf_j_little01_L':'LittleFinger1_L',
'cf_j_little02_L':'LittleFinger2_L',
'cf_j_little03_L':'LittleFinger3_L',
'cf_j_index01_L':'IndexFinger1_L',
'cf_j_index02_L':'IndexFinger2_L',
'cf_j_index03_L':'IndexFinger3_L',
'cf_j_thumb01_R':'Thumb0_R',
'cf_j_thumb02_R':'Thumb1_R',
'cf_j_thumb03_R':'Thumb2_R',
'cf_j_ring01_R':'RingFinger1_R',
'cf_j_ring02_R':'RingFinger2_R',
'cf_j_ring03_R':'RingFinger3_R',
'cf_j_middle01_R':'MiddleFinger1_R',
'cf_j_middle02_R':'MiddleFinger2_R',
'cf_j_middle03_R':'MiddleFinger3_R',
'cf_j_little01_R':'LittleFinger1_R',
'cf_j_little02_R':'LittleFinger2_R',
'cf_j_little03_R':'LittleFinger3_R',
'cf_j_index01_R':'IndexFinger1_R',
'cf_j_index02_R':'IndexFinger2_R',
'cf_j_index03_R':'IndexFinger3_R',
'cf_j_thigh00_L':'Left leg',
'cf_j_thigh00_R':'Right leg',
'cf_j_leg01_L':'Left knee',
'cf_j_leg01_R':'Right knee',
'cf_j_foot_L':'Left ankle',
'cf_j_foot_R':'Right ankle',
'cf_j_toes_L':'Left toe',
'cf_j_toes_R':'Right toe'
}
for bone in unity_rename_dict:
if c.get_armature().data.bones.get(bone):
c.get_armature().data.bones[bone].name = unity_rename_dict[bone]
#reset the eye vertex groups after renaming the bones
mod = c.get_body().modifiers[1]
mod.vertex_group = 'Left Eye'
mod = c.get_body().modifiers[2]
mod.vertex_group = 'Right Eye'
def apply_bone_widgets(self):
'''apply custom bone shapes from library file'''
if bpy.context.scene.kkbp.armature_dropdown in ['A','B']:
#Import custom bone shapes
c.import_from_library_file('Collection', ['Bone Widgets'], use_fake_user=False)
#Add custom shapes to the armature
armature = c.get_armature()
armature.data.show_bone_custom_shapes = True
c.switch(armature, 'pose')
armature.pose.bones["Spine"].custom_shape = bpy.data.objects["WidgetChest"]
armature.pose.bones["Chest"].custom_shape = bpy.data.objects["WidgetChest"]
armature.pose.bones["Upper Chest"].custom_shape = bpy.data.objects["WidgetChest"]
armature.pose.bones["cf_d_bust00"].custom_shape = bpy.data.objects["WidgetBust"]
armature.pose.bones["cf_d_bust00"].use_custom_shape_bone_size = False
armature.pose.bones["cf_j_bust01_L"].custom_shape = bpy.data.objects["WidgetBreastL"]
armature.pose.bones["cf_j_bust01_L"].use_custom_shape_bone_size = False
armature.pose.bones["cf_j_bust01_R"].custom_shape = bpy.data.objects["WidgetBreastR"]
armature.pose.bones["cf_j_bust01_R"].use_custom_shape_bone_size = False
armature.pose.bones["Left shoulder"].custom_shape = bpy.data.objects["WidgetShoulderL"]
armature.pose.bones["Right shoulder"].custom_shape = bpy.data.objects["WidgetShoulderR"]
armature.pose.bones["cf_pv_hand_R"].custom_shape = bpy.data.objects["WidgetHandR"]
armature.pose.bones["cf_pv_hand_L"].custom_shape = bpy.data.objects["WidgetHandL"]
armature.pose.bones["Head"].custom_shape = bpy.data.objects["WidgetHead"]
armature.pose.bones["Eye Controller"].custom_shape = bpy.data.objects["WidgetEye"]
armature.pose.bones["Neck"].custom_shape = bpy.data.objects["WidgetNeck"]
armature.pose.bones["Hips"].custom_shape = bpy.data.objects["WidgetHips"]
armature.pose.bones["Pelvis"].custom_shape = bpy.data.objects["WidgetPelvis"]
armature.pose.bones["MasterFootIK.R"].custom_shape = bpy.data.objects["WidgetFoot"]
armature.pose.bones["MasterFootIK.L"].custom_shape = bpy.data.objects["WidgetFoot"]
armature.pose.bones["ToeRotator.R"].custom_shape = bpy.data.objects["WidgetToe"]
armature.pose.bones["ToeRotator.L"].custom_shape = bpy.data.objects["WidgetToe"]
armature.pose.bones["HeelIK.R"].custom_shape = bpy.data.objects["WidgetHeel"]
armature.pose.bones["HeelIK.L"].custom_shape = bpy.data.objects["WidgetHeel"]
armature.pose.bones["cf_pv_knee_R"].custom_shape = bpy.data.objects["WidgetKnee"]
armature.pose.bones["cf_pv_knee_L"].custom_shape = bpy.data.objects["WidgetKnee"]
armature.pose.bones["cf_pv_elbo_R"].custom_shape = bpy.data.objects["WidgetKnee"]
armature.pose.bones["cf_pv_elbo_L"].custom_shape = bpy.data.objects["WidgetKnee"]
armature.pose.bones["Center"].custom_shape = bpy.data.objects["WidgetRoot"]
try:
bpy.context.space_data.overlay.show_relationship_lines = False
except:
#the script was run in the text editor or console, so this won't work
pass
# apply eye bones, mouth bones, eyebrow bones
eyebones = [1,2,3,4,5,6,7,8]
for piece in eyebones:
left = 'cf_J_Eye0'+str(piece)+'_s_L'
right = 'cf_J_Eye0'+str(piece)+'_s_R'
armature.pose.bones[left].custom_shape = bpy.data.objects['WidgetFace']
armature.pose.bones[right].custom_shape = bpy.data.objects['WidgetFace']
restOfFace = [
'cf_J_Mayu_R', 'cf_J_MayuMid_s_R', 'cf_J_MayuTip_s_R',
'cf_J_Mayu_L', 'cf_J_MayuMid_s_L', 'cf_J_MayuTip_s_L',
'cf_J_Mouth_R', 'cf_J_Mouth_L',
'cf_J_Mouthup', 'cf_J_MouthLow', 'cf_J_MouthMove', 'cf_J_MouthCavity']
for bone in restOfFace:
armature.pose.bones[bone].custom_shape = bpy.data.objects['WidgetFace']
evenMoreOfFace = [
'cf_J_EarUp_L', 'cf_J_EarBase_ry_L', 'cf_J_EarLow_L',
'cf_J_CheekUp2_L', 'cf_J_Eye_rz_L', 'cf_J_Eye_rz_L',
'cf_J_CheekUp_s_L', 'cf_J_CheekLow_s_L',
'cf_J_EarUp_R', 'cf_J_EarBase_ry_R', 'cf_J_EarLow_R',
'cf_J_CheekUp2_R', 'cf_J_Eye_rz_R', 'cf_J_Eye_rz_R',
'cf_J_CheekUp_s_R', 'cf_J_CheekLow_s_R',
'cf_J_ChinLow', 'cf_J_Chin_s', 'cf_J_ChinTip_Base',
'cf_J_NoseBase', 'cf_J_NoseBridge_rx', 'cf_J_Nose_tip']
for bone in evenMoreOfFace:
armature.pose.bones[bone].custom_shape = bpy.data.objects['WidgetSpine']
fingerList = [
'IndexFinger1_L', 'IndexFinger2_L', 'IndexFinger3_L',
'MiddleFinger1_L', 'MiddleFinger2_L', 'MiddleFinger3_L',
'RingFinger1_L', 'RingFinger2_L', 'RingFinger3_L',
'LittleFinger1_L', 'LittleFinger2_L', 'LittleFinger3_L',
'Thumb0_L', 'Thumb1_L', 'Thumb2_L',
'IndexFinger1_R', 'IndexFinger2_R', 'IndexFinger3_R',
'MiddleFinger1_R', 'MiddleFinger2_R', 'MiddleFinger3_R',
'RingFinger1_R', 'RingFinger2_R', 'RingFinger3_R',
'LittleFinger1_R', 'LittleFinger2_R', 'LittleFinger3_R',
'Thumb0_R', 'Thumb1_R', 'Thumb2_R']
for finger in fingerList:
if 'Thumb' in finger:
armature.pose.bones[finger].custom_shape = bpy.data.objects['WidgetFingerThumb']
else:
armature.pose.bones[finger].custom_shape = bpy.data.objects['WidgetFinger']
bp_list = self.get_bone_list('bp_list')
toe_list = self.get_bone_list('toe_list')
for bone in bp_list:
if armature.pose.bones.get(bone):
armature.pose.bones[bone].custom_shape = bpy.data.objects['WidgetSpine']
armature.pose.bones[bone].custom_shape_scale_xyz = Vector((1.8, 1.8, 1.8))
for bone in toe_list:
if armature.pose.bones.get(bone):
armature.pose.bones[bone].custom_shape = bpy.data.objects['WidgetSpine']
#Make the body and clothes layers visible
all_layers = [
False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False]
all_layers[0] = True
all_layers[8] = True
all_layers[9] = True
if bpy.app.version[0] == 3:
bpy.ops.armature.armature_layers(layers=all_layers)
else:
for index, show_layer in enumerate(all_layers):
if armature.data.collections.get(str(index)):
armature.data.collections.get(str(index)).is_visible = show_layer
armature.data.display_type = 'STICK'
def hide_widgets(self):
'''automatically hide bone widgets collection if it's visible'''
if bpy.context.scene.kkbp.armature_dropdown in ['A','B']:
c.show_layer_collection('Bone Widgets', False)
def only_show_core_armature_bones(self):
#Make only core, skirt and accessory bones visible
armature = c.get_armature()
c.switch(armature, 'object')
core_layers = [
True, False, False, False, False, False, False, False, #body
True, True, False, False, False, False, False, False, #clothes
False, False, False, False, False, False, False, False, #face
False, False, False, False, False, False, False, False]
if bpy.app.version[0] == 3:
bpy.ops.armature.armature_layers(layers=core_layers)
else:
for index, show_layer in enumerate(core_layers):
if armature.data.collections.get(str(index)):
armature.data.collections.get(str(index)).is_visible = show_layer
armature.data.display_type = 'STICK'
c.switch(armature, 'object')
# %% Supporting functions
@staticmethod
def survey(obj):
'''Function to check for empty vertex groups of an object
returns a dictionary in the form {vertex_group1: maxweight1, vertex_group2: maxweight2, etc}'''
maxWeight = {}
#prefill vertex group list with zeroes
for i in obj.vertex_groups:
maxWeight[i.name] = 0
#preserve the indexes
keylist = list(maxWeight)
#then fill in the real value using the indexes
for v in obj.data.vertices:
for g in v.groups:
gn = g.group
w = obj.vertex_groups[g.group].weight(v.index)
if (maxWeight.get(keylist[gn]) is None or w>maxWeight[keylist[gn]]):
maxWeight[keylist[gn]] = w
return maxWeight
@staticmethod
def survey_vertexes(obj):
has_vertexes = {}
for i in obj.vertex_groups:
has_vertexes[i.name] = False
#preserve the indexes
keylist = list(has_vertexes)
#then fill in the real value using the indexes
for v in obj.data.vertices:
for g in v.groups:
gn = g.group
w = obj.vertex_groups[g.group].weight(v.index)
if (has_vertexes.get(keylist[gn]) is None or w>has_vertexes[keylist[gn]]):
has_vertexes[keylist[gn]] = True
return has_vertexes
def set_armature_layer(self, bone_name, show_layer, hidden = False):
'''Assigns a bone to a bone collection.'''
armature = c.get_armature()
bone = armature.data.bones.get(bone_name)
if bone:
if bpy.app.version[0] == 3:
if armature.data.bones.get(bone_name):
armature.data.bones[bone_name].layers = (
True, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False,
False, False, False, False, False, False, False, False
)
#have to show the bone on both layer 1 and chosen layer before setting it to just chosen layer
armature.data.bones[bone_name].layers[show_layer] = True
armature.data.bones[bone_name].layers[0] = False
armature.data.bones[bone_name].hide = hidden
else:
original_mode = bpy.context.object.mode
bpy.ops.object.mode_set(mode = 'OBJECT')
show_layer = str(show_layer)
bone.collections.clear()
if armature.data.bones.get(bone_name):
if armature.data.collections.get(show_layer):
armature.data.collections[show_layer].assign(armature.data.bones.get(bone_name))
else:
armature.data.collections.new(show_layer)
armature.data.collections[show_layer].assign(armature.data.bones.get(bone_name))
armature.data.bones[bone_name].hide = hidden
bpy.ops.object.mode_set(mode = original_mode)
@staticmethod
def get_bone_list(kind):
'''returns a list of a certain category of bones'''
if kind == 'core_list':
#main bone list
return [
'cf_n_height', 'cf_j_hips', 'cf_j_waist01', 'cf_j_waist02',
'cf_j_spine01', 'cf_j_spine02', 'cf_j_spine03',
'cf_j_neck', 'cf_j_head',
'cf_d_bust00', 'cf_j_bust01_L', 'cf_j_bust01_R', 'Eyesx',
'cf_j_shoulder_L', 'cf_j_shoulder_R', 'cf_j_arm00_L', 'cf_j_arm00_R',
'cf_j_forearm01_L', 'cf_j_forearm01_R', 'cf_j_hand_R', 'cf_j_hand_L',
'cf_j_thumb01_L','cf_j_thumb02_L', 'cf_j_thumb03_L',
'cf_j_ring01_L', 'cf_j_ring02_L', 'cf_j_ring03_L',
'cf_j_middle01_L','cf_j_middle02_L', 'cf_j_middle03_L',
'cf_j_little01_L','cf_j_little02_L', 'cf_j_little03_L',
'cf_j_index01_L','cf_j_index02_L', 'cf_j_index03_L',
'cf_j_thumb01_R','cf_j_thumb02_R', 'cf_j_thumb03_R',
'cf_j_ring01_R','cf_j_ring02_R', 'cf_j_ring03_R',
'cf_j_middle01_R','cf_j_middle02_R', 'cf_j_middle03_R',
'cf_j_little01_R','cf_j_little02_R', 'cf_j_little03_R',
'cf_j_index01_R', 'cf_j_index02_R', 'cf_j_index03_R',
'cf_j_thigh00_L', 'cf_j_thigh00_R', 'cf_j_leg01_L', 'cf_j_leg01_R',
'cf_j_foot_L', 'cf_j_foot_R', 'cf_j_toes_L', 'cf_j_toes_R',
'cf_j_siri_L', 'cf_j_siri_R',
'cf_pv_knee_L', 'cf_pv_knee_R',
'cf_pv_elbo_L', 'cf_pv_elbo_R',
'cf_pv_hand_L', 'cf_pv_hand_R',
'cf_pv_foot_L', 'cf_pv_foot_R'
]
elif kind == 'non_ik':
#IK bone list
return [
'cf_j_forearm01_L', 'cf_j_forearm01_R',
'cf_j_arm00_L', 'cf_j_arm00_R',
'cf_j_thigh00_L', 'cf_j_thigh00_R',
'cf_j_leg01_L', 'cf_j_leg01_R',
'cf_j_leg03_L', 'cf_j_leg03_R',
'cf_j_foot_L', 'cf_j_foot_R',
'cf_j_hand_L', 'cf_j_hand_R',
'cf_j_bust03_L', 'cf_j_bnip02root_L', 'cf_j_bnip02_L',
'cf_j_bust03_R', 'cf_j_bnip02root_R', 'cf_j_bnip02_R']
elif kind == 'eye_list':
return [
'cf_J_Eye01_s_L', 'cf_J_Eye01_s_R',
'cf_J_Eye02_s_L', 'cf_J_Eye02_s_R',
'cf_J_Eye03_s_L', 'cf_J_Eye03_s_R',
'cf_J_Eye04_s_L', 'cf_J_Eye04_s_R',
'cf_J_Eye05_s_L', 'cf_J_Eye05_s_R',
'cf_J_Eye06_s_L', 'cf_J_Eye06_s_R',
'cf_J_Eye07_s_L', 'cf_J_Eye07_s_R',
'cf_J_Eye08_s_L', 'cf_J_Eye08_s_R',
'cf_J_Mayu_R', 'cf_J_MayuMid_s_R', 'cf_J_MayuTip_s_R',
'cf_J_Mayu_L', 'cf_J_MayuMid_s_L', 'cf_J_MayuTip_s_L']
elif kind == 'mouth_list':
return [
'cf_J_Mouth_R', 'cf_J_Mouth_L',
'cf_J_Mouthup', 'cf_J_MouthLow', 'cf_J_MouthMove', 'cf_J_MouthCavity',
'cf_J_EarUp_L', 'cf_J_EarBase_ry_L', 'cf_J_EarLow_L',
'cf_J_CheekUp2_L', 'cf_J_Eye_rz_L', 'cf_J_Eye_rz_L',
'cf_J_CheekUp_s_L', 'cf_J_CheekLow_s_L',
'cf_J_EarUp_R', 'cf_J_EarBase_ry_R', 'cf_J_EarLow_R',
'cf_J_CheekUp2_R', 'cf_J_Eye_rz_R', 'cf_J_Eye_rz_R',
'cf_J_CheekUp_s_R', 'cf_J_CheekLow_s_R',
'cf_J_ChinLow', 'cf_J_Chin_s', 'cf_J_ChinTip_Base',
'cf_J_NoseBase', 'cf_J_NoseBridge_rx', 'cf_J_Nose_tip']
elif kind == 'toe_list':
#bones that appear on the Better Penetration armature
return [
'cf_j_toes0_L', 'cf_j_toes1_L', 'cf_j_toes10_L',
'cf_j_toes2_L', 'cf_j_toes20_L',
'cf_j_toes3_L', 'cf_j_toes30_L', 'cf_j_toes4_L',
'cf_j_toes0_R', 'cf_j_toes1_R', 'cf_j_toes10_R',
'cf_j_toes2_R', 'cf_j_toes20_R',
'cf_j_toes3_R', 'cf_j_toes30_R', 'cf_j_toes4_R']
elif kind == 'bp_list':
#more bones that appear on the Better Penetration armature
return [
'cf_j_kokan', 'cf_j_ana', 'cf_J_Vagina_root', 'cf_J_Vagina_B', 'cf_J_Vagina_F',
'cf_J_Vagina_L.001', 'cf_J_Vagina_L.002', 'cf_J_Vagina_L.003', 'cf_J_Vagina_L.004', 'cf_J_Vagina_L.005',
'cf_J_Vagina_R.001', 'cf_J_Vagina_R.002', 'cf_J_Vagina_R.003', 'cf_J_Vagina_R.004', 'cf_J_Vagina_R.005']
elif kind == 'skirt_list':
return [
'cf_j_sk_00_00', 'cf_j_sk_00_01', 'cf_j_sk_00_02', 'cf_j_sk_00_03', 'cf_j_sk_00_04',
'cf_j_sk_01_00', 'cf_j_sk_01_01', 'cf_j_sk_01_02', 'cf_j_sk_01_03', 'cf_j_sk_01_04',
'cf_j_sk_02_00', 'cf_j_sk_02_01', 'cf_j_sk_02_02', 'cf_j_sk_02_03', 'cf_j_sk_02_04',
'cf_j_sk_03_00', 'cf_j_sk_03_01', 'cf_j_sk_03_02', 'cf_j_sk_03_03', 'cf_j_sk_03_04',
'cf_j_sk_04_00', 'cf_j_sk_04_01', 'cf_j_sk_04_02', 'cf_j_sk_04_03', 'cf_j_sk_04_04',
'cf_j_sk_05_00', 'cf_j_sk_05_01', 'cf_j_sk_05_02', 'cf_j_sk_05_03', 'cf_j_sk_05_04',
'cf_j_sk_06_00', 'cf_j_sk_06_01', 'cf_j_sk_06_02', 'cf_j_sk_06_03', 'cf_j_sk_06_04',
'cf_j_sk_07_00', 'cf_j_sk_07_01', 'cf_j_sk_07_02', 'cf_j_sk_07_03', 'cf_j_sk_07_04']
elif kind == 'tongue_list':
return [
'cf_j_tang_01', 'cf_j_tang_02', 'cf_j_tang_03', 'cf_j_tang_04', 'cf_j_tang_05',
'cf_j_tang_L_03', 'cf_j_tang_L_04', 'cf_j_tang_L_05',
'cf_j_tang_R_03', 'cf_j_tang_R_04', 'cf_j_tang_R_05',
]
def new_bone(self, new_bone_name):
'''Creates a new bone on the armature with the specified name and returns the blender bone'''
if bpy.app.version[0] == 3:
bpy.ops.armature.bone_primitive_add()
bone = c.get_armature().data.edit_bones['Bone']
bone.name = new_bone_name
else:
bone = c.get_armature().data.edit_bones.new(new_bone_name)
return bone
class apply_better_fbx_orientations(bpy.types.Operator):
bl_idname = "kkbp.applybetterfbxorientations"
bl_label = "应用 Better FBX 骨骼方向"
bl_description = "将骨骼方向修改为更自然的方向(基于 better_fbx 数据)。这会让骨骼沿着肢体方向延伸,而不是都朝上"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
try:
armature = c.get_armature()
if not armature:
self.report({'ERROR'}, "未找到骨架对象")
return {'CANCELLED'}
c.switch(armature, 'edit')
# 记录删除前的骨骼数量
bones_before = len(armature.data.edit_bones)
print(f"[KKBP] 操作前骨骼数量: {bones_before}")
# 如果勾选了删除配饰骨骼选项,先删除配饰骨骼
delete_option = context.scene.kkbp.delete_accessory_bones
print(f"[KKBP] 删除配饰骨骼选项: {delete_option}")
if delete_option:
print("[KKBP] 开始删除配饰骨骼...")
self.delete_accessory_bones(armature)
bones_after_delete = len(armature.data.edit_bones)
print(f"[KKBP] 删除后骨骼数量: {bones_after_delete}")
else:
print("[KKBP] 跳过删除配饰骨骼")
applied_count = 0
skipped_count = 0
print("[KKBP] 应用 better_fbx 骨骼方向...")
# 遍历字典中的所有骨骼并应用方向
for bone_name, offset in better_fbx_bone_tails.items():
if bone_name in armature.data.edit_bones:
bone = armature.data.edit_bones[bone_name]
bone.tail = bone.head + Vector(offset)
applied_count += 1
else:
skipped_count += 1
c.switch(armature, 'object')
self.report({'INFO'}, f"骨骼方向应用完成: {applied_count} 个已应用, {skipped_count} 个跳过")
print(f"[KKBP] 骨骼方向应用完成: {applied_count} 个已应用, {skipped_count} 个跳过")
print(f"[KKBP] 骨架总骨骼数: {len(armature.data.edit_bones)}")
return {'FINISHED'}
except Exception as error:
c.handle_error(self, error)
return {"CANCELLED"}
def delete_accessory_bones(self, armature):
'''删除不在 better_fbx_bone_tails 字典中的骨骼,只保留核心人体骨骼(仅用于 C 模式)'''
# 定义必须保留的骨骼(即使不在 better_fbx_bone_tails 中)
must_keep_bones = [
'cf_hit_head', # 头部碰撞
'p_cf_body_bone', # 身体骨骼
'p_cf_head_bone', # 头部骨骼
'a_n_back', # 背部锚点
'a_n_headside', # 头部侧面锚点
'cf_J_hitomi_tx_R', # 右眼瞳控制
'cf_J_hitomi_tx_L', # 左眼瞳控制
]
# 收集所有需要删除的骨骼(不在 better_fbx_bone_tails 中且不在必须保留列表中的)
bones_to_delete = []
for bone in armature.data.edit_bones:
bone_name = bone.name
# 如果骨骼不在 better_fbx_bone_tails 字典中,且不在必须保留列表中,标记为删除
if bone_name not in better_fbx_bone_tails and bone_name not in must_keep_bones:
bones_to_delete.append(bone_name)
# 删除标记的骨骼
deleted_count = 0
for bone_name in bones_to_delete:
if bone_name in armature.data.edit_bones:
armature.data.edit_bones.remove(armature.data.edit_bones[bone_name])
deleted_count += 1
print(f"[KKBP] 已删除 {deleted_count} 个配饰骨骼(包括 IK 控制骨骼)")
print(f"[KKBP] 保留了 {len(better_fbx_bone_tails)} 个核心骨骼 + {len(must_keep_bones)} 个必需骨骼")