- 기본기3 시작 프레임의 수평 루트모션 스냅을 차단하고 정면 투영/접촉 정지 거리 보정을 정리 - 스킬 완료 판정과 시작 타깃 정렬 로직을 보강하고 드로그/플레이어 정면 및 시작점 디버그 gizmo를 추가 - 드로그 기본기3 관련 애니메이션·패턴·스킬 자산을 재정리하고 Blender 보조 스크립트를 추가
178 lines
6.7 KiB
Python
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()
|