All posts

【Maya tools and scripts】DreamWall Pickerの使い方

【Maya tools and scripts】DreamWall Pickerの使い方

Mayaでアニメーションを作っていると、コントローラーを選ぶだけで意外と時間がかかります。人型の絵の上をクリックして選ぶピッカーが、配布リグに付いてくることが多く、中身は AnimSchool Picker か MG-Picker Studio のものが大半です。AnimSchool 同梱なら、公式ストアでライセンスを有効化するところから始まります。

結論から言うと、私がいま使っているのは DreamWall Picker(dwPicker) です。ベルギーのアニメーションスタジオ DreamWall が公開している、完全無料・MITライセンスのオープンソース ピッカーです。商用案件でもスタジオ内でも、ライセンスの縛りがありません。AnimSchool Picker の .pkr を標準機能で取り込める ので、ボタンを並べ直す必要は基本的になく、AnimSchool 本体を入れなくても同梱の .pkr だけで使えます。

MG-Picker を使っているリグの場合は、AnimSchool と違って dwPicker へ一発では移せません。どうしても DreamWall Picker 一本に統一したいときは、画面を見ながらボタンを置き直すなど、手動で移行することはできます。

入れ方、基本操作、AnimSchool の .pkr の取り込み、自作まで書きます。作例は、以前紹介した MegaBuBu さんの Reimu Animation Rig です。

リグそのもののレビューは、過去記事にまとめてあります。

関連記事「Reimu Animation Rig」Mayaリグレビュー

公式リポジトリは GitHub(DreamWall-Animation/dwpicker) です。ドキュメントは Official Documentation にあります。

DreamWall Picker とは

DW PickerのReimuリグの.pkrを読み込んだ画像

dwPicker は、Maya 上でキャラクターのコントローラーを GUI から選ぶためのピッカーです。人型のガイド画像の上にボタンを置き、クリックでコントローラーを選択します。Python / MEL のコマンドボタンも置けます。

作者は Lionel Brouyère さんと Olivier Evers さんです。Hotbox Designer のフォークから始まっています。

2021年の暮れ、DreamWall はそれまで使っていた AnimSchool Picker のライセンス事情で、制作中のピッカー 200 個以上にアクセスできなくなりかけました。現場を止めるわけにはいかないので、自分たちで代替を書き、同じ事情のスタジオにも使えるようオープンソースで無料公開しています。AnimSchool の .pkr を標準で取り込めるのは、そのときのピッカーを並べ直さずに使い続ける必要があったからです。

公式の対応表記は Maya 2018〜2025 です(README 上は Maya 2017 以降)。Maya 2025 向けの PySide6 対応は Release に入っています。Maya 2026 と 2027 は公式の対応表にはまだ載っていません。Maya 2027 では、ドラッグ&ドロップで入り、シェルフからウィンドウが開きました。

入れるときは、GitHub の Releases の 最新版 を使ってください。AnimSchool の取り込み修正は 1.0.3 以降 に入っています。本稿執筆時点(2026年9月)の最新は 1.0.4 です。

なぜ今 dwPicker なのか

Maya 向けのピッカーはいくつかあります。主なものを並べます。

ツール名タイプ主な強みコスト / ライセンス
DreamWall PickerGUI / 汎用軽量、オープンソース、JSON、AnimSchool の .pkr を取り込める無料(MIT)
AnimSchool PickerGUI / 汎用長年の定番、操作感が完成している条件付き無料 / サブスク有料
MG-Picker StudioGUI / 高機能配布リグ同梱が多い、機能が多いAnimator 版無料 / 作成は有料
FT Anim PickerGUI / マルチBlender と Maya の両方無料(オープンソース)
mGear Anim Picker統合型mGear リグとの連動無料(オープンソース)
Studio Libraryリスト型ポーズ・ミラー・セット選択無料

私が dwPicker を選んでいる理由です。

  • 完全無料・MIT: 商用でも再配布でも、ライセンスで止まらない
  • AnimSchool からの移行が簡単: .pkr を File → Import するだけ(Reimu で後述)
  • データが JSON: プレーンテキストなので、壊れてもテキストエディタで中を見られる
  • コードが GitHub にある: Maya のバージョンが上がっても、自分たちで直せる/直してもらえる余地がある

AnimSchool Picker は今でも使えます。入手は AnimSchool Store で、ライセンスキーの有効化が必要です。Indie は年収 8 万ドル未満なら無料、それを超えると有料のサブスクです。dwPicker なら、その手続きなしで Reimu の .pkr が読めます。

MG-Picker は高機能ですが、保存は独自の暗号化(.mgpkr)です。dwPicker が取り込めるのは AnimSchool の .pkr だけで、.mgpkr は直接読めません。 MG-Picker 側も、標準では AnimSchool の .pkr を開きません。Animator 版は同梱の .mgpkr を使うためのものです。

他ツールとの使い分け

ピッカーの用途は、基本的にコントローラーの選択です。ポーズやアニメーションの登録・呼び出しには別のツールが要ります。現場では Studio Library と併用することが多いです。

Studio Library と併用する

人型の絵の上で選ぶのが dwPicker、ポーズやクリップの登録・呼び出し・ミラーが Studio Library です。

関連記事【Maya tools and scripts】Studio Library の使い方

Studio Library の Selection Set だけでもコントローラー選択はできます。ピッカーが無いリグでは十分役に立ちます。ただし人型の絵の上をクリックする速さには、GUI ピッカーのほうが勝ります。

FT Anim Picker という選択肢

