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

2281 lines
113 KiB
Python

# 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),
}
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()
# 最后再次应用 better_fbx 骨骼方向,确保不被其他步骤覆盖
self.apply_better_fbx_bone_orientations()
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()
# 删除无用的配饰骨骼(头发、衣服等),只保留核心人体骨骼
# self.delete_accessory_bones()
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 中)
# 这些是 KKBP 后续步骤需要的特殊骨骼
must_keep_bones = [
'cf_pv_root', # IK 根骨骼
'cf_pv_hand_L', 'cf_pv_hand_R', # 手部 IK
'cf_pv_elbo_L', 'cf_pv_elbo_R', # 肘部 IK
'cf_pv_foot_L', 'cf_pv_foot_R', # 脚部 IK
'cf_pv_knee_L', 'cf_pv_knee_R', # 膝盖 IK
'cf_hit_head', # 头部碰撞
'p_cf_body_bone', # 身体骨骼
'p_cf_head_bone', # 头部骨骼
'a_n_back', # 背部锚点
'a_n_headside', # 头部侧面锚点
]
# 收集所有需要删除的骨骼(不在 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} 个配饰骨骼")
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)
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':
# 使用 better_fbx 数据而不是简单地对齐到目标骨骼
if bone1 in better_fbx_bone_tails:
print(f"[KKBP] 保留 {bone1} 的 better_fbx 骨骼方向")
# 不修改尾部,保留之前设置的方向
pass
else:
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:
# 使用 better_fbx 数据而不是简单地对齐到目标骨骼
if bone1 in better_fbx_bone_tails:
print(f"[KKBP] 保留 {bone1} 的 better_fbx 骨骼方向")
# 不修改尾部,保留之前设置的方向
pass
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 apply_better_fbx_bone_orientations(self):
'''统一应用 better_fbx 骨骼方向,确保不被其他步骤覆盖'''
armature = c.get_armature()
c.switch(armature, 'edit')
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
print(f"[KKBP] 骨骼方向应用完成: {applied_count} 个已应用, {skipped_count} 个跳过")
print(f"[KKBP] 骨架总骨骼数: {len(armature.data.edit_bones)}")
c.switch(armature, 'object')
c.print_timer('apply_better_fbx_bone_orientations')
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