<< TOP


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

    プロローグ; Prologue  V30公開


 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)






 パネルについての注意  >> この問題は Version 20 で解消されました。
     Mayaパネル について、前情報を提供します。(長年 Maya を使っている方でも知らなかった、と良く言われる情報なんです)
    前ページで紹介している SI_HotKey にて対応している 各種エディターの 複数表示 を行ったり、
    今回の カスタム・グラフエディタ 複数表示していたりしているものは パネル になります。
    詳しく述べると、画面表示したデフォルト仕様の グラフエディタ ティアオフして、そのコピーを表示している ということなります。



      この状態を パネル>パネル から見てみると、コピーした分のパネルが作成されて残っているのが解るかと思います。



     今度は、Mayaの設定を見てみます
    プレファレンス > インターフェイス > UI要素 > パネルの構成 の個目です。
    ■ 保存するとき も ■ 開くとき も 両方チェックが入っているのが デフォルト設定 です。



     これは、シーン内の パネル全てを保存し、そして 全てのパネルを保存した状態のシーン開く という意味です。
    保存時に開いていたウィンドー情報を保持するので、次に開く時、前と同じ状態になる、と言う意味では便利ですが、
    増やしていった全てのパネルをドンドンと溜めていく、という意味ではふさわしくない、とも言えます。
      そこで、開くとき は チェックを外すと パネルはリフレッシュされた綺麗な状態で開きます。
    また、大人数のプロジェクトで 他の人が作業した時のウィンドの状態(例 テキスチャーエディタ等 ) を開きたく無い場合にも、開くとき チェックを外す と良いです。
    自分の作業には必要のないウィンドーが表示されるわずらわしさをなくし、シーンを開く速度を速めます

     作業中のシーンで増えてしまったパネルを削除するには パネルエディタ で削除を行います。(パネルをうまく・きれいに削除するスクリプトは無いようです。)



     以上、パネルについての知識を踏まえた上で、マルチ カスタムグラフエディタ をお楽しみください。



 Maya での Python 準備
    ■ Maya の 日本語ドキュメント を ダウンロード して HELドキュメント をローカルディスクに用意して、Mayaに認識させましょう。
     最新の2015版はインストーラーではなく、ZIPで展開するだけのものなので、
     インストーラーだった May 2014 版 のディレクトリーを真似て 2015版を置くとしたら
     C:\Program Files (x86)\Autodesk\Maya2015\docs\Maya2015\ja_JP の下に展開したものをコピーします。
    その場所を、プレファレンス>インターフェイス>ヘルプ の ヘルプの場所 項目で カスタム の欄に登録します。



    そうしたら、スクリプトエディタ で、選択したコマンド名から> コマンドドキュメント を表示させたり、
    ヘルプ>Pythonのヘルプユーザーガイドが素早く表示できたりします。
    また、コマンド>クィックヘルプの表示 を有効にすると、右側に欄にオプションが表示されます。(狭いよ~^_^;;)
    コマンド>ツールヒント ヘルプの表示 はコマンドを書いている途中に情報を表示したりします。
    等など、色々と表示される仕組みはあるのですが、あとは好みでお願いします。





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


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

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

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



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



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

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



    ティアオフした 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・・・ という名前になっていきます。
    このコマンドでは、出来るパネルの名前が設定できないのと、日本語ってどうよ、って感じなのです。


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

    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 )

    そしたら、スクリプトエディタの右上のプルダウンメニューから、ファイル>スクリプトの保存 を選択します。



    保存先がユーザーディレクトリーの 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()
         ICON





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


    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 )

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




 カレントフレーム用のスライダーを作る ;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()

    実行すると、下図のように、スライドすると 現在のフレーム値が連動し、数値を入力しても現在のフレーム値になります。
    ただし、シーンの最小/最大フレーム値を変化させてもスライダーはリアルタイムには更新しませんので、なんのことはない、再表示してください。
    ちなみに、何枚でも表示できます。



    さて、ここでの キモ だった lambda式 は、ラジオボタンなど多くのところで素晴らしい働きをしてくれますので、ぜひマスターしてください。
    この章の締めくくる前に、少しこの lambda式 を解説してみたいと思います。


 lambda式 無名関数 ;Python http://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択毎にボタンのプリントも変わります。



    上記記述で面白いのは、グローバル値で 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 に設定に従って選択した頂点に設定します。



    さーて、いよいよ、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 コマンドを利用しています。


     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数入力欄 が増設されました!!



     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()




 Version 3 ; animの保存と読み込み
    カスタムグラフエディタ から直接 アニメーションデータの保存形式である .anim保存 読み込み の出来る ボタン が増設されました!!



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

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


    print cmds.pluginInfo("animImportExport",q=True,loaded=True)
    
    # 結果: False # 

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

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


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



    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: 文を設定しました。


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

    try:
        cmds.keyframe( query=True, selected=True, name=True)[0]
    
    except:
        print 'Animation Key is Not Seleced'




 Version 4 ; 階層アニメーション対応
    カスタムグラフエディタ から直接 保存 読み込み が出来る anim の形式を、 階層アニメーション対応 にしました。


     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アニメーションの形式を 保存 読み込み が出来るボタンと 階層の選択 が出来るボタンを追加しました。


     V4 にて対応した anim ファイルは、名前に基づかないので、例えば末端のユレ骨のアニメーションを他の骨にコピペする時などには便利です。
    ですが、その作成手順の違い等によって内部の関連性が異なるとアニメーションが壊れてしまう、ということになります。
    たとえ同じ構造のように見えてもシーンに読み込だ階層構造体に animファイルを Load すると壊れてしまう、ということが発生します。

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

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

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


    Python 解説
     さて、今回も FBXPythonUIなしExportImport を行えるように記述する訳ですが、
    FBX関連の書き方が ユーザーガイドに載っています、が、MELなんですね。
     Maya MEL スクリプティング データ交換 > ファイルトランスレーションに FBX を使用する > Maya FBX Plug-in >

    なので 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] を押すと、
    そのアニメーションが付いているオブジェクトのみの表示になります。


     [SelectHierarchy] 階層の選択 ボタン で、キャラクターの階層全てを選択すると、全てのアニメーションカーブが カスタムグラフエディタ 上に表示されます。
    表示 > アトリビュート > [レ] 移動 などにチェックを入れると 表示するカーブの種類のフィルタリングが出来ます。
    例えばスケースアニメーションが付いているのかとか検索できます。
    その後、実際にそのキーアニメーションがどのJointに設定されているのかを表示するのに この [SelectFromCurve] は役立ちます。



     このための追加スクリプト記述はとても簡単です。
    何か選択されているかを 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、新機能説明
    突然バージョンが一桁上がりました。それは、もう以前よりもはるかに完成度の高いツールに仕上がっていったからです。
    ここまで到達出来たのも、たくさんの要望と協力があってからこそです。
    まずは一気に新機能を説明していきます。



     ■タイムライン もうMayaにあるそのものになりました。シーンにあるものとシンクロして動きます。
       再生範囲もMayaメイン画面下のものを変更するとすぐに反映されます。
     ■再生ボタン等 もうMayにあるものと同じボタンなので、もう機能説明も要りません。
       Step数の指定で、|< と >|  ボタンは そのStep数に分コマ飛ばしが出来ます。
     ■[ShowSpeadsheet] 選択したカーブのスプレッドシートを、アトリビュートのコピー画面で表示してくれます。自動的に展開します。
       コピー画面なので、何枚でも表示でき、ずっと表示したままでいてくれます。
     ■CurveIndexInfo 選択したキーの In側Out側の AngleWeight を表示してくれます。
       [Set] ボタン は任意に選択したキーに 今表示している値を設定することが出来ます。



     ■移動/回転/スケール の X/Y/Z と V ボタン
       左マウスクリック ; これらはまずは、そのカーブを選択するボタンです。
        中マウスクリック ; カレントフレームにキーが合った場合に、このカーブの キー(頂点) を選択します。
       [ V10(9改)仕様変更 ] 中マウスクリック 複数ノードを選択して表示している場合、同一のカーブ全てを選択状態にします。例 全部の移動X 。
       Shift + 中マウスクリック ; そのカーブのフィルタリング をします。



     ■ |< と >|  ボタン 
       左マウスクリック ; タイムライン上にキーのある、前/次 のフレームにカレントフレームが動きます。
       中マウスクリック ; 選択しているそのカーブの 前/次 のキー(頂点) を選択します。
       Shift + 中マウスクリック ; カレントフレームから見て 前/次 のキー(頂点) を選択します。




    Python 解説

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


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

    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() {
    という文が書かれているところを見ると、symbolButton に timeplay.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数 を入れるとコマ飛ばしになるようにもしたかったので playButtonStepBackward と playButtonStepForward だけは値が入るように
    独自にPython化してみました。
    この辺の周りを window にして Python化するとこんな スクリプトが作成出来ました。

     再生時 play_forward は、そのMelコマンドをそのまま使っても良かったのですが、再生すると 停止のICONに変化する工夫を入れてみました。
     (でも、後で気が付いたのは、Mayaのメイン画面で再生して、カスタムグラフエディタで停止するとアイコンが逆になりますね。もう一回やって戻してください。)


    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];



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


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



    スプレッドシートの選択カーブの左にあります通り、移動;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 です。

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

    first_selected = cmds.ls( sl=True )[0]
    cmds.selectKey(first_selected,replace=True,attribute='translateX')




    で、カレントフレームにある 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


    一旦カーブ全体を選択して 全部のキーの値を取ります。



    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 と合わせて使い、ボタン間のデータをドラッグ&ドロップするために使う ボタン系のコマンドにあるオプションです。


    この中の キー モディファイア で 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デフォルトの機能には不具合があるようですので、それを補う意味でも有用で便利な機能となりました。



     ■ Mirror ボタン ; 選択したキーの範囲で
         ・ 左マウスボタン; (時間軸)のミラーになります。
         ・ 中マウスボタン; (値の正負)のミラーになります。




    Python 解説

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


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

    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')


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

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


    以上です。



 Version 10 (改9); SelectFromCurve 機能の改良
     複数のオブジェクトを選択して表示している時の機能の改善を試みました。
    幾つか案を頂き、今出来る範囲の事と照らし合わせて実装してみた機能になります。



     ■ SelectFromCurve ボタン ; 
        複数のオブジェクトを選択表示した状態で、カーブを1つ選択
     ・ 左マウスボタン; > そのアニメーションの付いたオブジェクトが一番上に表示されます。
         一番上に表示される事で、移動/回転/スケールのXYZ表示/非表示ボタンで操作が出来るようになります。




     ■ SelectFromCurve ボタン ; 
        複数のオブジェクトを選択表示した状態で、カーブを複数選択
     ・ 中マウスボタン; > そのアニメーションの付いたオブジェクトのみが表示されるようになります。




    Python 解説

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


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

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

     まずは、pCone1 に通常のアニメーションを付け Clip化した後マージを実行しActive化すると、どんなアニメーションカーブ名になるかをご覧ください。
    出来たカーブを選択して、そのログをスクリプトエディタで見ます。



    すると、animCurveTA4 となっていて、pCone1 でも rotateZ でも無いので、選択したカーブ名からオブジェクト名を推測する方法では駄目だと解ります。

    また、オブジェクトに設定される名前に関する情報ではどのようなパターンがあるか考えてみますと、以下をご覧ください。



    すると、通常のオブジェク名、ネームスペース名、キャラクターセット名、の組み合わせがありそうです。

     では早速、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で作業を開始しました)



     さて、ものすごく進歩したにも関わらず、機能的に新しく解説できるものは、何もありません。
    使う方としては、勝手にドンドンパネルが蓄積していくようなことは無くなり安心して使えるようになった、ということになります。




    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 です。

             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= で、設定するモノの親を決めているところです。
    つまり、window を win1として設定し、次に 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()
    


          このように表示できました。↑

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

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

    以上です。




 Version 30 ; Outliner対処 (GTMF2015ネタ)
     突然バージョンがまた一桁上がりました。
    GTMF2015 にて発表した Outlinerが横に表示されるバージョンです。
    アニメーションの付いているノード選択をどうにか改善したいと思って設置したものになります。



     操作については何も説明は要りませんね。
    階層構造内で選択したものがグラフエディタ内にアニメーションカーブ表示します。
    この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()
    


    これを実行すると、下図のようになります。
    後は、グラフエディタをカスタマイズし、パネルの増殖制御部分を追加して完成としました。



    起動コマンドは別名にしてありますので、Outliner 無しバージョンとの併用が可能です。

    import CustomSideGraphEditor
    CustomSideGraphEditor.custom_side_graph_editor()
    

    以上です。




 CustomGraphEditor.py と 既知の制限・注釈
 既知の制限・注釈
    ・・ なくなりました。めでたしめでたし・・




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

CustomGraphEditor_V10_6.zip 11.2KB 2015/2/23
V10(改7) CustomGraphEditor_V10_7.zip 11.5KB 2015/3/19
V10(改9) CustomGraphEditor_V10_9.zip 11.7KB 2015/4/4

V20(改4) CustomGraphEditor_V20_4.zip 12.1KB 2015/4/29

V30 CustomSideGraphEditor_V30.zip 12.3KB 2015/8/09

MayaLTで使えるMEL版
CustomSideGraphEditor_V30_mel.zip 6.2KB 2017/3/21 by



     
    という訳で、次回 は Mayaのワークグループ設定について です。

      乞う、ご期待!!