FT Anim Picker は Maya と Blender の両方で使えるオープンソースのピッカーです。SVG でボタン形状を作れるなど、見た目の自由度は高いです。Blender と Maya をまたいで同じピッカー環境にしたい人向きです。

Maya 単体で、今ある AnimSchool の .pkr を活かしたいなら dwPicker です。

mGear のリグなら、付属の Anim Picker で足ります。汎用ピッカーとして dwPicker を足す必要はありません。Anim Picker の保存も .pkr ですが、AnimSchool の .pkr とは別物で、dwPicker では読めません。

インストールと起動

ダウンロード

GitHub の Releases から、最新の zip をダウンロードします。ソースの main ブランチを直接使うより、Release のほうが安全です。

1.0.3 以降 を入れてください。これより前だと、新しい AnimSchool 形式の .pkr 取り込みで失敗することがあります。本稿執筆時点の最新は 1.0.4 です。Reimu の .pkr を読む前に、ここを済ませておきます。

ドラッグ&ドロップで入れる

公式の入れ方です。Maya 2027 でも、この手順どおり入りました。

  1. zip を解凍する
  2. 解凍したフォルダの中の drag_and_drop_to_maya_install.py を、Maya のビューポートへドラッグ&ドロップする
  3. ダイアログで入れ方を選ぶ

入れ方は2つあります。

モード内容向いている人
Into Maya's Scripts Folder起動中の Maya の scripts フォルダへコピーし、シェルフボタンを作る普通はこちら
From the Current Folder今ある場所を参照するシェルフボタンだけ作るフォルダを動かさない人

特別な事情がなければ、Into Maya's Scripts Folder を選んでください。

ファイルは次のフォルダに入ります。中に dwpicker フォルダができます。

  • Windows: C:\Users\ユーザー名\Documents\maya\<バージョン>\scripts
  • macOS: ~/Library/Preferences/Autodesk/maya/<バージョン>/scripts
  • Linux: ~/maya/<バージョン>/scripts

シェルフから起動する

インストールが通ると、シェルフに起動ボタンが付きます。Maya 2027 では、このボタンからウィンドウが開きました。初回はキャンバスが空で、メニューは File / Edit / Help です。中身は次です。

import dwpicker
dwpicker.show()

ボタンが付かない、または消してしまった場合は、スクリプトエディタ(Python)に貼って実行し、シェルフへ中ボタンドラッグで登録してください。ホットキーで出したり消したりするなら、同じ import のあとに dwpicker.toggle() を登録します。実行するたびに表示が切り替わります。

基本的な使い方

シェルフから dwPicker を開いた直後の操作です。ピッカーがまだ空なら、File → Open で .json を出すか、File → Import で AnimSchool の .pkr を取り込みます。Reimu での取り込み手順は次の章です。

ビューの操作

キャンバスは、一般的なピッカーと同じ感覚です。

  • ズーム … ホイール、または Maya と同じ Alt + 右ドラッグ
  • パン … 中ドラッグ
  • ビューのリセット … F(Focus)。既定ではボタンに合わせる。背景画像に合わせる方法は、後半の背景の節
  • 単一選択 … ボタンを左クリック
  • 追加選択 … Shift + クリック
  • 範囲選択 … ドラッグでボックス選択

選択は Maya のビューポートと同期します(既定でオン)。ピッカーで選ぶとシーン側も選ばれ、逆も同じです。

ネームスペース(複数キャラ)

リファレンスしたキャラにはネームスペースが付きます。dwPicker は、ウィンドウ上部のコンボボックスで対象を選びます。ツールバーが出ていないときは、設定から Namespace Toolbar を表示してください。

同じリグを複数体入れるとき(Reimu が2人など)は、このコンボボックスで切り替えます。選択ボタンは、コンボボックスをリファレンスのネームスペースに合わせます。Reimu なら Outliner の : より前(例: Reimu_Rig_master)です。AnimSchool のときのように : に戻す必要はありません。入れ子のネームスペースは標準では1段だけです。

Sanae を同じシーンに足すときは、Reimu のピッカーのネームスペースを切り替えるより、Sanae 同梱のピッカーを別途読み込んでください。 コンボボックスでも体の選択はできますが、衣装や小物のコントローラーはリグごとに違うので、Sanae は Sanae のものを使った方が確実です。

FK/IK マッチのようなコマンドボタンは別です。Reimu の例は取り込みの章にあります。

パネル(Body / Face など)

メインウィンドウのタブは、開いているピッカーファイルの数です。Reimu の Body と Face を別々に Import するとタブが2つになります。Sanae も同じだと4つです。

1つの JSON の中を全身/顔で分けるのが パネル です。T(Edit → Toggle Panel Display Mode)で、左右分割とサブタブを切り替えます。作り方は後半の Advanced Editor です。

階層選択

ボタンに親子が付いていると、ダブルクリック でそのボタンと配下をまとめて選べます。キャンバス上の矢印は、子から親へ向かって描画されます。

Reimu の .pkr 取り込みでは、親子は来ません。設定のオンオフではなく、Advanced Editor で子ボタンを Alt ドラッグして親へドロップすると付きます。肩なら、肘と手首を肩へドロップします。矢印が出ていれば、ダブルクリックで配下まで選べます。

矢印が邪魔なら Edit → Toggle Hierarchy Display、または Y で隠せます。

よく使うショートカット

既定のホットキーです。

