CG・コンテンツ制作
  1. CG・コンテンツ制作トップ
  2. DAIKIN CG Channel
  3. UsersNotes
  4. スクリプト
  5. Maya;Python でカスタム・グラフエディタ を作ろう!! Version 30
SUITE USERS NOTES

Maya;Python でカスタム・グラフエディタを作ろう!!
Version 30
GTMF2015 ネタ
(Maya 2012、2014、2015、2016
Maya; Let's Make a Custom GraphEditor with Python Version 30

プロローグ;Prologue V30公開

CustomGraphEditor_V30_00s
Pythonについてのページになります。
その成果物としてカスタマイズしたグラフエディタを作成しました。

なぜ、グラフエディタをカスタマイズしたか、と言うと、
カレントフレームをスクラブするスライダーが無かった、ことからはじまります。
ええ、Kキー(デフォルト)[SI HotKey はAlt+k]で動くことは知ってます。
でも、キーコマンドを実行してスクラブしてファンクション・カーブ編集は、ちょっと・・ね。
更に、カーブのタンジェントの編集も同一画面で出来たら、、と思ったからです。

今回は、Python で、特にGUIを作成する部分にスポットをあてて
詳しく解説すると共に、キター!! って対処方法も紹介します。

更新 Version 2
ステップ数の設定欄と、その値で、前フレーム/現在/次フレーム
に移動するボタンを追加しました。
複数表示の時、他のカレントフレームの値とは左欄の値は連動
しないので [現在] を押して合わせられるようにしています。
最大フレーム数の次は最小フレーム数に戻って来ます。(逆も)[Step数考慮必要]
更新 Version 3
直接 animファイル保存読み込みが可能なボタンの追加と
幾つかの不具合の解消しました。
更新 Version 4
animファイルの階層アニメーションに対応しました。
更新 Version 5
FBXファイルの直接保存読み込みが可能なボタンの追加と
階層の選択ボタンが追加されました。
更新 Version 6
選択したアニメーションカーブからオブジェクトを選択する
ボタンが追加されました。
更新 Version 10
画面一新、機能大幅アップ
Mayaタイムライン、カーブ&キー選択、フィルタリング機能追加等々
更新 Version 20
未使用パネルの削除対応とWindow化
パネルがドンドンと溜まるのを防ぐ対応 等々

更新 Version 30
Outliner対応
横にOutlinerが表示

なお、このサイトに掲載している事例の決まり事ですが、
使用に関しては自己責任でよろしくお願い申し上げます。
(Maya2014、2015、2016 で動作確認)

Only Japanese , at the moment. If you want this page to be in English
please Tweet or Mail me hard ^ _^ ;;
PS
カーブ・タンジェント編集部分のスクリプトは、既に公開されている
ユーザーさんの記述を参考にさせて頂いた部分も含まれています。
ありがたく使わせて頂きました、よろしくお願いします。
Windows7 Professional 64Bit、Intel Core i7-3930K(3.20GHz,6コア/12スレッド)
メモリ16GB、NVIDIA GeForce GTX560 Ti 2GB(メモリ2GB)

ritaro_ml



パネルについての注意  >> この問題は Version 20 で解消されました。
Maya パネル について、前情報を提供します。(長年 Maya を使っている方でも知らなかった、と良く言われる情報なんです)
前ページで紹介している SI_HotKey にて対応している各種エディターの複数表示を行ったり、
今回の カスタム・グラフエディタ 複数表示していたりしているものはパネルになります。
詳しく述べると、画面表示したデフォルト仕様の グラフエディタ ティアオフして、そのコピーを表示している ということなります。
cgrd_02
この状態をパネル > パネルから見てみると、コピーした分のパネルが作成されて残っているのが解るかと思います。
cgrd_03
今度は、Mayaの設定を見てみます
プレファレンス > インターフェイス > UI要素 > パネルの構成の個目です。
■保存するとき■開くとき両方チェックが入っているのが デフォルト設定です。
cgrd_04
これは、シーン内のパネル全てを保存し、そして全てのパネルを保存した状態のシーン開く という意味です。
保存時に開いていたウィンドー情報を保持するので、次に開く時、前と同じ状態になる、と言う意味では便利ですが、
増やしていった全てのパネルをドンドンと溜めていく、という意味ではふさわしくない、とも言えます。
そこで、開くときチェックを外すとパネルはリフレッシュされた綺麗な状態で開きます。
また、大人数のプロジェクトで他の人が作業した時のウィンドの状態(例テキスチャーエディタ等 )を開きたく無い場合にも、開くときチェックを外すと良いです。
自分の作業には必要のないウィンドーが表示されるわずらわしさをなくし、シーンを開く速度を速めます

作業中のシーンで増えてしまったパネルを削除するにはパネルエディタで削除を行います。(パネルをうまく・きれいに削除するスクリプトは無いようです。)
cgrd_05
以上、パネルについての知識を踏まえた上で、マルチカスタムグラフエディタをお楽しみください。
cgrd_01



Maya での Python 準備
Maya 日本語ドキュメントをダウンロード して HELドキュメント をローカルディスクに用意して、Mayaに認識させましょう。
Download & Install Maya Product Help(日本語Web版はこちら >> http://help.autodesk.com/view/MAYAUL/2015/JPN/
https://www.autodesk.com/support/technical/article/caas/tsarticles/ts/6hGHDwrHzKBq8zd65p4LpK.html
最新の2015版はインストーラーではなく、ZIPで展開するだけのものなので、
インストーラーだった May 2014 版 のディレクトリーを真似て 2015版を置くとしたら
C:\Program Files (x86)\Autodesk\Maya2015\docs\Maya2015\ja_JP の下に展開したものをコピーします。
その場所を、プレファレンス > インターフェイス > ヘルプヘルプの場所項目でカスタムの欄に登録します。
Maya_2015DOC_0
そうしたら、スクリプトエディタ で、選択したコマンド名から >  コマンドドキュメント を表示させたり、
ヘルプ > Pythonのヘルプユーザーガイドが素早く表示できたりします。
また、コマンド > クィックヘルプの表示有効にすると、右側に欄にオプションが表示されます。(狭いよ~^_^;;)
コマンド > ツールヒントヘルプの表示はコマンドを書いている途中に情報を表示したりします。
等など、色々と表示される仕組みはあるのですが、あとは好みでお願いします。
Maya_2015DOC_1



GraphEditor について;Python
MayaGraphEditor についてまず言えることは、"複数のツールから組合わさあれた scripted panel である"、ということです。
ドキュメントを見ると、複数のウィンドをまとめて表示しているのがパネルだと解ります。

scriptedPanelhttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/scriptedPanel.html

複数のGUIデータからなる GraphEditor ということが良く解る 現象として
スクリプトから GraphEditor をうまく綺麗に削除することができず、中身がなくてパネル枠だけで残った状態になったりします。
(パネルエディタからパネルを削除した時のログを見ると、deleteGraphEditor graphEditor2など見られますが、このコマンドだけでは綺麗に削除することができません)

つまり、ここで紹介する、ティアオフによる複数表示の GraphEditorは、パネルエディタから削除するが最も無難なようです。(か、開くときをOFFです。)

更に複数ツールから成り立っていることが解ることとして、アニメーションのカーブ部分を扱うコマンドは animCurveEditorになっています。

animCurveEditorhttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/animCurveEditor.html


さて、これらのことを知りつつ、パネルの情報を得るコマンドとしてはgetPanel というものがあります。

getPanelhttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/getPanel.html


以下の記述で黒い枠内に書かれたスクリプトは、下図のようにスクリプトエディタでPythonタブ内に記述して実行したものとします。
実行時にスクリプトを選択して状態で実行(テンキーのEnter)をした場合、実行後に記述が消えません。

getPanel scriptType というオプションを使い そのタイプが 'graphEditor' のもの って指定出来ます。
新規シーンでは GraphEditor が1つデフォルトで存在します。
また、取得した名前は[u'graphEditor1']とタイプが unicode であることに注目してください。
cgrd_06
ティアオフした GraphEditor を追加で4つ作成すると、合計5つの GraphEditor がシーンにあることになります。

import maya.cmds as cmds
print cmds.getPanel( scriptType='graphEditor' )

# 結果: [u'graphEditor1', u'graphEditor2', u'graphEditor3', u'graphEditor4', u'graphEditor5'] #


何個あるかはlen()で、数値 を 文字にするには str()なので、Python では 一気に書け~! で、こんな風に記述できます。

import maya.cmds as cmds
print str(len(cmds.getPanel( scriptType='graphEditor' )))

# 結果: 5 # <<これは文字列


また、動作を行っている己のパネル(アクティブなパネル)の名前を知るには withFocus というオプションを使います。
スクリプトエディタもパネルなので、実行するとこのスクリプトエディタのパネル名を取得します。

import maya.cmds as cmds
cmds.getPanel( withFocus=True )

# 結果: scriptEditorPanel1 # <<これは実行したパネル、つまりスクリプトエディタ・パネル



ではでは、とっておきの本命。
ティアオフしてコピーした GraphEditor を表示するようなスクリプトはないのか?
はい、実は2つも方法があります。(探してもみつからなかった?)
また、 MayaのGUIを作成している部分は Melなので、PythonからはMelを呼び出して実行するというかたちにします。

1つは、その名ものずばりtearOffCopyItemCmd、もう1つはtearOffPanelを使います。

前者は、こう解きます。 >> シーンにデフォルトであるスクリプトパネルの名前は graphEditor1 なので、それをティアオフコピーする。

import maya.cmds as cmds
import maya.mel as mel

mel.eval('tearOffCopyItemCmd scriptedPanel graphEditor1;')

同じスクリプトを何回でも実行出来て、何個でも GraphEditor を作れます。
パネルエディタで見るとグラフ エディタ2,3,4・・・という名前になっていきます。
このコマンドでは、出来るパネルの名前が設定できないのと、日本語ってどうよ、って感じなのです。


後者は、こう解きます。 >> graphEditorGraphEditor1 という名前のパネルでティアオフする。

import maya.cmds as cmds
import maya.mel as mel

mel.eval('tearOffPanel "GraphEditor1" "graphEditor" true;')

今度は、同じスクリプトを実行しても、最初しか表示しません。それは、既にGraphEditor1という名前のパネルが存在しているからです。
そこで出来るパネルの名前を"GraphEditor2""GraphEditor3"と変えてみると、その名のパネルがドンドンと作成出来ます。

ということで、こう解きます。
既存の GraphEditor の数を得て、それに1を足した数値を文字列に置き換えて"GraphEditor"の後ろに追記していけば
同じスクリプトでドンドンパネルが追加出来ると。

そこで、はっと思い出します。
あー、Melを実行しているコマンド部分にPytonからの変数を渡さなければならない と。

import maya.cmds as cmds
import maya.mel as mel

grp_panel_no_a = str( len(cmds.getPanel( scriptType='graphEditor' )) + 1 )
panel_name = "GraphEditor" + grp_panel_no_a

mel.eval('string $panel_name = "%s"' % panel_name)
mel.eval('tearOffPanel $panel_name "graphEditor" true;')


GraphEditor2から始まりますが、同じスクリプトで GraphEditor をどんどん追加表示できます。

ではここで、ティアオフしたGraphEditor2・・・が作成されるコマンドの後で
アクティブなパネルの名前を取得しようとprint cmds.getPanel( scriptType='graphEditor' )を記述して実行しても
まだスクリプトを実行しているスクリプトエディターがアクティブな状態なので、scriptEditorPanel1しか取得できません。

import maya.cmds as cmds
import maya.mel as mel

grp_panel_no_a = str( len(cmds.getPanel( scriptType='graphEditor' )) + 1 )
panel_name = "GraphEditor" + grp_panel_no_a

mel.eval('string $panel_name = "%s"' % panel_name)
mel.eval('tearOffPanel $panel_name "graphEditor" true;')

print cmds.getPanel( withFocus=True )

# 結果: scriptEditorPanel1


この章の締めとして、上記内容を1つのファイルCustomGraphEditor.pyとして保存し、Shelfにボタン登録したボタンから起動出来るようにしてみます。
以下のように書き直します。def 内は、4つのスペース(インデント)を設けて記述します。

import maya.cmds as cmds
import maya.mel as mel

def custom_graph_editor(*args):

    grp_panel_no_a = str( len(cmds.getPanel( scriptType='graphEditor' )) + 1 )
    panel_name = "GraphEditor" + grp_panel_no_a

    mel.eval('string $panel_name = "%s"' % panel_name)
    mel.eval('tearOffPanel $panel_name "graphEditor" true;')

    print cmds.getPanel( withFocus=True )

そしたら、スクリプトエディタの右上のプルダウンメニューから、ファイル > スクリプトの保存を選択します。
cgrd_07
保存先がユーザーディレクトリーのscripts 内になっているので、CustomGraphEditor.pyとして保存します。
(MAYA_SCRIPT_PATHがデフォルトで通っているディレクトリーです。)
(PCの環境設定にMAYA_APP_DIRを設定している場合は、ユーザーのディレクトリーはそちらになります。)

今度は、そのCustomGraphEditor.pyファイルを実行するスクリプトです。

import CustomGraphEditor
CustomGraphEditor.custom_graph_editor()


実行して動くことを確認したら、ボタンを登録したいShelfタブ を表示しておきます。
スクリプトエディタの右上のプルダウンメニューから、ファイル > スクリプトをシェルフに保存...を選択します。

このページの下で公開しているCustomGraphEditor.pyシェルフに登録する時は、
CustomGraphEditor.py をユーザーディレクトリーにコピーして、
例C:\Users\ユーザー名\Documents\maya\2015-x64\ja_JP\prefs\scripts(MAYA_APP_DIRがある場合はそちら
以下のコマンドで立ち上げます。

import CustomGraphEditor
CustomGraphEditor.custom_graph_editor()
CustomGraphED-ICONICON

cgrd_08
アイコンのラベル文字を解るような短縮文字にしてシェルフを保存 します。
これでもう、ボタンを押せばカスタムのグラフエディタが表示される仕組みは準備できました。



Mayaのメインメニューからのプルダウンを設定して、そこからの起動は、下記のように記述します。

import maya.cmds as cmds
import menu.qt_menu_SliderFrame
import CustomGraphEditor

def ST_Pro1_MainMenu():
    cmds.setParent( 'MayaWindow' )
    cmds.menu( label=u'Pro1MainMenu', tearOff=True )

    cmds.setParent( '..', menu=True )
    cmds.menuItem( divider=True )
    cmds.menuItem( subMenu=True, label='Pro1_AnimeTools', tearOff=True)
    cmds.menuItem( label='SliderFrame', command='menu.qt_menu_SliderFrame.Open_qt_menu_SliderFrame()' )
    cmds.menuItem( label='CustomGraphEditor', command='CustomGraphEditor_00.custom_graph_editor' )
    cmds.menuItem( divider=True )

メニュー自体もティアオフ出来ます。
こちらについては、ワークグループの設定を説明する別ページにて解説出来ればと考えています。(お楽しみに ^_^;;)
cgrd_09



カレントフレーム用のスライダーを作る;Python
さて、お次はPythonを使ってカレントフレームが変更できる横長のスライダー を作ってみましよう。
幾つか種類あり、今回は整数値の表示部とスライダーのあるintSliderGrpでいきます。

intSlider 整数のスライダーです。
intSliderGrp ラベル、整数値のフィールド、整数値のスライダーを組み合わせたものです。
floatSlider 実数のスライダーです。
floatSliderGrp ラベル、実数値のフィールド、実数値のスライダーを組み合わせたものです。

floatSliderButtonGrp オプションのボタンとシンボルボタンを使って、浮動小数点スライダのコンポーネントを作成します。
attrFieldSliderGrp ラベル、実数値のフィールド、実数値のスライダーを組み合わせたもので
ノードのアトリビュートに接続してリアルタイムに値を変更できます。
hudSlider
hudSliderButton
3D ビューポート上にある 2D の非アクティブオーバーレイプレーンに配置される
ヘッドアップディスプレイ(HUD)スライダボタンコントロールを作成します。

シーンの現在のカレントフレーム値を設定するのはcurrentTime(フレーム数, edit=True)で出来ます。
スライダーが表示される時、シーンの最小値/最大値をplaybackOptionsコマンドのminTime/maxTimeで取得します。

cmds.window()/cmds.showWindow()内のものがウィンド表示されるものです。
rowLayoutコマンドは水平一列に配置できるレイアウトを作成するのですが、
adjustableColumn オプションで横に引き伸ばした時にスライダーも伸びるように設定出来ます。

スライダーの初期値=value は、作成時のシーンのフレーム数currentTime( query=True )を取得しています。
このスクリプトで最もラブリーな書き方になっている所は、
スライダーが変化したり=changeCommand、ドラッグされたり=dragCommand した時に、
スライダー自身の値が取得されるように使っている関数の仕組み lambda x:set_current_frame(int(x))という部分です。(ブレイクスルーでしょ!!)

import maya.cmds as cmds

def set_current_frame(current_frame):
    cmds.currentTime(current_frame, edit=True)

current_minimum_frame = cmds.playbackOptions(query=True, minTime=True)
current_maxmum_frame = cmds.playbackOptions(query=True, maxTime=True)

cmds.window(title='CurrentFrameSlider')
cmds.rowLayout(adjustableColumn=True)
cmds.intSliderGrp(
    'TimeSlider',
    label='CurrentFrame',
    field=True,
    min=current_minimum_frame,
    max=current_maxmum_frame,
    step=1,
    value=cmds.currentTime( query=True ),
    changeCommand=lambda x:set_current_frame(int(x)),
    dragCommand=lambda x:set_current_frame(int(x))
)
cmds.showWindow()

実行すると、下図のように、スライドすると現在のフレーム値が連動し、数値を入力しても現在のフレーム値になります。
ただし、シーンの最小/最大フレーム値を変化させてもスライダーはリアルタイムには更新しませんので、なんのことはない、再表示してください。
ちなみに、何枚でも表示できます。
cgrd_10
さて、ここでのキモだったlambda式 は、ラジオボタンなど多くのところで素晴らしい働きをしてくれますので、ぜひマスターしてください。
この章の締めくくる前に、少しこの lambda式 を解説してみたいと思います。


lambda式 無名関数 ;Pythonhttp://docs.python.jp/2/reference/expressions.html#lambda
defステートメントのように関数を作成するが、lambdaは 「式」 を記述する。
defステートメントは関数の名前を記述するが、lambda式 では意図的に代入しない限り名前が無い、無名関数生成式。
defでは記述できないような場所にlambda式 は書くことが出来、とっても便利 です ^ _ ^ ;;

func = lambda x: x * 2

って実行した後に、

func(5)

って実行すると、

# 結果: 10 # 

という実行結果が得られるのは、def で書くと、以下と同じことだと解ります。

def func(x):
    return x * 2

func(5)

# 結果: 10 # 


そうだと気が付けば、下記のように defと同じく キーワード引数デフォルト値 が設定出来ます。

func = lambda x,y=5,z=10: x + y + z

func(x=1)

# 結果: 16 #

上記intSliderGrp lambda式 を用いた良い点とは、スライダーの値を取得する為に
cmds.intSliderGrp('TimeSlider', q=True, value=True) というコマンドを再度実行しなくて済むと同時に
同一スクリプトから複数パネルを作成した場合に スライダーの名前(上例では'TimeSlider')が重複して起こる誤動作を防ぎます。


そして、ここから面白い部分の紹介ですが、 lambda式 は、受け渡すデータが 式 となんら関係の無いものでもOKなんです。
最初の式を見てから、下の式を見て、結果を予想してください。

func = lambda y: 15

func(5)

はい、# 結果: 15 # です。
つまり、何を投げても 15 が返って来るってことは、投げたデータ値とは全く関連性の無い値を返している、ということになります。
例えば、ボタンを押したというコマンドが来たら、決まった値を返すって時に便利ですね。(ボタンを押した事と、返す値には関連性がありません)
次章で、ラジオボタンの作成 を紹介するのですが、先に lambda式 部分を解説しますと、

onCommand1=lambda x:radio_button_grp_key(1),
onCommand2=lambda x:radio_button_grp_key(2),
onCommand3=lambda x:radio_button_grp_key(3))

と書かれている部分は、ラジオのボタン1を押すと という数値を返す、ボタン2 を、ボタン3 を返すとなります。



ラジオボタンを作る;Python
さて、お次は Python を使って3択のラジオボタン を作ってみましよう。
同時に、rowLayout を使って、テキスト入力付きボタン を横一列に配置します。

import maya.cmds as cmds

radio_button_no = 1

def radio_button_grp_key(value):
    global radio_button_no
    radio_button_no = value
    print "Selected Radio Button is " + str(radio_button_no)

def button_push(value):
    bb = 1
    if value is not 1:
        bb = radio_button_no
    print "Pushed Button is " + str(bb)

cmds.window( width=150 )

cmds.rowLayout(numberOfColumns=2,columnAttach=[(1, 'both', 0), (2, 'both', 0)])

cmds.radioButtonGrp(
    'KeyInOutSelect',
    labelArray3=['Both', 'In', 'Out'],
    numberOfRadioButtons=3,select=1,
    onCommand1=lambda x:radio_button_grp_key(1),
    onCommand2=lambda x:radio_button_grp_key(2),
    onCommand3=lambda x:radio_button_grp_key(3)
)

cmds.textFieldButtonGrp(
    'AngleG',
    text='0',
    buttonLabel='GetAngle',
    buttonCommand=lambda *args:button_push(int(2))
)

cmds.showWindow()

上記をスクリプトエディタで実行してみると、下図のようなウィンドが表示され、3択と共にプリントが変わり、また3択毎にボタンのプリントも変わります。
cgrd_11
上記記述で面白いのは、グローバル値で radio_button_no = 1 と初期値化していて、ボタンの値が来たらグローバル値を置き換えています。
また、ボタンも最初の値は1なんですが、ボタンを押すと必ず2が来るので、そしたらグローバルのradio_button_noの値をプリントしています。
一見良さげなんですが、問題は例えば2つウィンドを作って、動作してみると、ラジオボタンを必ず押した後では良いのですが、
押さないで異なるウィンドのボタンを押すと、その前に選択したウィンドーのラジオボタンの値をプリントしてしまいます。
まー、使えなくは無いですが、ちょっと誤動作が残っている感じです。
先に進みます。
もう少しGUIを増やしつつ、色々と準備していきます。

import maya.cmds as cmds

radio_button_no = 1

def radio_button_grp_key(value):
    global radio_button_no
    radio_button_no = value
    print "Selected Radio Button is " + str(radio_button_no)

def start_tan(tangent_value):
    print tangent_value
    cmds.keyTangent( lock=False )
    if radio_button_no == 1:
        cmds.keyTangent(edit=True,absolute=True, inAngle=tangent_value, outAngle=tangent_value )
    elif radio_button_no == 2:
        cmds.keyTangent(edit=True,absolute=True, inAngle=tangent_value )
    elif radio_button_no == 3:
        cmds.keyTangent(edit=True,absolute=True, outAngle=tangent_value )
    cmds.keyTangent( lock=True )

def get_angle_from_selected_curve(*args):
    print "Get Angle From Selected Curve"

def set_angle_to_selected_index(*args):
    print "Set Angle From textFieldButtonGrp"

cmds.window( width=150 )

cmds.rowLayout(
   numberOfColumns=4,
   columnAttach=[(1, 'both', 0), (2, 'both', 0),(3, 'both', 0),(4, 'both', 0)]
)

cmds.radioButtonGrp(
    'KeyInOutSelect',
    labelArray3=['Both', 'In', 'Out'],
    numberOfRadioButtons=3,select=1,
    onCommand1=lambda x:radio_button_grp_key(1),
    onCommand2=lambda x:radio_button_grp_key(2),
    onCommand3=lambda x:radio_button_grp_key(3)
)

cmds.floatSliderGrp(
    'KeyTangentAngle',
    label='Key Tangent Angle',
    value=0,
    field=True,
    minValue=-90,
    maxValue=90,
    sliderStep=1.0,
    changeCommand=lambda x:start_tan(x),
    dragCommand=lambda x:start_tan(x)
)

cmds.textFieldButtonGrp(
    'AngleG',
    text='0',
    buttonLabel='GetAngle',
    buttonCommand=get_angle_from_selected_curve
)

cmds.button(
    'AngleS',
    label='SetAngle',
    command=set_angle_to_selected_index
)

cmds.showWindow()

上記をスクリプトエディタで実行してみると、下図のようなウィンドが表示され、3択と共にプリントが変わり、スライダーと共に数値が変化します。
この値が、接線値として、ラジオボタンの設定によって Both/In/Out に設定されれ良い、ということになります。
Get/Set のボタンは、今はプリントが表示され、動作は確認出来ます。ここは、Getの時は Both/In/Out に設定に従って選択した頂点の接線値を取得し、
Setはその値をBoth/In/Outに設定に従って選択した頂点に設定します。
cgrd_12
さーて、いよいよ、GraphEditorにくっ付けたり、カーブからの接線値の所得、そして複数パネル表示時の誤動作をどう解決するかになります。



カスタム・グラフエディタ;Python
で、 GraphEditor と追加されたGUIはどうやって結合したかというと、なんということはなく、
単に列挙しただけです。 GraphEditor 自身がパネルレイアウトなのです。Version 3

#coding: UTF-8
import maya.cmds as cmds
import maya.mel as mel 

<中略>

def custom_graph_editor(*args):

    grp_panel_no_a = str( len(cmds.getPanel( scriptType='graphEditor' )) + 1 )

    current_minimum_frame = cmds.playbackOptions(query=True, minTime=True)
    current_maxmum_frame = cmds.playbackOptions(query=True, maxTime=True)

    panel_name = "GraphEditor" + grp_panel_no_a
    mel.eval('string $panel_name = "%s"' % panel_name)
    mel.eval('tearOffPanel $panel_name "graphEditor" true;')

    cmds.columnLayout( adjustableColumn=True )
    cmds.rowLayout(
        numberOfColumns=9,
        adjustableColumn=4
    )

    cmds.text('anim')

    cmds.button('anim_save',label='Save',command=anim_save)

    cmds.button('anim_load',label='Load',command=anim_load)

    cmds.intSliderGrp(
        'TimeSlider'+ grp_panel_no_a,
        label='CurrentFrame',
        field=True,
        min=current_minimum_frame,
        max=current_maxmum_frame,
        step=1,
        value=cmds.currentTime( query=True ),
        changeCommand=lambda x:set_current_frame(int(x)),
        dragCommand=lambda x:set_current_frame(int(x)),
        columnWidth=[2,50]
    )

    cmds.button(
        'PreFrame',
        label='<',
        command=set_pre_current_frame
    )

    cmds.button(
        'NowFrame',
        label='[]',
        command=set_now_current_frame
    )

    cmds.button(
        'NextFrame',
        label='>',
        command=set_next_current_frame
    )

    cmds.text('Step')

    cmds.intField(
        'nStep'+ grp_panel_no_a,
        width=30,
        value=1
    )

    cmds.setParent('..')
    cmds.rowLayout(
        numberOfColumns=4,
    )

    cmds.radioButtonGrp(
        'KeyInOutSelect',
        labelArray3=['Both', 'In', 'Out'],
        numberOfRadioButtons=3,
        select=1,
        onCommand1=lambda x:radio_button_grp_key(1),
        onCommand2=lambda x:radio_button_grp_key(2),
        onCommand3=lambda x:radio_button_grp_key(3)
    )

    cmds.floatSliderGrp(
        'KeyTangentAngle',
        label='Key Tangent Angle',
        value=0,
        field=True,
        minValue=-90,
        maxValue=90,
        sliderStep=1.0,
        changeCommand=lambda x:start_tan(x),
        dragCommand=lambda x:start_tan(x)
    )

    cmds.textFieldButtonGrp(
        'AngleG'+ grp_panel_no_a,
        text='0',
        buttonLabel='GetAngle',
        buttonCommand=get_angle_from_selected_curve,
        columnWidth=[1,100]
    )

    cmds.button(
        'AngleS',
        label='SetAngle',
        command=set_angle_to_selected_index
    )


ただし、パネル > パネルで作成したパネル名が残っていますが、そこからパネルを表示させても追加したGUIは含まれていません。
単に新しくパネルを作成してください。

そして、複数枚パネルを作成すると値が新しい方になってしまうグローバル関数の使用をやめて
各パネル毎に値を取って置く方法として Mayaの optionVar コマンドを利用しています。

optionVarhttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/optionVar.html

optionVar 変数は、userPrefs.mel にプリファレンスの一部として Maya のさまざまな呼び出しに存在する変数を保存します。
cmds.optionVar( list=True ) とするとリストを表示するのですが、既にたくさんの変数が格納されているものなのです。
intValue(iv) [string, int] (文字列から数値を取り出す)stringValue(sv) [string, string] (文字列から文字列を取り出す)で覚えさせます。

import maya.cmds as cmds
cmds.optionVar( intValue=('Toranpu_Syurui', 4))

cmds.optionVar( q='Toranpu_Syurui' )

# 結果: 4 # 

cmds.optionVar( exists='Toranpu_Syurui' )

# 結果: True # 

cmds.optionVar( remove='Toranpu_Syurui' )
cmds.optionVar( exists='Toranpu_Syurui' )
# 結果: False # 


複数ある記述としてgrp_panel_no_b = str( cmds.getPanel( withFocus=True )[11:] )がありますが、
これはアクティブなこのパネルの名前から例えばgraphEditor2の場合、前から11番目の文字から最後の文字、つまり 2 だけを取り出し
textFieldButtonGrp のラベル名をこのパネル独自の前としているだけです。(他のパネルと異なるラベル名にすることによって誤動作を防いでいます)

print 'graphEditor2'[11:] 

# 結果: 2 # 

そのほかは、選択したアニメーション・カーブの頂点からタンジェント値を求めるところは、少しややこしいことになっていますね。
設定する方は簡単だったりします。



Version 2;Step数の設定と < 、[]、 >
スライドバーの横に、3つのボタン < 、 []、 > Step数入力欄が増設されました!!
cgrd_21
Step数はデフォルトではが入っています。このままですと、[1フレーム前]、[現在]、[1フレーム次]に各ボタンを押すと進みます。
もし、シーンの再生範囲を 1~100 にしていると、100 の次は に戻ってくれます(逆も)。ループアニメーションの作成に便利です。
[現在] ボタンは、複数のカスタムグラフエディターや下のシーンのタイムスライダーでカレントフレームを移動した場合、
残念ながら各スライダーと左の数値は連動しないので、この [現在] ボタンを押すことで今のカレントフレームになる、というものです。

Step数は変更することが出来、とか とか設定すると、その分を移動するボタンになります。
つまり、Step数2で1フレーム目から > ボタンを押すと、1、3、5、7、9のフレームに移動します。
これはCGアニメなど、毎フレーム再生しないアニメーションを見る時に便利かも知れません。
しかも、このカスタムグラフエディタは複数表示して置けるので、
Step数の異なるカスタムグラフエディタを幾つか用意してコマ送りして見る・・・なんてことが出来ます。
尚、Step数以外の時、エンドフレームを超えると最小になる、というのは仕様です(逆も)。(つまり1~100の時、98からStep3だと、開始フレームに行きます。)


Python記述として面白い 追加部分 はGUI のところで、
横一列に並ぶcmds.rowLayout( )による スライダーやボタンなどが 2列 ある場合は、その上にcmds.columnLayout( )が必要で、
2列目頭にはcmds.setParent('..')が必要です。


import maya.cmds as cmds

def p_step(*args):
    o_step = cmds.intField('nStep',query=True, value=True)
    print o_step

cmds.window()
cmds.columnLayout( adjustableColumn=True )
cmds.rowLayout( numberOfColumns=3, adjustableColumn=2)
cmds.button('PStep',label='PStep',command=p_step)
cmds.text('Step')
cmds.intField('nStep',width=30,value=1)
cmds.setParent('..')
cmds.rowLayout( numberOfColumns=3, adjustableColumn=2)
cmds.button('A',label='A')
cmds.text('OtherStep')
cmds.intField('nOtherStep',width=30,value=2)
cmds.showWindow()
cgrd_22



Version 3;animの保存と読み込み
カスタムグラフエディタから直接アニメーションデータの保存形式である .anim保存読み込みの出来るボタンが増設されました!!
cgrd_31
既存のグラフエディタには、表示しているファンクションカーブ、つまりキーアニメーションを 保存/読み込み できる項目はなく、
通常はMayaのメイン画面、ファイル > 選択項目の書き出し...とファイル > 読み込みで行います。
しかも、この .anim ファイル形式を扱えるようにする animImportExport.mll というプラグイン
インストール時に用意はされているが、デフォルトでは ロードされていません(.animファイル形式が無い・・・と書き出そうとした時気が付くとイライラ・・・)
そこで、せっかくワークフローを改善しようとして用意してるカスタムグラフエディタ な訳ですので
直接保存と読み込み が出来るようにPythonで作ってみました。
animImportExport.mll プラグインもロードしてなかったら、自動的にロードしてくれるように組みましょう。(ふいにプラグインがロードされていなかったり・・を防ぎます)

Python 解説
まずは、animImportExport.mll プラグインがロードされているかの判別はpluginInfo コマンドを使用して出来ます。

pluginInfohttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/pluginInfo.html

print cmds.pluginInfo("animImportExport",q=True,loaded=True)

# 結果: False # 
cgrd_32

ロードされてなかったら False が返って来ることが解ったので、 if 文で not を使って逆の意味にしてロードしてなかったらロードしろ・・・と記述すれば完了。

if not cmds.pluginInfo("animImportExport",q=True,loaded=True):
    cmds.loadPlugin( 'animImportExport' )
    cmds.pluginInfo( 'animImportExport', edit=True, autoload=True )


次は、Save/Load用のディレクトリーとファイル名を指定するブラウザとその結果を実際に読み書きするコマンドはfileDialog2fileを使います。
この2つはかなり便利で奥が深いコマンドなので、知っていると、きっと後々役に立ちます。

fileDialog2http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/fileDialog2.html
file http://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/file.html


fileDialog2はそのオプションを見ると、fileModeの番号を変えると Save なのか、Load なのか、を指定できることが解ります。簡単!!
fileDialog2(fileMode=0 保存
fileDialog2(fileMode=1 開く

fileもオプションが沢山ある中で、保存の時はexportSelectedAnim=True読み込みの時はi=Trueで行けそうなのは解るとして、
typeってなんだ、と気がつきます。こういう時は、実際に動作する既存方法のログを取ってみるとヒントが出て来ます。
Mayaのメイン画面、ファイル > 選択項目の書き出し...とファイル > 読み込みを実行して(下のMelコマンドでも動くよ)表示される沢山のログの中から

ExportSelection;

file -force -options "precision=8;intValue=17;nodeNames=1;
verboseUnits=0;whichRange=1;range=0:10;options=keys;hierarchy=none;controlPoints=0;
shapes=1;helpPictures=0;useChannelBox=0;copyKeyCmd=-animation 
objects -option keys -hierarchy none -controlPoints 0 -shape 1 " -typ "animExport" -pr -es "F:/user_maya/Maya2015/scenes/aaa.anim";
// 結果: F:/user_maya/Maya2015/scenes/aaa.anim // 

-----------------------------------------------------------------------------------
Import;

file -import -type "animImport"  -ignoreVersion -ra true -mergeNamespacesOnClash false -namespace "keyanm" 
-options ";targetTime=4;copies=1;option=replace;pictures=0;connect=0;" 
-pr -loadReferenceDepth "all" "F:/user_maya/Maya2015/scenes/keyanm.anim";
fileCmdCallback;
xgmBeforeImportCB({"3"});
import maya.OpenMaya;maya.OpenMaya.MFileIO().beforeImportFilename()
# F:/user_maya/Maya2015/scenes/keyanm.anim # 

Melのfileオプションに-typ/-typeがあり、保存の時は"animExport"、読み込みの時は"animImport"だと解ります。
そうしたら、Pythonでこんな風にすればSave/Load出来ちゃいます。

[Save]
import maya.cmds as cmds
singleFilter = 'animExport(*.anim)'
filename = cmds.fileDialog2(fileMode=0, fileFilter=singleFilter, dialogStyle=2, caption='Save Key Animaton')
cmds.file(filename[0],force=True, exportSelectedAnim=True, type='animExport')

[Load]
import maya.cmds as cmds
singleFilter = 'animImport(*.anim)'
filename = cmds.fileDialog2(fileMode=1, fileFilter=singleFilter, dialogStyle=2, caption='Load Key Animaton')
cmds.file(filename[0],force=True, i=True, type='animImport')


Version 3;幾つかの不具合の解消
カスタムグラフエディタでアニメーションのカーブは表示してはいるのに、どの頂点も選択していない場合に
タンジェント角度スライダーを動かしたり、タンジェント角度を取得/設定ボタンを押したりした時に思わぬ誤動作が存在していました。
ここを修正すべく、カーブを選択していなかったら何も動作しないようにtry: except:文を設定しました。

例外を処理するhttp://docs.python.jp/2/tutorial/errors.html?highlight=try%20except#tut-handling

try: except:文はエラーが発生したら何々する、ということが出来るので、
ここでの場合、カーブのインデックスを選択していなかったらエラー、にしてみました。

try:
    cmds.keyframe( query=True, selected=True, name=True)[0]

except:
    print 'Animation Key is Not Seleced'




Version 4;階層アニメーション対応
カスタムグラフエディタから直接保存読み込みが出来る anim の形式を、階層アニメーション対応にしました。
cgrd_40
Mayaにおいて、通常の操作で階層構造のアニメーションを出力するには、
メイン画面から、ファイル > 選択項目の書き出し...の所で、ファイルタイプ特有のオプション > 階層 > 下位に設定するがあります。
そうして出力した anim ファイルでないと、読み込みの時、エラー表示が出て、読み込めないのです。
この設定で出力した時のログを見ると、上記のLog と異なる部分を発見できます。
その部分をそっくり Python で認識する記述に変更しただけになります。(案の定、file は奥が深いってところです。)

[Save] (↓ options= の行は本当は1行です)
import maya.cmds as cmds
singleFilter = 'animExport(*.anim)'
filename = cmds.fileDialog2(fileMode=0, fileFilter=singleFilter, dialogStyle=2, caption='Save Key Animaton')
cmds.file(
    filename[0],
    type='animExport',
    exportSelected=True,
    options='options=keys;hierarchy=below;controlPoints=0;shapes=1;
        copyKeyCmd=-animation objects -option keys -hierarchy below -controlPoints 0 -shape 1'
    )

[Load]
import maya.cmds as cmds
singleFilter = 'animImport(*.anim)'
filename = cmds.fileDialog2(fileMode=1, fileFilter=singleFilter, dialogStyle=2, caption='Load Key Animaton')
cmds.file(
    filename[0],
    type='animImport',
    i=True,
    ignoreVersion=True,
    renameAll=True,
    options='targetTime=4;option=replace;connect=0'
    )

なんと長いオプションなのでしょうか。しかも他の記述方法ではうまくいきませんでした。



Version 5;FBXアニメーション対応
カスタムグラフエディタから直接FBXアニメーションの形式を 保存 読み込み が出来るボタンと階層の選択 が出来るボタンを追加しました。
cgrd_50
V4 にて対応した anim ファイルは、名前に基づかないので、例えば末端のユレ骨のアニメーションを他の骨にコピペする時などには便利です。
ですが、その作成手順の違い等によって内部の関連性が異なるとアニメーションが壊れてしまう、ということになります。
たとえ同じ構造のように見えてもシーンに読み込だ階層構造体に animファイルを Load すると壊れてしまう、ということが発生します。

そこで、今度は逆にガッチリ名前に基づくことになるのですが、FBX形式のアニメーションの利用が考えられます。
FBXネームスペースまで含んだ名前を見るので、名前の管理はしっかりしないといけません。
そのかわり、今度は、上図のように、あとから頭に骨を追加して内部の関連性が変化したとしても、
名前で解決しているので、ちゃんとアニメーションの読み込みに成功します。

ちなみに、anim ファイルも FBX ファイルも [Save][Load] は、
アニメーションを保存/読み込みたい一番上のボーンを選択するだけでそれ以下の階層もその対象になります。

今回追加した[SelectHierarchy] 階層の選択ボタンは、
選択したボーン以下全てのアニメーションをカスタム・グラフエディタに表示したい とか 削除したい とかいう場合に便利です。


Python 解説
さて、今回も FBXPythonUIなしExportImport を行えるように記述する訳ですが、
FBX関連の書き方が ユーザーガイドに載っています、が、MELなんですね。
Maya MEL スクリプティングデータ交換 > ファイルトランスレーションに FBX を使用する > Maya FBX Plug-in >
http://download.autodesk.com/global/docs/maya2014/ja_jp/?url=files/GUID-F48E3B78-3E56-4869-9914-CE0FAB6E3116.htm,topicNumber=d30e149705

なので Pythonからはmel.eval で記述することになります。

まずは、fbxmaya.mll プラグインがロードされているかの判別をまたpluginInfo コマンドを使用して行います。
更に、そもそもプラグイン が無かった場合を想定して例外を処理するtry: except:文にしておきます。

try:
    if not cmds.pluginInfo("fbxmaya",q=True,loaded=True):
        cmds.loadPlugin( 'fbxmaya' )
        cmds.pluginInfo( 'fbxmaya', edit=True, autoload=True )
except:
    print 'FBX plugin is not installed'


今回もログを参考にしていますが、file コマンドで使用する type が、
出力する時は 'FBX export' なのに、入力の時は 'FBX' なのは、え? ですね。そして,なんと長いオプションなのでしょうか。
また、if fbx_filename: に記述を改善しているのは、
ブラウザまでは表示したのに [キャンセル] した場合に、エラー表示になるのを防いでいます。

FBXExport時のMelオプションとしては
ユニット単位を cm にしています。(inch にしたい場合は変えてください)
mel.eval('FBXExportConvertUnitString "cm"')

[Save](↓options=の行は本当は1行です)
import maya.cmds as cmds
import maya.mel as mel
singleFilter = 'FBX(*.fbx)'
fbx_filename = cmds.fileDialog2(
    fileMode=0, 
    fileFilter=singleFilter,
    dialogStyle=2,
    caption='Save FBX Animaton'
    )
if fbx_filename:
    mel.eval('FBXExportConvertUnitString "cm"')
    cmds.file(fbx_filename[0],
        force=True,
        type='FBX export',
        preserveReferences=True,
        exportSelected=True,
        options='verboseUnits=0;options=keys;hierarchy=none;controlPoints=0;shapes=1;
            useChannelBox=0;copyKeyCmd=-animation objects -option keys -hierarchy none -controlPoints 0 -shape 1'
        )


FBXImport時のMelオプションとしては
ユニット単位を cm にしています。(inch にしたい場合は変えてください)
Merge = シーンに同等のものがないノードは、すべて削除されます。 になっています。
mel.eval('FBXImportSetLockedAttribute -v true')
mel.eval('FBXImportMode -v merge')
mel.eval('FBXImportConvertUnitString "cm"')

[Load]
import maya.cmds as cmds
import maya.mel as mel
selected_obj = cmds.ls( sl=True )[0]

singleFilter = 'FBX(*.fbx)'
fbx_filename = cmds.fileDialog2(
    fileMode=1,
    fileFilter=singleFilter,
    dialogStyle=2,
    caption='Load FBX Animaton'
    )
if fbx_filename:
    mel.eval('FBXImportSetLockedAttribute -v true')
    mel.eval('FBXImportMode -v merge')
    mel.eval('FBXImportConvertUnitString "cm"')
    cmds.file(
        fbx_filename[0],
        type='FBX',
        i=True,
        ignoreVersion=True
        )
cmds.select(selected_obj, r=True )




Version 6;カーブから選択対応
カスタムグラフエディタ上に表紙されている任意のアニメーションカーブを選択して[SelectFromCurve]を押すと、
そのアニメーションが付いているオブジェクトのみの表示になります。
cgrd_61
[SelectHierarchy]階層の選択ボタンで、キャラクターの階層全てを選択すると、全てのアニメーションカーブがカスタムグラフエディタ 上に表示されます。
表示 > アトリビュート > [レ]移動などにチェックを入れると表示するカーブの種類のフィルタリングが出来ます。
例えばスケースアニメーションが付いているのかとか検索できます。
その後、実際にそのキーアニメーションがどのJointに設定されているのかを表示するのにこの[SelectFromCurve]は役立ちます。
cgrd_62
このための追加スクリプト記述はとても簡単です。
何か選択されているかを if 文で検索し、選択されていれば、そのカーブ名から取得して、そのオブジェクト名部分のみの名前を選択しています。
たった6行でした。

def select_objfrom_curve(*args):
    if not cmds.ls( sl=True ):
        print 'No Curve is selected'
    else:
        selected_f_curve = cmds.keyframe( query=True, lastSelected=True, name=True)[0]
        cmds.select( (selected_f_curve[:selected_f_curve.index('_')]) , r=True)




Version 10 (改6,9); 新GUI、新機能説明
突然バージョンが一桁上がりました。それは、もう以前よりもはるかに完成度の高いツールに仕上がっていったからです。
ここまで到達出来たのも、たくさんの要望と協力があってからこそです。
まずは一気に新機能を説明していきます。
CustomGraphEditor_V10_02
■タイムラインもうMayaにあるそのものになりました。シーンにあるものとシンクロして動きます。
再生範囲もMayaメイン画面下のものを変更するとすぐに反映されます。
■再生ボタン等もうMayにあるものと同じボタンなので、もう機能説明も要りません。
Step数の指定で、|< >| ボタンはそのStep数に分コマ飛ばしが出来ます。
■[ShowSpeadsheet] 選択したカーブのスプレッドシートを、アトリビュートのコピー画面で表示してくれます。自動的に展開します。
コピー画面なので、何枚でも表示でき、ずっと表示したままでいてくれます。
■CurveIndexInfo 選択したキーのIn側Out側の AngleWeight を表示してくれます。
[Set] ボタン は任意に選択したキーに今表示している値を設定することが出来ます。
/CustomGraphEditor_V10_01
■移動/回転/スケール の X/Y/Z と V ボタン
左マウスクリック;これらはまずは、そのカーブを選択するボタンです。
中マウスクリック;カレントフレームにキーが合った場合に、このカーブの キー(頂点) を選択します。
[ V10(9改)仕様変更 ] 中マウスクリック複数ノードを選択して表示している場合、同一のカーブ全てを選択状態にします。例全部の移動X。
Shift + 中マウスクリック;そのカーブのフィルタリングをします。
CustomGraphEditor_V10_9_00
| > と > | ボタン
左マウスクリック;タイムライン上にキーのある、前/次のフレームにカレントフレームが動きます。
中マウスクリック;選択しているそのカーブの前/次のキー(頂点)を選択します。
Shift + 中マウスクリック;カレントフレームから見て前/次のキー(頂点)を選択します。




Python 解説

さて、今回一番大きかったのは、Mayaのメイン画面下にあるタイムラインと同じものをスライダーの代わりに入れ組めたことでしょうか。
これは、なんてことはない、コマンドが用意されていたのです。それは、timePortです。

timePorthttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/timePort.html

ただし、例として載っている記述だけでは少し足りなくて、きちんとサイズを指定してあけると表示します。

cmds.timePort(enableBackground=True,bgc=[0.2,0.2,0.2],width=500,height=20 )

タイムラインの横の再生等のボタンとコマンドを調べてみます。
再生ボタンを押してスクリプトエディタの履歴に出て来る(ヒストリ > すべてのコマンドのエコーを有効にする)のはplayButtonForwardです。
これがどこのMelに書かれているのかwhatIsで調べてみると、

whatIs playButtonForward
// 結果: Mel procedure found in: C:/Program Files/Autodesk/Maya2015/scripts/others/timeSlider.mel // 

と出て来ます。timeSlider.melをちょっと覗いてみるとこんなコマンド達が再生ボタン回りにあることが分ります。

// playButtonStart, 最初のフレームへ
// playButtonStepBackward, ステップ数前フレーム
// playButtonBackward
// playButtonStop,
// playButtonRecord,
// playButtonForward 再生 > 
// playButtonStepForward, ステップ数次フレーム
// playButtonEnd 最後のフレームへ

global proc playButtonForward() {
という文が書かれているところを見ると、symbolButtontimeplay.xpmという画像(ICON)で表示してplayButtonForwardコマンドを実行させている
ってわかってきます。画像(ICON)達も調べると

Mayaのデフォルトアイコン
TIMEEND.XPM
TIMEFWD.XPM
TIMENEXT.XPM
TIMEPLAY.XPM キーのある次のフレームへ timenext
TIMEPREV.XPM キーのある前のフレームへ timeprev
TIMEREV.XPM
TIMEREW.XPM
TIMESTART.XPM
TIMESTOP.XPM

あとは、Step数 を入れるとコマ飛ばしになるようにもしたかったのでplayButtonStepBackwardplayButtonStepForwardだけは値が入るように
独自にPython化してみました。
この辺の周りをwindow にしてPython化するとこんなスクリプトが作成出来ました。

再生時play_forwardは、そのMelコマンドをそのまま使っても良かったのですが、再生すると停止のICONに変化する工夫を入れてみました。
(でも、後で気が付いたのは、Mayaのメイン画面で再生して、カスタムグラフエディタで停止するとアイコンが逆になりますね。もう一回やって戻してください。)
CustomGraphEditor_V10_03
def step_backward(*args):
    by = cmds.intField('nStep',query=True,value=True)
    curr = cmds.currentTime( query=True )
    min = cmds.playbackOptions(query=True,minTime=True)
    max = cmds.playbackOptions(query=True,maxTime=True)

    if curr != min and (curr - by ) >= min and (curr - by) < max:
        cmds.currentTime(curr - by,edit=True)
    else:
        cmds.currentTime(max,edit=True)

def step_forward(*args):
    by = cmds.intField('nStep',query=True,value=True)
    curr = cmds.currentTime( query=True )
    min = cmds.playbackOptions(query=True,minTime=True)
    max = cmds.playbackOptions(query=True,maxTime=True)

    if curr != max and (curr + by ) <= max and (curr + by) > min:
        cmds.currentTime(curr + by,edit=True)
    else:
        cmds.currentTime(min,edit=True)

def play_forward(*args):
    if cmds.symbolButton('play_forward',q=True,image=True) == 'timeplay.xpm':
        cmds.symbolButton('play_forward',edit=True,image='timestop.xpm')
    elif cmds.symbolButton('play_forward',q=True,image=True) == 'timestop.xpm':
        cmds.symbolButton('play_forward',edit=True,image='timeplay.xpm')
    mel.eval('playButtonForward;')

def current_time_port( dragControl, dropControl, messages, x, y, dragType ): 
    print 'dropControl ='+ dropControl

cmds.window( w=500, h=35 )
cmds.columnLayout()
cmds.rowLayout(numberOfColumns=8,adjustableColumn=1)
cmds.timePort(enableBackground=True,bgc=[0.2,0.2,0.2],width=790,height=20,dropCallback=current_time_port)
cmds.symbolButton( image='timerew.xpm',command="mel.eval('playButtonStart;')" )
cmds.symbolButton( image='timeend.xpm',command=step_backward)
cmds.symbolButton('play_forward',image='timeplay.xpm',command=play_forward)
cmds.symbolButton( image='timestart.xpm',command=step_forward)
cmds.symbolButton( image='timefwd.xpm',command="mel.eval('playButtonEnd;')" )
cmds.text('Step')
cmds.intField('nStep',width=30,value=1)

cmds.showWindow() 


ShowSpeadsheet の表示部分も改めて知ることが多かったです。
またMel解析です。
グラフエディタで1本カーブを選択し、カーブ > スプレッドシート.. を選択するとスプレッドシートが展開するのですが、
ログを見ると、OpenAnimSpreadsheet graphEditor2FromOutliner;と出ます。
これも追うと、
whatIs OpenAnimSpreadsheet;
// 結果: Mel procedure found in: C:/Program Files/Autodesk/Maya2015/scripts/others/loadAnimMenuLibrary.mel //
と出ます。ではこの中で OpenAnimSpreadsheetはどんなスクリプトかと追うと、
showEditor$animCurvesに気が付きます。
すると、こういう風に組めると思います。

string $animCurves_r[];
$animCurves_r = `keyframe -query -name`;
showEditor $animCurves_r[0];
CustomGraphEditor_V10_04
次に。このコピーを作成して、元を消すって言うことをしてみました。(元のアトリビュートエディタを残しても良かったのですが)
string $animCurves_r[];
$animCurves_r = `keyframe -query -name`;
showEditor $animCurves_r[0];
commitAENotes($gAECurrentTab);copyAEWindow;
commitAENotes($gAECurrentTab);ToggleAttributeEditor;


で、最後に□スプレッドシートの展開という所にチェックを入れておきたかったのですが、
ここが最初分りませんでした。ここの使用はMaya2013.5からコマンドが変化しています。
ここは気付かされたのは他の方のご意見からでした(情報ありがとうございます)。
SRTのカーブを選択すると、その種類によってノードタイプが異なることを発見したのです。(え、知ってました・・・)
CustomGraphEditor_V10_05
スプレッドシートの選択カーブの左にあります通り、移動;CurveTL回転;CurveTAスケール;CurveTUとなっています。
ついでにウェイトやカスタムアトリビュートのカーブはCurveTUとなっているようです。

o_selected = cmds.keyframe( query=True, selected=True, name=True)[0]
print o_selected
print cmds.nodeType(o_selected)
 結果 pSphere1_translateX > animCurveTL
 結果 pSphere1_rotateX     > animCurveTA
 結果 pSphere1_scaleX      > animCurveTU


そうするとノードタイプに応じてチェックボックスを有効にして展開したシートを表示するというスクリプトを書きます。(Mayaのバージョン分岐もあります。)
ほとんどMelですね。

def show_spreadsheet(*args):
    try:
        o_sel_curve = cmds.keyframe( query=True, selected=True, name=True)[0]
        if cmds.about(api=True) >= 201350:
            mel.eval('string $animCurves_r[];')
            mel.eval('$animCurves_r = `keyframe -query -name`;')
            mel.eval('showEditor $animCurves_r[0];')
            mel.eval('commitAENotes($gAECurrentTab);copyAEWindow;')
            mel.eval('commitAENotes($gAECurrentTab);ToggleAttributeEditor;')
            
            if cmds.nodeType(o_sel_curve) == 'animCurveTL':
                mel.eval('checkBoxGrp -edit -value1 1 expandChkBoxGrpanimCurveTL;')
                mel.eval('keyframeOutliner -edit -display "wide" keyOutlineranimCurveTL;')
            elif cmds.nodeType(o_sel_curve) == 'animCurveTA':
                mel.eval('checkBoxGrp -edit -value1 1 expandChkBoxGrpanimCurveTA;')
                mel.eval('keyframeOutliner -edit -display "wide" keyOutlineranimCurveTA;')
            elif cmds.nodeType(o_sel_curve) == 'animCurveTU':
                mel.eval('checkBoxGrp -edit -value1 1 expandChkBoxGrpanimCurveTU;')
                mel.eval('keyframeOutliner -edit -display "wide" keyOutlineranimCurveTU;')

        else:
            mel.eval('string $animCurves_r[];')
            mel.eval('$animCurves_r = `keyframe -query -name`;')
            mel.eval('showEditor $animCurves_r[0];')
            mel.eval('commitAENotes($gAECurrentTab);copyAEWindow;')
            mel.eval('commitAENotes($gAECurrentTab);ToggleAttributeEditor;')
            mel.eval('checkBoxGrp -edit -value1 1 expandChkBoxGrp;')
            mel.eval('keyframeOutliner -edit -display "wide" keyOutliner;')
    except:
        print 'Animation Key is Not Seleced'


選択されているノードからカーブ選択 カーブの頂点選択について。
もう1つ大きな追加機能として、移動/回転/スケール の X/Y/Z と V ボタン部分のノードからカーブ選択やカーブから頂点選択部分です。
カーブを選択するコマンドはselectKeyです。

selectKeyhttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/selectKey.html

ポリゴン球を選択した状態かた移動X軸のカーブを選択してみます。

first_selected = cmds.ls( sl=True )[0]
cmds.selectKey(first_selected,replace=True,attribute='translateX')
CustomGraphEditor_V10_06
で、カレントフレームにあるX軸カーブの頂点を選択してみます。

first_selected = cmds.ls( sl=True )[0]
o_time = int(cmds.currentTime( query=True ))
cmds.selectKey(first_selected,replace=True,time=(o_time,o_time),attribute='translateX')


このボタンでの動作はこれで良かったのですが、後にオブジェクトじゃないもの、そうClip を選択してカーブをの頂点を選択したいとなった時、
これだと選択出来ないことに気がつきました。
確かに、カーソルでカーブの頂点を選択した時のログは

selectKey -add -k -t 1 pSphere1_translateX ;


となっていますので、keyframe オプションを使用してみます。

first_selected = cmds.ls( sl=True )[0]
o_time = int(cmds.currentTime( query=True ))
cmds.selectKey(first_selected + '_translateX', time=(o_time,o_time),r=True,keyframe=True)


となります。
さて、少し複雑になりますが、選択した頂点の次の頂点選択とかカレントフレームより次の頂点選択とかやってみます。
この時点では、既にカーブの頂点がどこかしたら選択されている、という段階から始まるので、現在のキー値を取得したとします。

selected_f_curve_key = cmds.keyframe( query=True, selected=True)[0]
print selected_f_curve_key

#結果 20.0
一旦カーブ全体を選択して全部のキーの値を取ります。
CustomGraphEditor_V10_07
cmds.selectKey(selected_f_curve)
selected_f_curve_keys = cmds.keyframe( query=True, selected=True)
print selected_f_curve_keys

#結果 [1.0, 20.0, 40.0, 60.0, 100.0]


そしたら、このリストの中の値が最初に取得した値より大きい時の値を取得すれが良いので、

selected_f_curve = cmds.keyframe( query=True, selected=True, name=True)[0]
#selected_f_curve_obj = (selected_f_curve[:selected_f_curve.index('_')])
#selected_f_curve_att = (selected_f_curve[selected_f_curve.index('_')+1:])
selected_f_curve_key = cmds.keyframe( query=True, selected=True)[0]

cmds.selectKey(selected_f_curve)
selected_f_curve_keys = cmds.keyframe( query=True, selected=True)

for o_frame in selected_f_curve_keys:
    if selected_f_curve_key < o_frame:
        break

#cmds.selectKey(selected_f_curve_obj, time=(o_frame,o_frame), attribute=selected_f_curve_att )
cmds.selectKey(selected_f_curve, time=(o_frame,o_frame),r=True,keyframe=True) 

#結果 40.0 を選択している


選択したキーより1つ前のキーを選択は、どうでしょう。
スクリプトでは、リストの中の値が、選択したキー値より同じか超えた時のインデックスから1つ引いた時の値としました。

o_index = 0
for o_frame in selected_f_curve_keys:
    if selected_f_curve_key <= o_frame:
        break
    o_index = o_index + 1

b_frame = selected_f_curve_keys[o_index - 1]

#cmds.selectKey(selected_f_curve_obj, time=(b_frame,b_frame), attribute=selected_f_curve_att )
cmds.selectKey(selected_f_curve, time=(b_frame,b_frame),r=True,keyframe=True) 

#結果 1.0 を選択している


あ、インデックスだけと求めたい場合はこうなります。

selected_f_curve_index = cmds.keyframe( query=True, selected=True, indexValue=True)
print selected_f_curve_index

#結果 [0, 1, 2, 3, 4]



カーブのフィルタリングをご紹介します。
既存のツールは、表示 > アトリビュートの選択..にあります。
でもここにあると表示しずらいのと、カスタムグラフエディタは複数枚表示したいので別ウィンドーツールだと選択が困ります。
コマンドのログを見ると

filterUISelectAttributesCheckbox translateX 1 graphEditor2OutlineEd;
filterUISelectAttributesCheckbox translateX 0 graphEditor2OutlineEd;

#結果 上がON 下がOFF

ということで、現在の表示しているグラフエディタの名前を取得して操作したいカーブ名を書けば良いようなので、

grp_panel_no_b = str( cmds.getPanel( withFocus=True )[11:] )
outline_ed_name = "graphEditor" + grp_panel_no_b + 'OutlineEd'
mel.eval('string $outlineed_name = "%s"' % outline_ed_name)
mel.eval('filterUISelectAttributesCheckbox translateX 1 $outlineed_name;')

↑このスクリプトはスクリプトエディタで動かしても意味がありません。
最初の行は、今フォーカスしているパネルのデータを取得しているので、スクリプトエディタを取得してしまいます。

最後に、中ボタンクリックを実現しているコマンドはdragControlです。
本来はdropCallbackと合わせて使い、ボタン間のデータをドラッグ&ドロップするために使うボタン系のコマンドにあるオプションです。

button dragCallbackhttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/button.html#flagdragCallback

この中のキー モディファイアで 0 == モディファイアなし、1 == SHIFT、2 == CTL、3 == CTL + SHIFT を識別できるところを使わせて頂きました。
なお、使わなくてもdropCallback も設定し、選択マニュピレーターが中ボタン操作後に直ぐには動かなくなるという挙動を防いでいます。
この部分を設定した、移動Xボタンの設定は、

grp_panel_no_a = str( len(cmds.getPanel( scriptType='graphEditor' )) + 1 )

cmds.button('b_c_tx'+ grp_panel_no_a,label='X',bgc=[0.3,0.0,0.0],annotation='TranslateX',
    command=select_curve_tx,dragCallback=filter_tx,dropCallback=filter_tx2)

コマンド部分は ボタンの色換えも含めて、

def filter_v( dragControl, x, y, modifiers ):
    if not cmds.ls( sl=True ):
        print 'Nothing is selected'
    else:
        if modifiers == 0:
            first_selected = cmds.ls( sl=True )[0]
            o_time = int(cmds.currentTime( query=True ))
            cmds.selectKey(first_selected,replace=True,time=(o_time,o_time),attribute='visibility')
        elif modifiers == 1:
            grp_panel_no_b = str( cmds.getPanel( withFocus=True )[11:] )
            outline_ed_name = "graphEditor" + grp_panel_no_b + 'OutlineEd'
            mel.eval('string $outlineed_name = "%s"' % outline_ed_name)
            c_v = cmds.button('b_c_v'+ grp_panel_no_b,q=1,bgc=True)
            if(c_v==[0.30000762951094834, 0.30000762951094834, 0.30000762951094834]):
                cmds.button('b_c_v'+ grp_panel_no_b,edit=1,bgc=[0.8,0.8,0.8])
                mel.eval('filterUISelectAttributesCheckbox visibility 1 $outlineed_name;')
            elif(c_v==[0.8,0.8,0.8]):
                cmds.button('b_c_v'+ grp_panel_no_b,edit=1,bgc=[0.3,0.3,0.3])
                mel.eval('filterUISelectAttributesCheckbox visibility 0 $outlineed_name;')

def filter_v2(*args):
    pass


以上です。



Version 10(改7)ミラー 機能対応
ミラー機能が追加されました。
Mayaデフォルトの機能には不具合があるようですので、それを補う意味でも有用で便利な機能となりました。
CustomGraphEditor_V10_7
■Mirror ボタン;選択したキーの範囲で
左マウスボタン(時間軸)のミラーになります。
中マウスボタン(値の正負)のミラーになります。




Python 解説

さて、ミラー機能を実現してくれるコマンドはscaleKeyです。

scaleKeyhttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/scaleKey.html

まず、横軸、つまり時間軸のミラーには、そのキーの最初と最後のキー値を変更範囲とします。
選択されたカーブから最初のキー値と最後のキー値を取得します。

selected_curve_key = cmds.keyframe( query=True, selected=True)

n_start = selected_curve_key[0]
k_count = -1
for o_key in selected_curve_key:
    k_count = k_count + 1
n_end = selected_curve_key[k_count]

そしたら、その最初のキー値と最後のキー値を、newStartTime,newStartFloat,newEndTime,newEndFloatに入れてあげます。

cmds.scaleKey(scaleSpecifiedKeys=True,newStartTime=n_end,newStartFloat=n_end,newEndTime=n_start,newEndFloat=n_start )

実は、これで終わりだったのですが、ボタンを連続してクリックすると、なにやら不穏な動きをするのです。
そこで、とっても回避策なのですが、doBufferのsnapshotとswapを入れてみることにしてみました。

mel.eval('doBuffer snapshot')
mel.eval('doBuffer swap')


縦軸、つまり値の正負のミラーは、scaleKeyvalueScaleに-1 を使えばよいようです。

cmds.scaleKey(scaleSpecifiedKeys=True,timeScale=1,timePivot=0,floatScale=1,floatPivot=0,valueScale=-1,valuePivot=0)


以上です。



Version 10(改9);SelectFromCurve 機能の改良
複数のオブジェクトを選択して表示している時の機能の改善を試みました。
幾つか案を頂き、今出来る範囲の事と照らし合わせて実装してみた機能になります。
CustomGraphEditor_V10_9_01
■SelectFromCurveボタン;
複数のオブジェクトを選択表示した状態で、カーブを1つ選択
左マウスボタン > そのアニメーションの付いたオブジェクトが一番上に表示されます。
一番上に表示される事で、移動/回転/スケールのXYZ表示/非表示ボタンで操作が出来るようになります。
CustomGraphEditor_V10_9_02
■SelectFromCurveボタン;
複数のオブジェクトを選択表示した状態で、カーブを複数選択
中マウスボタン > そのアニメーションの付いたオブジェクトのみが表示されるようになります。




Python 解説

さて、選択しているアニメーションカーブから オブジェクトを特定するコマンドはlistConnectionsです。

listConnectionshttp://help.autodesk.com/cloudhelp/2015/JPN/Maya-Tech-Docs/CommandsPython/listConnections.html

選択しているアニメーションカーブからそのアトリビュートやオブジェクトの情報を得るには、
最初 attributeInfoなどアトリビュートと付くコマンド名なのかと思っていて調べていたのですが、全く良い結果を得られませんでした。
更に、今回、とても悩まされたのは、アニメーションカーブを選択して得られるカーブの名前からは、
オブジェクトの名前を得てはいけないケースが多々あることに気が付いた点です。
(例えば、ClipをマージしてActive化したカーブ名にはアトリビュートに関係する文字は一切ありません。)

また厄介だったのは、そのオブジェクトにはネームスペースが付いていたり、キャラクターセット名が付いていたりする場合があり、
通常のオブジェクト名のものと合わせて、幾つかのケースがありそうです。
と、意外な謎解きゲームに突入してしまったのです。
そこに救世主のように探し当てたのが、listConnections コマンドだったのです。

まずは、pCone1 に通常のアニメーションを付け Clip化した後マージを実行しActive化すると、どんなアニメーションカーブ名になるかをご覧ください。
出来たカーブを選択して、そのログをスクリプトエディタで見ます。
CustomGraphEditor_V10_9_03
すると、animCurveTA4となっていて、pCone1 でも rotateZ でも無いので、選択したカーブ名からオブジェクト名を推測する方法では駄目だと解ります。

また、オブジェクトに設定される名前に関する情報ではどのようなパターンがあるか考えてみますと、以下をご覧ください。
CustomGraphEditor_V10_9_04
すると、通常のオブジェク名、ネームスペース名、キャラクターセット名、の組み合わせがありそうです。

では早速、listConnectionsを使って情報を引き出してみましょう。
ネームスペースが NewNamespace1 で、 D01 というキャラクターセットの付いた pPipe1 の移動Xのカーブを選択した状態から始めます。
まずは、カーブ名を取得します。

selected_f_curve = cmds.keyframe( query=True, selected=True, name=True)[0]
print selected_f_curve

#結果 pPipe1_translateX

この情報にlistConnections に、plugs=Trueというフラグを立ててみると、

selected_list = cmds.ls( selection=True )
selected_f_curve = cmds.keyframe( query=True, selected=True, name=True)[0]
selected_connect = cmds.listConnections(selected_f_curve,plugs=True)[0]
print selected_connect

#結果 D01.pPipe1_translateX

となり、キャラクターセット名まで取得出来ることがわかります。
そこで、同じlistConnections に、type='transform'というタイプ指定で入力すると、

selected_f_curve = cmds.keyframe( query=True, selected=True, name=True)[0]
selected_connect = cmds.listConnections(selected_f_curve,plugs=True)[0]
selected_obj = cmds.listConnections( selected_connect, type='transform')[0]
print selected_obj

#結果 NewNamespace1:pPipe1

となり、ネームスペースまでのオブジェクト名 がめでたく取得できまし<これが解決のポイントでした。 > 

ところが、逆にネームスペースもキャラクターセットも無い 普通のオブジェクト、
例えば、pCube1 のpCube1_translateXカーブを選択して上記を実行するとエラーになります。
それは、それらの情報が無いからですよね。
そこで、うまいことにselected_connectで取得した値をnodeTypeに渡すとcharacterと返って来るのが解ったので、
'character'で条件分岐を作成すると、全てのパターンでうまくオブジェクトの名前を取得出来ることになります。

selected_f_curve = cmds.keyframe( query=True, selected=True, name=True)[0]
selected_connect = cmds.listConnections(selected_f_curve,plugs=True)[0]

if cmds.nodeType(selected_connect) == 'character':
    selected_obj = cmds.listConnections( selected_connect, type='transform')[0]
else:
    selected_att = cmds.listConnections(selected_f_curve,plugs=True)[0]
    selected_obj = selected_att.split(".")[0]
print selected_obj

#結果 pCube1
#結果 NewNamespace4:pSphere1


で、ツールの仕上げとしては、複数のオブジェクトが最初選択しているところから始まるので、
一旦全部のオブジェクト名をリストにたくわえ、その中から、今カーブで取得したオブジェクト名を削除して、
一番最初にカーブから取得したオブジェクト名で選択し直して、残りを更新したリストから add フラグで選択し直すことをすれば、
一番上に選択したカーブから得たオブジェクトが来る としました。

selected_list = cmds.ls( selection=True )


cmds.select(clear=True)

if selected_obj in selected_list:
    selected_list.remove(selected_obj)
    cmds.select(clear=True)
    cmds.select(selected_obj,r=True)
    cmds.selectKey(selected_f_curve,add=True)
    for o_obj in selected_list:
        cmds.select(o_obj, add=True)
else:
    cmds.select(selected_obj ,r=True)
    cmds.selectKey(selected_f_curve,add=True)


中ボタンの方が簡単で、選択したものだけを残せばOKです。

以上です。



Version 20(改4);Panel対処とWindow化
突然バージョンがまた一桁上がりました。
作成されるパネルをWindow内にちゃんと管理して配置し、併せて使われなくなったパネルを削除して、
パネルが増殖し続けるのを防ぐ対応をした為です。
外枠に表示される ラベル名 も、New CustomGraphEditor* となりました。(また、Maya2016で作業を開始しました)
CustomGraphEditor_V20_04
さて、ものすごく進歩したにも関わらず、機能的に新しく解説できるものは、何もありません。
使う方としては、勝手にドンドンパネルが蓄積していくようなことは無くなり安心して使えるようになった、ということになります。




Python 解説

では、こちらの Pythonについての解説はというと、沢山の変更があります。
まずは、数枚グラフエディタ を作成(ティアオフのコピー)した場合、そのリストを取得するは、

graphpanels = cmds.getPanel(scriptType='graphEditor')
print graphpanels

// 結果: [u'graphEditor1', u'graphEditor2', u'graphEditor3', u'graphEditor4']

となります。
最初のgraphEditor1はデフォルトのパネル名なので、このリストから外しておきます。

graphpanels.remove('graphEditor1')
print graphpanels

// 結果: [u'graphEditor2', u'graphEditor3', u'graphEditor4']

そして表示していたグラフエディタ を全て閉じた後、パネル > パネルを見ると、グラフエディタ2,3,4が残っているのが確認出来ます。
ここで、表示していない全パネルリスト を以下のように取得します。

invis_panel = cmds.getPanel(invisiblePanels=True)
print invis_panel

// 結果: [u'modelPanel2', (--注略--), u'outlinerPanel1', (--注略--), u'graphEditor2', u'graphEditor3', u'graphEditor4', u'StereoPanel']

後は、表示してないパネルのリストから、グラフエディタ があったらそのパネルを削除する、ってスクリプトを書けばOKです。
で、パネルを消すコマンドは・・ 、deleteUI です。

deleteUIhttp://help.autodesk.com/cloudhelp/2016/JPN/Maya-Tech-Docs/CommandsPython/deleteUI.html
panel (pnl)  削除するオブジェクト名をパネルに限定します。

と書かれています。 つまり、

for o_graphp in graphpanels:
    if o_graphp in invis_panel:
        cmds.deleteUI(o_graphp,panel=True)

graphpanels = cmds.getPanel(scriptType='graphEditor')
print graphpanels
// 結果: [u'graphEditor1']

print した結果を見ると、graphEditor1だけが残っています。
このスクリプトをグラフエディタ が起動する前部分に追記しました。

さて、もう1つの進歩として、グラフエディタというスクリプトパネルをちゃんとWindowコマンドの中に入れて表示する、というところです。
そうすることによってWindowコマンドで表示している内容を制御できるようになります。
記述のポイントは parent= で、設定するモノの親を決めているところです。
つまり、windowwin1として設定し、次にpane1という縦に2つに分化した paneLayout の親を win1 にし、
scriptedPanelの親はpane1 という風に設定します。

import maya.cmds as cmds

if cmds.window('TEST', exists=True):
    cmds.deleteUI('TEST')

win1 = cmds.window('TEST', title='New CustomGraphEditor', resizeToFitChildren=True)
pane1 = cmds.paneLayout(configuration='horizontal2', paneSize=[2,1,1], parent=win1)

graphmenu = 'graphEditor2'
cmds.scriptedPanel(graphmenu, label=graphmenu ,type='graphEditor', parent=pane1)

cmds.setParent('..')
cmds.rowLayout(numberOfColumns=3,adjustableColumn=2)
cmds.button()
cmds.button()
cmds.button()
cmds.showWindow()
CustomGraphEditor_V20_04_01
このように表示できました。↑

ここでは graphEditor2 というパネル名で表示したので、
閉じた後に再度実行すると、# エラー: line 1: オブジェクト名 'graphEditor2' が固有ではありません。とエラーになります。
そう'graphEditor2' というパネルが残っているからです。

あとは、window の名前の重複にならない設定とか、
パネルの番号を各ボタンにも使って複数のグラフエディタが表示された時にボタン名がユニークになるようにしたりとか・・
と、かなりの変更がありました。興味がある方は スクリプトの中身を見てみてください。

以上です。



Version 30 ;Outliner対処 (GTMF2015ネタ)
突然バージョンがまた一桁上がりました。
GTMF2015にて発表したOutlinerが横に表示されるバージョンです。
アニメーションの付いているノード選択をどうにか改善したいと思って設置したものになります。
CustomGraphEditor_V30_00s
操作については何も説明は要りませんね。
階層構造内で選択したものがグラフエディタ内にアニメーションカーブ表示します。
このOutliner付きカスタムグラフエディタも複数表示に対応しています。



Python 解説

Pythonについての解説は、paneLayout コマンド のフラグを vertical2 にして、
アウトライナーのパネルを表示した後、グラフエディタを表示するという風にします。

import maya.cmds as cmds

if cmds.window('TEST', exists=True):
    cmds.deleteUI('TEST')

win1 = cmds.window('TEST', title='New CustomGraphEditor', resizeToFitChildren=True)
pane1 = cmds.paneLayout(configuration='vertical2', paneSize=[1,20,1], parent=win1)

in_panel = cmds.outlinerPanel()
outliner = cmds.outlinerPanel(in_panel, query=True,outlinerEditor=True)
cmds.outlinerEditor(outliner, edit=True,showDagOnly=True,transmitFilters=False,setFilter='DefaultPolygonObjectsFilter')

cmds.setParent('..')

graphmenu = 'graphEditor2'
cmds.scriptedPanel(graphmenu, label=graphmenu ,type='graphEditor', parent=pane1)

cmds.rowLayout(numberOfColumns=3,adjustableColumn=2)
cmds.button()
cmds.button()
cmds.button()
cmds.showWindow()


これを実行すると、下図のようになります。
後は、グラフエディタをカスタマイズし、パネルの増殖制御部分を追加して完成としました。
CustomGraphEditor_V30_01
起動コマンドは別名にしてありますので、Outliner 無しバージョンとの併用が可能です。

import CustomSideGraphEditor
CustomSideGraphEditor.custom_side_graph_editor()

以上です。



CustomGraphEditor.py と既知の制限・注釈
という訳で現在のところまでのカスタム・グラフエディタ のファイルを公開します。
(Pyファイルは シェルフにボタン登録するか、プルダウンメニューに登録して実行します。)
あくまでも自己責任でお使いください。use as your own risk.
既知の制限・注釈
・・なくなりました。めでたしめでたし・・



CustomGraphEditor (Maya2012,2014,2015,2016)
twitter ware @SI_UsersNotes

CustomGraphEditor_V10_6.zip11.2KB 2015/2/23
V10(改7)CustomGraphEditor_V10_7.zip11.5KB 2015/3/19
V10(改9)CustomGraphEditor_V10_9.zip11.7KB 2015/4/4

V20(改4) CustomGraphEditor_V20_4.zip12.1KB 2015/4/29

V30 CustomSideGraphEditor_V30.zip12.3KB 2015/8/09

MayaLTで使えるMEL版CustomSideGraphEditor_V30_mel.zip6.2KB2017/3/21 byizt_ml


という訳で、次回は Mayaのワークグループ設定についてです。
乞う、ご期待!!
戻る 次へ

お気軽にお問い合わせください

Daikin CG News お申し込み

CGクリエイター向けのセミナー・イベントやキャンペーン、製品情報をメールマガジンでお届けします。(登録無料)

Twitter

ページの先頭へ