프로그램 사용/Blender

blnder 포즈 바꾸면서 렌더하기

구차니 2026. 7. 28. 17:10

armature 관련 ridgify 였나? 플러그인으로 생성한 골격을 이용해서

 

아래처럼 다양한 포즈로 렌더하는 예제

 

pose mode 까진 아니어도

armature가 pose로 되어있어야 한다는데(by claude) 무슨소리인진 모르겠다.

import bpy
import random
import math
import os

# -------------------------
# 설정
# -------------------------
NUM_IMAGES = 200
ARMATURE_NAME = "metarig"      # 랜덤 포즈를 적용할 아마추어 이름
MAX_ANGLE_DEG = 25.0           # 본 하나가 회전할 수 있는 최대 각도(도)
EXCLUDE_NAME_KEYWORDS = [       # 이 문자열이 이름에 포함된 본은 건드리지 않음
    "root", "pelvis", "spine", "metarig",
]
# IK/Copy Rotation/Child Of 등 다른 소스가 회전을 덮어쓰는 컨스트레인트 타입.
# 이런 컨스트레인트가 걸린 본은 rotation_euler를 바꿔도 무시되므로 건너뜁니다.
SKIP_CONSTRAINT_TYPES = {"IK", "COPY_ROTATION", "COPY_TRANSFORMS", "CHILD_OF"}
RESPECT_ROTATION_LIMITS = True
RANDOM_SEED = None
DEBUG = True

armature = bpy.data.objects[ARMATURE_NAME]
scene = bpy.context.scene

if DEBUG:
    print(f"[DEBUG] 현재 씬: {scene.name} / 전체 씬 목록: {list(bpy.data.scenes.keys())}")
    print(f"[DEBUG] 아마추어: {armature.name}, 모드: {armature.mode}")
    print(f"[DEBUG] pose_position (변경 전): {armature.data.pose_position}")
    print(f"[DEBUG] animation_data: {armature.animation_data}")
    if armature.animation_data and armature.animation_data.action:
        print(f"[DEBUG] action: {armature.animation_data.action.name}")

# 1) Rest Position으로 설정되어 있으면 본 회전이 렌더에 반영되지 않으므로 강제로 Pose Position 설정
if armature.data.pose_position != 'POSE':
    armature.data.pose_position = 'POSE'
    if DEBUG:
        print(f"[DEBUG] pose_position을 'POSE'로 변경함")

# 2) 액션(F-curve)이 물려있으면 매 프레임 평가 시 수동으로 넣은 rotation_euler를 덮어쓰므로 해제
if armature.animation_data and armature.animation_data.action is not None:
    if DEBUG:
        print(f"[DEBUG] 기존 액션 '{armature.animation_data.action.name}' 을 해제함 (수동 포즈와 충돌 방지)")
    armature.animation_data.action = None

scene.render.engine = 'BLENDER_EEVEE'
scene.render.resolution_x = 640
scene.render.resolution_y = 480
scene.render.resolution_percentage = 100

# 저장 폴더 (blend 파일 기준)
output_dir = bpy.path.abspath("//render_output")
os.makedirs(output_dir, exist_ok=True)

# PNG 저장
scene.render.image_settings.file_format = 'PNG'


def should_skip(bone_name: str) -> bool:
    lower = bone_name.lower()
    return any(kw.lower() in lower for kw in EXCLUDE_NAME_KEYWORDS)


def has_blocking_constraint(pbone) -> bool:
    """IK 등 다른 소스가 회전을 덮어쓰는 컨스트레인트가 있으면 True."""
    for c in pbone.constraints:
        if c.mute:
            continue
        if c.type in SKIP_CONSTRAINT_TYPES:
            return True
    return False


def get_rotation_limits(pbone):
    """본에 Limit Rotation 컨스트레인트가 있으면 (min, max) 라디안 튜플 반환."""
    for c in pbone.constraints:
        if c.type == "LIMIT_ROTATION" and not c.mute:
            return (
                (c.min_x, c.max_x),
                (c.min_y, c.max_y),
                (c.min_z, c.max_z),
                c.use_limit_x, c.use_limit_y, c.use_limit_z,
            )
    return None


def random_rotate_bone(pbone, max_angle_rad):
    limits = get_rotation_limits(pbone) if RESPECT_ROTATION_LIMITS else None
    prev_mode = pbone.rotation_mode
    if prev_mode != "XYZ":
        pbone.rotation_mode = "XYZ"

    rx = random.uniform(-max_angle_rad, max_angle_rad)
    ry = random.uniform(-max_angle_rad, max_angle_rad)
    rz = random.uniform(-max_angle_rad, max_angle_rad)

    if limits:
        (minx, maxx), (miny, maxy), (minz, maxz), ux, uy, uz = limits
        if ux:
            rx = max(minx, min(maxx, rx))
        if uy:
            ry = max(miny, min(maxy, ry))
        if uz:
            rz = max(minz, min(maxz, rz))

    pbone.rotation_euler = (rx, ry, rz)


def randomize_pose():
    max_angle_rad = math.radians(MAX_ANGLE_DEG)
    rotated = 0
    skipped_name = 0
    skipped_constraint = 0

    for pbone in armature.pose.bones:
        if should_skip(pbone.name):
            skipped_name += 1
            continue
        if has_blocking_constraint(pbone):
            skipped_constraint += 1
            continue
        random_rotate_bone(pbone, max_angle_rad)
        rotated += 1

    if DEBUG:
        print(f"[DEBUG] 회전 적용: {rotated}개 / 이름 제외: {skipped_name}개 / 컨스트레인트 제외: {skipped_constraint}개")


if RANDOM_SEED is not None:
    random.seed(RANDOM_SEED)

bpy.context.view_layer.objects.active = armature
prev_mode = armature.mode

if armature.mode != "POSE":
    result = bpy.ops.object.mode_set(mode="POSE")
    if DEBUG:
        print(f"[DEBUG] mode_set(POSE) 결과: {result}, 현재 모드: {armature.mode}")

if DEBUG:
    all_bones = [b.name for b in armature.pose.bones]
    print(f"[DEBUG] 전체 본 개수: {len(all_bones)}")
    print(f"[DEBUG] 본 목록: {all_bones}")

# -------------------------
# 렌더링 루프
# -------------------------
for i in range(NUM_IMAGES):
    # 매 렌더마다 아마추어 랜덤 포즈 적용
    randomize_pose()

    # 씬/디펜던시 그래프 강제 갱신 (배치 렌더 시 이전 프레임 캐시 문제 방지)
    bpy.context.view_layer.update()
    depsgraph = bpy.context.evaluated_depsgraph_get()
    depsgraph.update()

    # 렌더 저장 경로
    scene.render.filepath = os.path.join(output_dir, f"image_{i:03d}.png")
    print(f"Rendering {scene.render.filepath}")
    bpy.ops.render.render(write_still=True)

if prev_mode != armature.mode:
    bpy.ops.object.mode_set(mode=prev_mode)

print("Done.")

 

+

object mode /  bone 에서 이런걸 발견. 아.. 이게 위에 그거였나?