キー動作
Fビューをリセット(Focus)
Tパネルの分割/タブ表示を切り替え
Y階層の矢印表示を切り替え
Ctrl + EAdvanced Editor(編集)
Ctrl + O開く
Ctrl + S保存
Ctrl + N新規
Ctrl + Z / Ctrl + Yアンドゥ / リドゥ

アニメーション中は、F とタブ切り替えとネームスペースが分かっていれば作業できます。

今回の作例——MegaBuBu「Reimu Animation Rig」

取り込みの実例に使うのは、MegaBuBu さん(Chonlawat Thammawan さん)の Reimu Animation Rig です。Gumroad から 無料 で入手できます。ShaderFX のトゥーンシェーダー付きで、Viewport 2.0 のままプレイブラストしても見栄えがします。同梱ピッカーは AnimSchool Picker です。Body と Face が別ファイルになっています。

以前のレビューでは、AnimSchool Picker 本体の入手が難点でした。dwPicker ならそこを通らずに済みます。ただし取り込みだけでは Match FK to IK / Match IK to FK は動きません。 選択はすぐ使えます。Match の直し方は後述です。リグの中身(スカートのコリジョン、目のプリセット、FK/IK など)はレビュー側を見てください。

仕様がほぼ同じ Sanae も、同梱の .pkr を同じ手順で取り込みます。Reimu のピッカーを流用するより、Sanae のものを読んでください。リグの詳細は、冒頭のレビュー記事を見てください。

関連記事「Reimu Animation Rig」Mayaリグレビュー

Reimu のピッカーを dwPicker に取り込む

AnimSchool で作った .pkr は、作り直しなしで取り込めます。 Reimu で一度やれれば、他の配布リグの .pkr も自作の .pkr も同じです。

(※ここに Reimu のフォルダ内で .pkr が見えているエクスプローラーのスクショを挿入予定)

1. リグを入手して解凍する

  1. Reimu Animation Rig(Gumroad) からダウンロードする
  2. zip を解凍する
  3. Picker フォルダの中を .pkr で探す(Body 用と Face 用が分かれている)

私が使ったバージョンでは Reimu_Picker_Body_v3.pkr と Reimu_Picker_Face_v3.pkr でした。名前は配布バージョンで違うことがあります。Maya のシーンファイル(.ma / .mb)も、同じ配布物の中にあります。

2. リグをシーンへリファレンスする

Maya では、配布リグは リファレンス で読むのが基本です。アップデートと、アニメーションシーンの容量の両方に効きます。以前のレビューでも、Reimu はリファレンス運用を前提に書いています。

  1. File → Create Reference
  2. Reimu の .ma(または .mb)を指定する
  3. ビューポートにキャラが出ていることを確認する

インポート(ファイルをシーンに飲み込む)でもピッカーは読めますが、このあとのネームスペース切り替えを考えると、リファレンスのほうが後が楽です。

(※ここにリファレンス後の Reimu と、Reference Editor のスクショを挿入予定)

3. dwPicker で .pkr を Import する

  1. シェルフから dwPicker を開く(dwpicker.show())
  2. File → Import を選ぶ
  3. Reimu_Picker_Body_v3.pkr を指定する
  4. JSON に変換されて、dwPicker のタブに載る
  5. 同じ手順で Reimu_Picker_Face_v3.pkr も Import する

AnimSchool Picker は起動しません。登録も不要です。File → Import が、.pkr を dwPicker 形式へ変換する入口です。

取り込み後は File → Save(Ctrl + S)で、それぞれ .json として保存してください。次回からは JSON を Open すれば済みます。Reimu のフォルダに並べて置くと、後から探しやすいです。

4. 取り込み後に確認すること

ボタンの再配置は、基本的にしなくて大丈夫です。取り込んだあとに私が見ているところだけ挙げます。

  • ネームスペース … リファレンスしているので、ウィンドウ上部のコンボボックスが Reimu のネームスペースになっているか。ツールバーが出ていないときは、設定から Namespace Toolbar を表示する
  • Body と Face … タブが2つあるか。片方しか読んでいないと、顔または全身が選べない
  • クリックで選べるか … 手足のコントローラーを1つ押し、ビューポートの選択が連動するか

公式ドキュメントの対象は「2022年より前に作った AnimSchool ピッカー」です。それ以降の形式(AnimSchool v4 など)は、以前インポートが壊れることがあり、1.0.3 で修正 されています。Reimu の .pkr が読めないときは、ボタンを並べ直す前に dwPicker の Release が最新か を疑ってください。

IK/FK マッチのボタンについて

Reimu リグには Match FK to IK / Match IK to FK があります。IK で置いた手足を FK に渡す、その逆も、ボタン1つです。AnimSchool では重宝していましたが、リファレンスだとピッカーのネームスペースを : に戻してから押す必要がありました。

dwPicker に取り込むとスクリプトは空になります。ボタンは残りますが、押しても無反応です。リファレンスでもインポートでも同じです。次のコードなら、リファレンスのままで動きます。

載せ直すときは、空の Scripts を直接いじるのではなく Add command です。Ctrl + E で Advanced Editor を開き、左のキャンバスで Match FK to IK(または Match IK to FK)を選びます。右の Action で、Targets は空のまま、Scripts の Add command を押します。

Edit/Create command が開きます。Language は python、Mouse button は left。Unique undo はオン、Eval deferred はオフ。Ctrl と Shift はオフのままです。Code 欄を空にして、次の Python を貼って Ok します。

コメントは 英数字のみ にしてください。日本語が入ると、Windows では File → Open で真っ白になります(UTF-8 ではなくシステムエンコードで開くため)。

