Files
Colosseum/Tools/Blender/approx_tpose.py
dal4segno 8c08e63c81 fix: 드로그 기본기3 시작 루트모션과 추적 보정 정리
- 기본기3 시작 프레임의 수평 루트모션 스냅을 차단하고 정면 투영/접촉 정지 거리 보정을 정리
- 스킬 완료 판정과 시작 타깃 정렬 로직을 보강하고 드로그/플레이어 정면 및 시작점 디버그 gizmo를 추가
- 드로그 기본기3 관련 애니메이션·패턴·스킬 자산을 재정리하고 Blender 보조 스크립트를 추가
2026-04-17 09:56:40 +09:00

178 lines
6.7 KiB
Python

import bpy
from mathutils import Matrix
from mathutils import Vector
# 사용 방법
# 1. 대략 T포즈로 만들고 싶은 Armature를 활성 선택한다.
# 2. Pose Mode에서 이 스크립트를 실행한다.
# 3. 결과가 뒤집히면 FORWARD_AXIS 부호를 바꿔 다시 실행한다.
# 4. 팔 방향이 반대로 가면 LEFT_AXIS / RIGHT_AXIS를 서로 바꿔 다시 실행한다.
# 축 설정
# Blender FBX는 전방이 -Y인 경우가 많다.
UP_AXIS = Vector((0.0, 0.0, 1.0))
FORWARD_AXIS = Vector((0.0, -1.0, 0.0))
LEFT_AXIS = Vector((1.0, 0.0, 0.0))
RIGHT_AXIS = Vector((-1.0, 0.0, 0.0))
DOWN_AXIS = Vector((0.0, 0.0, -1.0))
# 현재 포즈를 최대한 유지한 채 방향만 맞추려면 False
# 먼저 완전히 Rest Pose로 돌려놓고 시작하려면 True
RESET_TO_REST_BEFORE_ALIGN = False
# 현재 프레임에 키를 찍고 싶으면 True
INSERT_KEYFRAME = False
BONE_PATTERNS = {
"hips": ["hips", "pelvis", "root", "cog"],
"spine": ["spine", "spine_01", "spine1", "abdomen"],
"chest": ["chest", "spine_02", "spine2", "upperchest", "upper_chest"],
"neck": ["neck", "neck_01"],
"head": ["head"],
"shoulder_l": ["shoulder.l", "clavicle_l", "clavicle.l", "leftshoulder", "shoulder_l"],
"upperarm_l": ["upperarm_l", "upperarm.l", "arm_l", "leftarm", "upper_arm_l"],
"forearm_l": ["lowerarm_l", "forearm_l", "forearm.l", "leftforearm", "lower_arm_l"],
"hand_l": ["hand_l", "hand.l", "lefthand"],
"shoulder_r": ["shoulder.r", "clavicle_r", "clavicle.r", "rightshoulder", "shoulder_r"],
"upperarm_r": ["upperarm_r", "upperarm.r", "arm_r", "rightarm", "upper_arm_r"],
"forearm_r": ["lowerarm_r", "forearm_r", "forearm.r", "rightforearm", "lower_arm_r"],
"hand_r": ["hand_r", "hand.r", "righthand"],
"thigh_l": ["thigh_l", "upleg_l", "upleg.l", "leftupleg", "upperleg_l"],
"shin_l": ["calf_l", "lowerleg_l", "shin_l", "leg_l", "lowerleg.l"],
"foot_l": ["foot_l", "foot.l", "leftfoot"],
"toe_l": ["toe_l", "toes_l", "ball_l", "toe.l", "toes.l", "ball.l"],
"thigh_r": ["thigh_r", "upleg_r", "upleg.r", "rightupleg", "upperleg_r"],
"shin_r": ["calf_r", "lowerleg_r", "shin_r", "leg_r", "lowerleg.r"],
"foot_r": ["foot_r", "foot.r", "rightfoot"],
"toe_r": ["toe_r", "toes_r", "ball_r", "toe.r", "toes.r", "ball.r"],
}
def normalize_name(name: str) -> str:
return name.lower().replace(" ", "").replace("-", "").replace(":", "").replace("_", "")
def find_pose_bone(armature_obj: bpy.types.Object, key: str):
patterns = [normalize_name(item) for item in BONE_PATTERNS[key]]
for pose_bone in armature_obj.pose.bones:
normalized = normalize_name(pose_bone.name)
if normalized in patterns:
return pose_bone
for pose_bone in armature_obj.pose.bones:
normalized = normalize_name(pose_bone.name)
for pattern in patterns:
if pattern in normalized:
return pose_bone
return None
def get_bone_vector(pose_bone: bpy.types.PoseBone) -> Vector:
children = [child for child in pose_bone.children if child.bone.use_deform]
if children:
child = children[0]
vector = child.head - pose_bone.head
else:
vector = pose_bone.tail - pose_bone.head
if vector.length < 1e-6:
vector = pose_bone.tail - pose_bone.head
return vector.normalized()
def rotate_pose_bone_towards(pose_bone: bpy.types.PoseBone, target_direction: Vector):
current_direction = get_bone_vector(pose_bone)
target_direction = target_direction.normalized()
if current_direction.length < 1e-6 or target_direction.length < 1e-6:
return
rotation = current_direction.rotation_difference(target_direction)
pivot = pose_bone.head.copy()
transform = (
Matrix.Translation(pivot)
@ rotation.to_matrix().to_4x4()
@ Matrix.Translation(-pivot)
)
pose_bone.matrix = transform @ pose_bone.matrix
def insert_rotation_keyframe(pose_bone: bpy.types.PoseBone):
if pose_bone.rotation_mode == "QUATERNION":
pose_bone.keyframe_insert(data_path="rotation_quaternion")
elif pose_bone.rotation_mode == "AXIS_ANGLE":
pose_bone.keyframe_insert(data_path="rotation_axis_angle")
else:
pose_bone.keyframe_insert(data_path="rotation_euler")
def align_bone(armature_obj: bpy.types.Object, key: str, target_direction: Vector, label: str, report: list[str]):
pose_bone = find_pose_bone(armature_obj, key)
if pose_bone is None:
report.append(f"[누락] {label}")
return
rotate_pose_bone_towards(pose_bone, target_direction)
bpy.context.view_layer.update()
if INSERT_KEYFRAME:
insert_rotation_keyframe(pose_bone)
report.append(f"[정렬] {label}: {pose_bone.name}")
def main():
armature_obj = bpy.context.object
if armature_obj is None or armature_obj.type != "ARMATURE":
raise RuntimeError("활성 오브젝트가 Armature가 아닙니다.")
if bpy.context.mode != "POSE":
raise RuntimeError("Pose Mode에서 실행해야 합니다.")
if RESET_TO_REST_BEFORE_ALIGN:
bpy.ops.pose.select_all(action="SELECT")
bpy.ops.pose.transforms_clear()
bpy.context.view_layer.update()
report = []
align_bone(armature_obj, "spine", UP_AXIS, "Spine", report)
align_bone(armature_obj, "chest", UP_AXIS, "Chest", report)
align_bone(armature_obj, "neck", UP_AXIS, "Neck", report)
align_bone(armature_obj, "head", UP_AXIS, "Head", report)
align_bone(armature_obj, "shoulder_l", LEFT_AXIS, "Shoulder_L", report)
align_bone(armature_obj, "upperarm_l", LEFT_AXIS, "UpperArm_L", report)
align_bone(armature_obj, "forearm_l", LEFT_AXIS, "ForeArm_L", report)
align_bone(armature_obj, "hand_l", LEFT_AXIS, "Hand_L", report)
align_bone(armature_obj, "shoulder_r", RIGHT_AXIS, "Shoulder_R", report)
align_bone(armature_obj, "upperarm_r", RIGHT_AXIS, "UpperArm_R", report)
align_bone(armature_obj, "forearm_r", RIGHT_AXIS, "ForeArm_R", report)
align_bone(armature_obj, "hand_r", RIGHT_AXIS, "Hand_R", report)
align_bone(armature_obj, "thigh_l", DOWN_AXIS, "Thigh_L", report)
align_bone(armature_obj, "shin_l", DOWN_AXIS, "Shin_L", report)
align_bone(armature_obj, "foot_l", FORWARD_AXIS, "Foot_L", report)
align_bone(armature_obj, "toe_l", FORWARD_AXIS, "Toe_L", report)
align_bone(armature_obj, "thigh_r", DOWN_AXIS, "Thigh_R", report)
align_bone(armature_obj, "shin_r", DOWN_AXIS, "Shin_R", report)
align_bone(armature_obj, "foot_r", FORWARD_AXIS, "Foot_R", report)
align_bone(armature_obj, "toe_r", FORWARD_AXIS, "Toe_R", report)
print("\n".join(report))
print("대략 T포즈 정렬이 끝났습니다. 결과가 뒤집히면 상단 축 설정을 바꿔 다시 실행하세요.")
main()