【メモ】Maya アニメーションのランダムな動きを与える

ChatGPTで作成

選択したオブジェクトの親階層以下、あるいは子階層のみのモーションにランダムを与える。

import maya.cmds as cmds
import random

WINDOW_NAME = "rotationNoiseTool_v6"


def get_hierarchy_transforms(include_selected_parent=True):
    selected = cmds.ls(selection=True, long=True) or []
    targets = []

    for obj in selected:
        if include_selected_parent and cmds.nodeType(obj) == "transform":
            targets.append(obj)

        children = cmds.listRelatives(
            obj,
            allDescendents=True,
            type="transform",
            fullPath=True
        ) or []

        targets.extend(children)

    return list(dict.fromkeys(targets))


def add_rotation_noise(
    min_interval=3,
    max_interval=12,
    min_percent=5.0,
    max_percent=400.0,
    spline_ratio=0.5,
    fallback_base=1.0,
    no_key_random_min=-10.0,
    no_key_random_max=10.0,
    include_selected_parent=True
):
    targets = get_hierarchy_transforms(include_selected_parent)

    if not targets:
        cmds.warning("対象オブジェクトがありません。")
        return

    if min_interval > max_interval:
        min_interval, max_interval = max_interval, min_interval

    if min_percent > max_percent:
        min_percent, max_percent = max_percent, min_percent

    if no_key_random_min > no_key_random_max:
        no_key_random_min, no_key_random_max = no_key_random_max, no_key_random_min

    start = int(cmds.playbackOptions(q=True, min=True))
    end = int(cmds.playbackOptions(q=True, max=True))

    rotate_attrs = ["rotateX", "rotateY", "rotateZ"]
    inserted_count = 0

    for obj in targets:
        for attr in rotate_attrs:
            plug = obj + "." + attr

            if not cmds.objExists(plug):
                continue

            if cmds.getAttr(plug, lock=True):
                continue

            key_times = cmds.keyframe(plug, q=True, timeChange=True) or []
            key_times = sorted(set([float(k) for k in key_times]))

            frame = start + random.randint(min_interval, max_interval)

            while frame <= end:
                base_value = cmds.getAttr(plug, time=frame)

                if not key_times:
                    random_offset = random.uniform(no_key_random_min, no_key_random_max)
                    new_value = base_value + random_offset

                else:
                    prev_keys = [k for k in key_times if k < frame]
                    next_keys = [k for k in key_times if k > frame]

                    prev_key = max(prev_keys) if prev_keys else None
                    next_key = min(next_keys) if next_keys else None

                    if prev_key is not None and next_key is not None:
                        prev_value = cmds.getAttr(plug, time=prev_key)
                        next_value = cmds.getAttr(plug, time=next_key)
                        delta_base = abs(next_value - prev_value)

                    elif prev_key is not None:
                        prev_value = cmds.getAttr(plug, time=prev_key)
                        delta_base = abs(base_value - prev_value)

                    elif next_key is not None:
                        next_value = cmds.getAttr(plug, time=next_key)
                        delta_base = abs(next_value - base_value)

                    else:
                        delta_base = fallback_base

                    if delta_base == 0:
                        delta_base = fallback_base

                    percent = random.uniform(min_percent, max_percent) / 100.0
                    amount = delta_base * percent
                    direction = random.choice([-1, 1])

                    new_value = base_value + amount * direction

                cmds.setKeyframe(
                    plug,
                    time=frame,
                    value=new_value
                )

                tangent_type = "spline" if random.random() < spline_ratio else "linear"

                cmds.keyTangent(
                    plug,
                    time=(frame, frame),
                    inTangentType=tangent_type,
                    outTangentType=tangent_type
                )

                inserted_count += 1
                key_times.append(float(frame))
                key_times = sorted(set(key_times))

                frame += random.randint(min_interval, max_interval)

    cmds.inViewMessage(
        amg="回転キーを {} 個追加しました".format(inserted_count),
        pos="midCenter",
        fade=True
    )

    print("Rotation keys inserted:", inserted_count)


def run_from_ui(*args):
    add_rotation_noise(
        min_interval=cmds.intSliderGrp("minInterval", q=True, value=True),
        max_interval=cmds.intSliderGrp("maxInterval", q=True, value=True),
        min_percent=cmds.floatSliderGrp("minPercent", q=True, value=True),
        max_percent=cmds.floatSliderGrp("maxPercent", q=True, value=True),
        spline_ratio=cmds.floatSliderGrp("splineRatio", q=True, value=True),
        fallback_base=cmds.floatSliderGrp("fallbackBase", q=True, value=True),
        no_key_random_min=cmds.floatSliderGrp("noKeyRandomMin", q=True, value=True),
        no_key_random_max=cmds.floatSliderGrp("noKeyRandomMax", q=True, value=True),
        include_selected_parent=cmds.checkBox("includeSelectedParent", q=True, value=True)
    )


def create_ui():
    if cmds.window(WINDOW_NAME, exists=True):
        cmds.deleteUI(WINDOW_NAME)

    cmds.window(WINDOW_NAME, title="Rotation Noise Tool v6", widthHeight=(470, 420))
    cmds.columnLayout(adjustableColumn=True, rowSpacing=10)

    cmds.text(label="選択オブジェクト+下階層の回転にランダムキーを追加")

    cmds.checkBox(
        "includeSelectedParent",
        label="最初に選択した親オブジェクトにも回転キーを追加する",
        value=True
    )

    cmds.intSliderGrp(
        "minInterval",
        label="間隔 下限",
        field=True,
        minValue=1,
        maxValue=100,
        value=3
    )

    cmds.intSliderGrp(
        "maxInterval",
        label="間隔 上限",
        field=True,
        minValue=1,
        maxValue=100,
        value=12
    )

    cmds.floatSliderGrp(
        "minPercent",
        label="変化量 下限 %",
        field=True,
        minValue=5.0,
        maxValue=400.0,
        value=5.0
    )

    cmds.floatSliderGrp(
        "maxPercent",
        label="変化量 上限 %",
        field=True,
        minValue=5.0,
        maxValue=400.0,
        value=400.0
    )

    cmds.floatSliderGrp(
        "fallbackBase",
        label="基準差分が0の時の角度",
        field=True,
        minValue=0.1,
        maxValue=30.0,
        value=1.0
    )

    cmds.floatSliderGrp(
        "noKeyRandomMin",
        label="キー無し時 最小角度",
        field=True,
        minValue=-180.0,
        maxValue=180.0,
        value=-10.0
    )

    cmds.floatSliderGrp(
        "noKeyRandomMax",
        label="キー無し時 最大角度",
        field=True,
        minValue=-180.0,
        maxValue=180.0,
        value=10.0
    )

    cmds.floatSliderGrp(
        "splineRatio",
        label="Spline比率",
        field=True,
        minValue=0.0,
        maxValue=1.0,
        value=0.5
    )

    cmds.button(
        label="回転キーを追加",
        height=40,
        command=run_from_ui
    )

    cmds.showWindow(WINDOW_NAME)


create_ui()Code language: Python (python)