dwpicker.current_namespace() は使いません。取り込み後のターゲットは ::ctrl のままで、空が返るからです。短い名前があればそれを使い、なければ *:名前 で拾います。Maya 2027 の Reimu で、リファレンス(Reimu_Rig_master:)とネームスペースなしのインポートの両方を確認しています。

Reimu v8 の IKFK は 0 が IK、1 が FK です。割り当てはリグによって違うので、流用するときは確認してください。初期値は腕が FK(1)、足が IK(0)で、これは仕様です。AnimSchool 同梱も同じ値なので、記事のスクリプトも合わせています。Match の前に、いま操作している側のコントローラーでポーズを付けてから押してください。

AnimSchool 同梱は matchTransform ではなく、option の message 属性(fkC1〜fkC3、ikJ1〜ikJ3、fkJ3 など)からノードを取り、一時 parentConstraint で合わせます。下のスクリプトは、焼き付け(行列の読み出し→constraint 削除→書き戻し)を足しています。

ボタン名は 切り替え先 です。

ボタンやること切り替え後のモード
Match FK to IKいまの FK ポーズに IK を合わせる → IK モードへIK(IKFK = 0)
Match IK to FKいまの IK ポーズに FK を合わせる → FK モードへFK(IKFK = 1)

スクリプトで気をつけたところです。

  1. 焼き付け … 一時 parentConstraint のあと、ワールド行列/位置を読んでから constraint を消し、値を書き戻してキーを打つ。消す前にキーだけ打つと、ポーズがデフォルトに戻ることがある
  2. 足の足首 … FK と IK でローカル軸が違う。同じ Rotate Y でも向きが合わない。fkJ3(L_ik_ankle_orientOffset_drvjnt / R_…、jointOrient 約 90,0,90)を使い、FK→IK は fkJ3 → ikC3、IK→FK は fkC3_world = inv(JO) * ikC3_world
  3. 腕 … AnimSchool 同梱どおり fkJ3 / ikJ のワールド合わせ
  4. 選択 … 何も選ばないと四肢すべて。片肢だけなら、そのコントローラーを選んでから押す

Maya 2027 の Reimu v8 で、立ちポーズと動かしたポーズの往復を確認済みです。

Match FK to IK(いまの IK ポーズに FK を合わせ、FK へ切り替える):

from maya import cmds
import maya.OpenMaya as om


def n(name):
    short = name.split(':')[-1]
    if cmds.objExists(short):
        return short
    hits = cmds.ls('*:' + short) or []
    if hits:
        return hits[0]
    cmds.warning('missing ' + short)
    return None


def conn(opt, attr):
    node = n(opt)
    if not node:
        return None
    hits = cmds.listConnections(node + '.' + attr, s=True, d=False) or []
    return hits[0] if hits else None


LEG_OPTS = frozenset(('L_leg_option_ctrl', 'R_leg_option_ctrl'))

FOOT_OPTION = {
    'L_leg_option_ctrl': 'IK_L_foot_option_ctrl',
    'R_leg_option_ctrl': 'IK_R_foot_option_ctrl',
}


def zero_local(name):
    node = n(name)
    if not node:
        return
    for attr in ('tx', 'ty', 'tz', 'rx', 'ry', 'rz'):
        plug = node + '.' + attr
        if cmds.getAttr(plug, settable=True):
            cmds.setAttr(plug, 0)


def match_ik_to_fk(option):
    # Snap IK controls to FK pose (used by Match FK to IK)
    ikC1 = conn(option, 'ikC1')
    ikC2 = conn(option, 'ikC2')
    ikC3 = conn(option, 'ikC3')
    fkC3 = conn(option, 'fkC3')
    fkJ1 = conn(option, 'fkJ1')
    fkJ2 = conn(option, 'fkJ2')
    fkJ3 = conn(option, 'fkJ3')
    is_leg = option in LEG_OPTS
    tip_src = fkJ3
    if not all((ikC1, ikC2, ikC3, fkJ1, fkJ2, tip_src)):
        cmds.warning('missing FK/IK links on ' + str(option))
        return
    if is_leg and not fkC3:
        cmds.warning('missing FK ankle on ' + str(option))
        return

    # Root: translate only. Bake BEFORE deleting constraint.
    c1 = cmds.parentConstraint(fkJ1, ikC1, sr=['x', 'y', 'z'], mo=False)[0]
    t1 = cmds.xform(ikC1, q=True, ws=True, t=True)
    cmds.delete(c1)
    cmds.xform(ikC1, ws=True, t=t1)

    # Tip: arms and legs both use fkJ3 (orientOffset) world match
    c3 = cmds.parentConstraint(tip_src, ikC3, mo=False)[0]
    m3 = cmds.xform(ikC3, q=True, ws=True, matrix=True)
    cmds.delete(c3)
    if is_leg:
        t3 = cmds.xform(fkC3, q=True, ws=True, t=True)
    else:
        t3 = [m3[12], m3[13], m3[14]]
    cmds.xform(ikC3, ws=True, matrix=m3)
    cmds.xform(ikC3, ws=True, t=t3)

    if is_leg:
        foot = n(FOOT_OPTION.get(option, ''))
        if foot:
            zero_local(foot)

    a = om.MVector(*cmds.xform(fkJ1, ws=True, q=True, t=True))
    b = om.MVector(*cmds.xform(fkJ2, ws=True, q=True, t=True))
    c = om.MVector(*cmds.xform(fkC3 if is_leg else tip_src, ws=True, q=True, t=True))
    mid = (c + a) * 0.5
    pv = (b - mid) * 2 + mid
    cmds.move(pv.x, pv.y, pv.z, ikC2, ws=True)

    cmds.setKeyframe(ikC1, ikC2, ikC3)


LIMBS = (
    ('L_arm_option_ctrl', ('L_arm_option_ctrl', 'FK_L_arm', 'IK_L_arm')),
    ('R_arm_option_ctrl', ('R_arm_option_ctrl', 'FK_R_arm', 'IK_R_arm')),
    (
        'L_leg_option_ctrl',
        ('L_leg_option_ctrl', 'FK_L_leg', 'IK_L_leg', 'FK_L_foot', 'IK_L_foot'),
    ),
    (
        'R_leg_option_ctrl',
        ('R_leg_option_ctrl', 'FK_R_leg', 'IK_R_leg', 'FK_R_foot', 'IK_R_foot'),
    ),
)


def options_to_match():
    sel = cmds.ls(sl=True) or []
    if not sel:
        return [name for name, _ in LIMBS]
    matched = []
    for opt_name, prefixes in LIMBS:
        for node in sel:
            short = node.split(':')[-1]
            if any(short == p or short.startswith(p) for p in prefixes):
                matched.append(opt_name)
                break
    return matched if matched else [name for name, _ in LIMBS]


cmds.undoInfo(openChunk=True)
try:
    for opt in options_to_match():
        match_ik_to_fk(opt)
        node = n(opt)
        if node:
            cmds.setAttr(node + '.IKFK', 0)
finally:
    cmds.undoInfo(closeChunk=True)

Match IK to FK(いまの IK ポーズに FK を合わせ、FK へ切り替える):

from maya import cmds
import maya.api.OpenMaya as om2
import math


def n(name):
    short = name.split(':')[-1]
    if cmds.objExists(short):
        return short
    hits = cmds.ls('*:' + short) or []
    if hits:
        return hits[0]
    cmds.warning('missing ' + short)
    return None


def conn(opt, attr):
    node = n(opt)
    if not node:
        return None
    hits = cmds.listConnections(node + '.' + attr, s=True, d=False) or []
    return hits[0] if hits else None


LEG_OPTS = frozenset(('L_leg_option_ctrl', 'R_leg_option_ctrl'))

FOOT_ZERO = {
    'L_leg_option_ctrl': ('FK_L_foot_heel_ctrl', 'FK_L_foot_ball_ctrl'),
    'R_leg_option_ctrl': ('FK_R_foot_heel_ctrl', 'FK_R_foot_ball_ctrl'),
}


def zero_local(name):
    node = n(name)
    if not node:
        return
    for attr in ('tx', 'ty', 'tz', 'rx', 'ry', 'rz'):
        plug = node + '.' + attr
        if cmds.getAttr(plug, settable=True):
            cmds.setAttr(plug, 0)


def world_matrix(node):
    return om2.MMatrix(cmds.xform(node, q=True, ws=True, matrix=True))


def jo_matrix(joint):
    rx, ry, rz = cmds.getAttr(joint + '.jointOrient')[0]
    e = om2.MEulerRotation(
        math.radians(rx), math.radians(ry), math.radians(rz),
        om2.MEulerRotation.kXYZ,
    )
    return e.asMatrix()


def match_fk_to_ik(option):
    # Snap FK controls to IK pose (used by Match IK to FK)
    fkC1 = conn(option, 'fkC1')
    fkC2 = conn(option, 'fkC2')
    fkC3 = conn(option, 'fkC3')
    ikC3 = conn(option, 'ikC3')
    fkJ3 = conn(option, 'fkJ3')
    ikJ1 = conn(option, 'ikJ1')
    ikJ2 = conn(option, 'ikJ2')
    ikJ3 = conn(option, 'ikJ3')
    is_leg = option in LEG_OPTS
    if not all((fkC1, fkC2, fkC3, ikJ1, ikJ2, ikJ3)):
        cmds.warning('missing FK/IK links on ' + str(option))
        return
    if is_leg and not (ikC3 and fkJ3):
        cmds.warning('missing leg ankle links on ' + str(option))
        return

    # Root: rotate only. Bake BEFORE deleting constraint.
    c1 = cmds.parentConstraint(ikJ1, fkC1, st=['x', 'y', 'z'], mo=False)[0]
    m1 = cmds.xform(fkC1, q=True, ws=True, matrix=True)
    cmds.delete(c1)
    cmds.xform(fkC1, ws=True, matrix=m1)

    axis_map = (
        ('rx', ['y', 'z']),
        ('ry', ['x', 'z']),
        ('rz', ['x', 'y']),
    )
    for axis, skip_rot in axis_map:
        if cmds.getAttr(fkC2 + '.' + axis, lock=True):
            continue
        c2 = cmds.parentConstraint(
            ikJ2, fkC2, st=['x', 'y', 'z'], sr=skip_rot, mo=False
        )[0]
        m2 = cmds.xform(fkC2, q=True, ws=True, matrix=True)
        cmds.delete(c2)
        cmds.xform(fkC2, ws=True, matrix=m2)

    if is_leg:
        # Inverse of fkJ3->ikC3: fkC3_world = inv(JO) * ikC3_world
        wi = world_matrix(ikC3)
        wf = jo_matrix(fkJ3).inverse() * wi
        t = cmds.xform(ikC3, q=True, ws=True, t=True)
        cmds.xform(fkC3, ws=True, matrix=list(wf))
        cmds.xform(fkC3, ws=True, t=t)
    else:
        c3 = cmds.parentConstraint(ikJ3, fkC3, st=['x', 'y', 'z'], mo=False)[0]
        m3 = cmds.xform(fkC3, q=True, ws=True, matrix=True)
        cmds.delete(c3)
        cmds.xform(fkC3, ws=True, matrix=m3)

    cmds.setKeyframe(fkC1, fkC2, fkC3, attribute='rotate')

    if is_leg:
        for foot in FOOT_ZERO.get(option, ()):
            zero_local(foot)


LIMBS = (
    ('L_arm_option_ctrl', ('L_arm_option_ctrl', 'FK_L_arm', 'IK_L_arm')),
    ('R_arm_option_ctrl', ('R_arm_option_ctrl', 'FK_R_arm', 'IK_R_arm')),
    (
        'L_leg_option_ctrl',
        ('L_leg_option_ctrl', 'FK_L_leg', 'IK_L_leg', 'FK_L_foot', 'IK_L_foot'),
    ),
    (
        'R_leg_option_ctrl',
        ('R_leg_option_ctrl', 'FK_R_leg', 'IK_R_leg', 'FK_R_foot', 'IK_R_foot'),
    ),
)


def options_to_match():
    sel = cmds.ls(sl=True) or []
    if not sel:
        return [name for name, _ in LIMBS]
    matched = []
    for opt_name, prefixes in LIMBS:
        for node in sel:
            short = node.split(':')[-1]
            if any(short == p or short.startswith(p) for p in prefixes):
                matched.append(opt_name)
                break
    return matched if matched else [name for name, _ in LIMBS]


cmds.undoInfo(openChunk=True)
try:
    for opt in options_to_match():
        match_fk_to_ik(opt)
        node = n(opt)
        if node:
            cmds.setAttr(node + '.IKFK', 1)
finally:
    cmds.undoInfo(closeChunk=True)

Match ボタンは、ウィンドウ上部のネームスペース切り替えを合わせなくても押せます。インポートでネームスペースが無いときも、そのままで足ります。手足の IK / FK スイッチでの切り替え自体は、スクリプトなしでもできます。

CTRL / GEO の表示ボタン

Body と Face の左下に、CTRL(FACE / HAIR / CLOTH)と GEO(PROXY / BODY / CLOTH)の緑・赤があります。緑が出す、赤が消す、です。ラベルの文字自体は押しても何も起きません。効くのは横の四角です。

こちらは Match と違い、取り込み後も MEL が残っています。中身は setAttr ":option_ctrl.allFacialCtrl" 1; のような書き方です。先頭の : はネームスペースなしなので、リファレンスだとノードが見つかりません。顔カーブを出すには、option_ctrl.allFacialCtrl に加えて、ディスプレイレイヤ REMU_facial_ctrl_LY の .v も切る必要があります。Channel Box では allFacialCtrl がオンオフし、レイヤエディタのマークは付かないままでも、ビューポートは動きます。レイヤの行をスクリプトから外すと、また効かなくなります。服は REMU_clothing_ctrl_LY、髪は REMU_hair_ctrl_LY です。

直し方は、Scripts に残っている leftClick (mel) の鉛筆を押し、Language を python に変えてコードを差し替える、です。Match のように Add command すると、古い MEL が残ります。すでに足してしまった場合は、MEL のほうをゴミ箱で消してください。コメントは英数字のみです。

CTRL FACE の緑(顔コントローラーを出す)です。赤は、下の val = 1 を val = 0 にするだけです。

from maya import cmds


def n(name):
    short = name.split(':')[-1]
    if cmds.objExists(short):
        return short
    hits = cmds.ls('*:' + short) or []
    if hits:
        return hits[0]
    cmds.warning('missing ' + short)
    return None


val = 1
node = n('option_ctrl')
if node:
    cmds.setAttr(node + '.allFacialCtrl', val)
layer = n('REMU_facial_ctrl_LY')
if layer:
    cmds.setAttr(layer + '.v', val)

HAIR / CLOTH も同じ形です。差し替えるのは '.allFacialCtrl' とレイヤ名です。ドットは残してください。node + 'allHairCtrl' だとノード名とアトリビュートがつながって失敗します。GEO にレイヤは不要です。Body タブにも同じ四角があるので、使うなら同じコードを載せます。同じボタン名が表に複数行あるときは、その行を全部、1つのボタンへ書きます。GEO PROXY の緑なら、option_ctrl に対して次の3行です。

if node:
    cmds.setAttr(node + '.proxyGeo', 1)
    cmds.setAttr(node + '.characterGeo', 0)
    cmds.setAttr(node + '.outlineGeo', 0)

GEO BODY / CLOTH はノードが2つあります。レイヤは使いません。それぞれ n() してから visibility を書きます。GEO BODY の緑です。

val = 1
for name in ('body_old', 'body_outline'):
    node = n(name)
    if node:
        cmds.setAttr(node + '.visibility', val)
ボタンノードアトリビュート緑赤
CTRL FACEoption_ctrlallFacialCtrl10
CTRL FACEREMU_facial_ctrl_LYvisibility(.v)10
CTRL HAIRoption_ctrlallHairCtrl10
CTRL HAIRREMU_hair_ctrl_LYvisibility(.v)10
CTRL CLOTHoption_ctrlallClothingCtrl10
CTRL CLOTHREMU_clothing_ctrl_LYvisibility(.v)10
GEO PROXYoption_ctrlproxyGeo10
GEO PROXYoption_ctrlcharacterGeo01
GEO PROXYoption_ctrloutlineGeo01
GEO BODYbody_oldvisibility10
GEO BODYbody_outlinevisibility10
GEO CLOTHclothing_grpvisibility10
GEO CLOTHclothing_grp_outlinevisibility10

取り込み後に、必要なボタンへ上のコードを載せて、自分の JSON として保存してください。

MG-Picker について(直接は読めない)

The Mecha-Mechs のように、MG-Picker Studio 同梱のリグもあります。こちらの .mgpkr は独自の暗号化なので、dwPicker へ直接インポートはできません。 Reimu の .pkr とは手間が違います。

関連記事「The Mecha-Mechs」Mayaリグレビュー

どちらを使うかは、こう考えています。

  • そのリグを今すぐ動かしたい … MG-Picker の Animator 版(閲覧・使用)を使う
  • これから自作する/AnimSchool 資産(Reimu など)を移す … dwPicker を主軸にする

MG-Picker の画面を見ながら、dwPicker の Advanced Editor でボタンを置き直すことはできます。数分〜十数分で「使える代替」にはなります。ただしこれは移行機能ではありません。次の章と同じ、ゼロからの作成 です。

ゼロからピッカーを作る(自作リグ/MG-Picker の手動移行)

.pkr が無いリグ、自分で組んだリグ、MG-Picker 同梱を dwPicker に寄せたいときは、画面を自分で作ります。AnimSchool でピッカーを作っていた人は、操作の考え方はそのままです。保存先が JSON になり、ライセンスから解放される、という違いです。

Reimu のように .pkr があるなら Import で足ります。この章は飛ばして構いません。

Advanced Editor を開く

DreamWall Picker Advanced Editor

Edit → Advanced Editor、または Ctrl + E です。左側がキャンバス、右側がアトリビュート(Picker タブ / Shapes タブ)です。

配布してアニメーターに触られたくないときは、dwpicker.show(editable=False) で開けます。元ファイルは変わりません。

ボタンを一発で作る

TextのContentからボタンの名前を変えられる

いちばん速いのは、Maya でコントローラーを2つ以上選んでから、Advanced Editor のキャンバスを 右クリック → Add selection multiple buttons です。ツールバーや Shapes タブにはありません。1つしか選んでいないと項目がグレーになります。そのときは Add selection button(単数)です。

一括で作ると、ボタンは同じ場所に重なって置かれます。10個選ぶと10個重なるので、すぐドラッグして広げてください。下に隠れていて1個に見えることがあります。ボールのように2〜3個なら、Add selection button で1つずつ置いた方が位置が分かりやすいです。MG-Picker の画面を横に置いて同じ並びにするときも、一括は「作る」起点で、並べは手作業です。

親子階層も、この一括作成から付けるのが楽です。あとから付けるなら、Advanced Editor のキャンバスで Alt を押しながら、子ボタンを親へドラッグ&ドロップします。ダブルクリックで配下まで選べるようになります。

形・色・ラベル

ボタンの形状を変えられる

キャンバスでボタンを選んでから、Shapes タブ先頭の Display → Shape です。ドロップダウンは square / round / rounded_rect / custom です。角の丸みは、rounded_rect にしたあと Dimensions の roundness です。サイズは同じ Shape 内の Dimensions です。塗りは下へスクロールした Appearance → Background color → normal、枠は Border color → normal です。ホバーとクリック用もここにあります。文字色は Text → Color、ラベルは Text → Content です。

ボタンの色も変えられる

Custom(ベクター形状)は見た目の自由度が高い反面、描画が重いです。公式も「基本形状の約50倍遅い」「1000個超えると FPS が落ちる」と書いています。飾りに数個、くらいに留めたほうがよいです。

左右をミラーする機能もあります。命名が L_ / R_ などのときは、ミラー時に検索置換を走らせるオプションがあります。

パネルを分ける

パネルは、1つのピッカーファイルの中を分割する機能です。メインウィンドウ上部のタブ(JSON を Open / Import するたびに増えるもの)とは別です。

ボールの例では、コントローラーをパネル 1、ガイド画像をパネル 2 に振っています。メインのタブは Bouncing_Ball_rig の1つだけで、中が左右に分かれます。

T(Edit → Toggle Panel Display Mode)を押すと、分割がサブタブになります。Panel 1 がコントローラー、Panel 2 がガイドです。同じ切替は、Picker タブの Display panels as tab でもできます。

2つ目のパネルは、追加ボタンではありません。Advanced Editor の右を Picker タブにします。Picker Panels に「1」と書かれた四角があります。下辺または右辺の矢印をドラッグすると、パネル 2 が増えます。Columns orientation が vertical だと、ピッカー側は左右に並びます。

既存のボタンを移すときは、キャンバスで選んで 右クリック → Assign to panel → 1 または 2 です。Shapes タブの Panel number は表示のみで、ここでは変えられません。新しく置くボタンは、Picker Panels で選んでいる番号に入ります。

ツールバーの Isolate を入れると、選んだパネルだけがキャンバスいっぱいに出ます。パネル 1 ならコントローラー、パネル 2 ならガイドです。全部同時に見たいときは Isolate を外します。

ガイド画像の敷き方は次の節です。

Reimu なら、Body をパネル 1、Face をパネル 2 にまとめます。メインのタブは1人1つです。Sanae をリファレンスしたときは、Sanae 用の JSON をもう1タブ開きます。ネームスペース切り替えは同じリグの複製向きで、Sanae の衣装・顔は Reimu のピッカーでは足りません。結果のタブは2つ(人数分)です。別ファイルのまま Import すると、2人でタブは4つです。

Visibility Layers は、Maya のディスプレイレイヤに近い「まとめて表示/非表示」です。右クリックの Create Layer from Selection で作ります。

背景画像を敷く

キャラのシルエットや三面図をガイドにすると、ボタン位置が決まります。MG-Picker を見ながら置き直すときも、同じガイドを敷くと早いです。

  1. Advanced Editor のツールバーで、txt の右隣を押す(ホバーすると Add background shape。見た目は緑の四角)
  2. 背景ロックを外して、そのシェイプをキャンバスで選ぶ
  3. 右の Shapes タブを、Image までスクロールする
  4. Path に画像を指定する
  5. その直下の Fit to shape を True にする(画像を背景シェイプの矩形に合わせる。ビューの F とは別)

F で背景まで枠に入れたいときは、背景シェイプを選んで Shapes タブ先頭の Display → Ignored by focus を False にします。背景は既定で True なので、F はボタンだけを最大表示します。ボタンより背景が大きいなら、これだけで背景にフィットします。ボタン側を True にすると、背景だけが対象になります。

相対パス ../../images/... は公式に非対応です。絶対パス か、プロジェクト内で解決できるパスにしてください。背景を誤クリックしたくなければ、背景ロックを戻します。

タブに名前を入れる

名前を入れる場所は、Advanced Editor の Picker タブに2つあります。

Picker Name が、メインウィンドウ上部のタブ名です。ボールの例では Bouncing_Ball_rig になっています。Reimu と Sanae を並べるなら、ここにキャラ名を入れます。メインウィンドウのタブをダブルクリックするか、Edit → Change picker title でも同じです。

サブタブの名前は、その下の Panels Zoom Locked の Name です。既定は Panel 1 / Panel 2 です。セルをクリックして、Body / Face のように打ち直します。T でサブタブにしたときに、この名前が出ます。

コマンドボタンとネームスペース

選択だけでなく、Python / MEL を載せられます。IK/FK スイッチ、リセット、全選択などです。

短いノード名だけ書いて、あればその名前、なければ cmds.ls('*:名前') で実ノードを拾う方が確実です。取り込み後の .pkr はターゲットが ::ctrl のままなので、公式の dwpicker.current_namespace() は空を返し、ネームスペースなしの名前だけを探すとリファレンスで失敗します。Reimu の Match も、この探し方で、リファレンスとインポートの両方で動かしています。

ターゲットを差し替えたあとは、Advanced Editor で U を押すと更新できます。API の詳細は dwpicker Module と Scripts です。


保存と配布

保存の経路は2つあります。Reimu の取り込み結果を残すときも、同じです。

シーンに埋め込む

dwPicker は、開いているピッカーを Maya シーン側にも持ちます。Reimu をリファレンスしたアニメーションシーンを保存し、次回開いて dwPicker を起動すると、前回のピッカーが載ります。アニメーターが「JSON を別途開く」必要がないのが利点です。

JSON として書き出す

File → Save(Ctrl + S)または Save As で、外部の .json に出します。Reimu なら Body と Face を別 JSON にして、リグのフォルダへ並べておくと分かりやすいです。BOOTH やチーム共有でも、リグファイルとは別に JSON を置く運用が楽です。

JSON はテキストです。Git で差分も追えます。万一壊れても、テキストエディタで中を確認できます。バイナリの .mgpkr や、AnimSchool の .pkr より扱いやすいです。

読み込みは File → Open(Ctrl + O)です。スクリプトからなら次です。パスは自分の環境に合わせてください。

import dwpicker
dwpicker.show()
dwpicker.open_picker_file(r'C:\path\to\reimu_body.json')
dwpicker.open_picker_file(r'C:\path\to\reimu_face.json')

リファレンス時の注意

  • ネームスペースは、ツールバーで切り替える(Reimu の取り込み後と同じ)
  • JSON 内のターゲット名は、ルートのネームスペース1段を前提にする
  • 背景画像のパスが切れることがある。受け取り側でも辿れる場所に置く

まとめ

DreamWall Picker は、AnimSchool Picker が現場から引き剥がされたときに、スタジオが自分たちで代替を書いて公開したツールです。だから .pkr の取り込みが最初からある し、ライセンスは MIT で商用も問題ありません。

今回は MegaBuBu の Reimu を例にしました。やったことはこれだけです。

  1. dwPicker を入れる(最新の Release。下限は 1.0.3、本稿時点は 1.0.4)
  2. Reimu をリファレンスする
  3. File → Import で Body / Face の .pkr を読む
  4. ネームスペースを合わせて、選択できるか確認する
  5. Match が空なら、本稿の Python を Add command に載せる
  6. CTRL / GEO が効かないなら、FACE の例と同じ n() でノードを探して setAttr する
  7. JSON で保存する

AnimSchool 本体の手続きは不要です。他の .pkr 同梱リグも、同じ Import です。

自作リグや MG-Picker 同梱は、Advanced Editor でゼロから作ります。選択からボタン一発、背景、パネル、コマンドは短いノード名と *:名前 です。

関連記事「Reimu Animation Rig」Mayaリグレビュー関連記事【Maya tools and scripts】Studio Library の使い方関連記事MayaでRigging まとめ関連記事MayaでRigging まとめ

コメント

まだコメントはありません