チュートリアル / カットシーンのデータ管理
第2回:TimeEditorを⼯夫して管理を効率化してみよう②
- Maya
- UI・ビューポート
- ゲーム
- コラム
- スクリプト・API
- チュートリアル
- 学生・初心者
みなさんこんにちは。
このコラムでは、ゲーム制作におけるカットシーンのデータ管理について、変化球的な効率化の事例を紹介しています。
今回も前回に引き続き、「TimeEditorを⼯夫して管理を効率化してみよう」のお話です。
前回は、Maya上でのカットシーン制作においてTimeEditorのGUIを利⽤して、データベース的に利⽤してみる、といった趣旨の運⽤提案のお話でした。
今回は、スクリプトでTimeEditorを操作する話題を中⼼に取り上げます。
TimeEditorのスクリプト操作
まずはスクリプト操作したいTimeEditorの操作を考えてみます。
前回にもご紹介した、カットシーン管理⽤にTimeEditorをカスタムした弊社のサンプルToolをみながら、操作について追ってみましょう。
想定されるTool挙動︖
このToolは、カットシーンのアセットとして出⼒したいノードをクリップに紐づけて登録し、出⼒対象のアニメーション⻑をクリップの⻑さとして設定しクリップを保存します。
しかしTool操作としては、いちいち⼿作業で
・クリップを作成
・決まった名前にリネーム
・対象のアニメーション区間になるようにクリップ⻑を調整
といった操作は、⾮常に⾯倒です。
そこで…、
1. 対象ノードを選択
2. Tool上のメニューなどから処理を実⾏
…という操作で⼀連の作業が⾏えるようにします。
では、そのスクリプト操作を⾒てみましょう。
トラックを作成する
まずはトラックを作るところから始めてみます。
https://help.autodesk.com/cloudhelp/2025/JPN/Maya-Tech-Docs/CommandsPython/timeEditorTracks.html
TimeEditorにトラックを追加するのは簡単で、TimeEditorを開いている状態で…
cmds.timeEditorTracks( compositonName, e=1, at = -1,tn='track_name')
…と実⾏すれば任意の名前で作成ができます。
ポイントは以下の通り。
・変数「compositionName」に対象のCompositon名を指定。
・atフラグ(addTrack)でトラックを追加するインデックスを指定します。 -1で末尾を指定できます。
・tnフラグ(trackName)でトラックに付ける名前を指定します。
なお、現在表⽰されているCompositionの名前を取得するには、
cmds.timeEditorComposition(q=True, act=True)
…と、このようなシンプルなスクリプトで取得が可能です。
現在表⽰されているCompositionに次々トラックやクリップを追加していく想定なら、これで⼗分です。
cmds.timeEditorTracksでトラックを作ると、トラックのインデックスが返り値で取得できる
上記で紹介したトラックを作るスクリプトを実⾏すると、作成したトラックのインデックスが返り値として取得できます。
前回説明したように、TimeEditorではトラックはノードとして存在しているのではなく、Compositionを表す「timeEditorTracksノード」内のマルチアトリビュートとして存在しています。
そのため、トラックを新たに作成するコマンドなのにも拘わらず、editフラグを⽴てて編集モードで実⾏する必要があるんですね。
outlinerの⾒た⽬ではまるでそれ専⽤のノードがあるように⾒えますが、ここは惑わされてはいけません。
トラックのリネーム︖
1. スクリプトのコマンドでリネームする⽅法
トラックをリネームするには、以下のようなスクリプトで実⾏できます。
cmds.timeEditorTracks( e=1, tn="NEW_NAME", pt='Composition1|track_name' )
・tnフラグ(trackName)でリネーム後の名前を指定します。
・ptフラグ(path)で操作対象のトラックを指定します。
2. インデックス番号とsetAttrでリネームする⽅法
上記でもできるのですが、別の⽅法として、trackを作成した際に取得できるインデックスの番号を使ってリネームする⽅法があります。
こちらはsetAttrコマンドを⽤いて⾏い、いたってシンプルです。
前述のようにTrackはtimeEditorTracksノード(Composition)のマルチアトリビュートで、「<timeEditorTrackノード名>.track[*]」という形でトラックの数分登録されています。
ここであらかじめ取得したTrackのインデックスを使えば、ねらいのマルチアトリビュートを指定して操作することが可能になります。
Trackの名前を格納しているアトリビュートまでたどり着ければ、あとは簡単で…
cmds.setAttr('{}.track[{}].trackName'.format(compositonName,index), 'NEW_NAME', type='string')
・変数「compositionName」に対象のtimeEditorTrackノード名(Composition名)を指定
・変数「index」に対象trackのインデックスを指定
・'NEW_NAME'に任意のリネーム後の名前を指定
・string値をsetAttrする場合、「typeフラグ」で「string」を明⽰的に指定する
このように指定して実⾏すればOK。
対象Trackに⾊を付けてみる
Trackには背景に⾊を付けることもできます。
アトリビュートエディタでは、各TrackアトリビュートのTrack Colorアトリビュートと、Use Track Colorのチェックで操作が可能です。
これをスクリプトで⾏うと…
cmds.setAttr('{}.track[{}].useTrackColor'.format(compositonName,index), True)
cmds.setAttr('{}.track[{}].trackColor'.format(compositonName,index), 0.1, 0.2, 0.5)
・変数「compositionName」に対象のCompositon名を指定
・変数「index」に対象trackのインデックスを指定
・アトリビュート「trackColor」に対して、任意のRGBの値を指定
このように指定します。
このように、TimeEditorのTrack部分に⾊を付けることができました。うまく運⽤できれば視認性の向上を測ることができると思います。
実際の運⽤では、TimeEditor状の⾒た⽬がカラフルになりすぎて逆効果と判断し、実際には導⼊していません。
しかし、別のケースでは有効なTipsになるかもしれません。 頭の⽚隅に置いていただければ幸いです。
操作対象のトラックは、フルパス指定よりもインデックス指定の⽅が安⼼
timeEditorTrackコマンドでは、狙いのトラックをフルパス指定することで概ねコマンド操作を⾏うことができる仕様です。
しかし、Track操作に関して実際に運⽤してみて、「フルパス指定」で設計運⽤するよりも「インデックス指定」で⾏った⽅が、⾊々と対応しやすく感じました。
これは、
明⽰的にtrackNameを指定していたとしても、既に同名のトラックがあった場合では勝⼿にリネームされてしまう
という、Mayaではよくある挙動がいい例で、つまり…
実のところ本当は何という名前で⽣成されたのか分からない
…という事が個⼈的には⾮常に不安だった、ということです。
また、対象のtrack名を何とか取得したとしても、そこからフルパスに直さないといけない事も、正直⼿間です。
⼀⽅インデックスでの運⽤は、trackを⽣成すれば純粋にそのインデックスを取得ができて楽だし、安⼼です。
別の例だと、timeEditorで選択しているtrackのインデックスを取得するのも⼤変シンプルで…
cmds.timeEditorTracks(q = True,st=True)
これだけで選択したtrackのインデックスと対象のCompositionの名前を「tracksNode:trackIndex」フォーマットでリスト取得できます。
また、Compositionの名前を明⽰的に指定して…
cmds.timeEditorTracks(compositionName,q = True,st=True)
このように実⾏してあげれば、選択しているtrackのインデックスの整数リストが取得可能です。
インデックスってステキですね。
クリップを作成する
続いてクリップです。
https://help.autodesk.com/cloudhelp/2025/JPN/Maya-Tech-Docs/CommandsPython/timeEditorClip.html
クリップを作成するには、以下のスクリプトで実⾏が可能です。
cmds.timeEditorClip(user_clipName,track='{}:{}'.format(compositonName, index),s = startTime,d = clipLength)
・変数「user_clipName」には任意のクリップ名を指定
・trackフラグには対象のトラックを指定
・指定書式は 「Composition名:インデックス番号」 と書き、コンポジションとトラックを指定する。
・sフラグ(startTime)には、開始フレームの数値を指定。
・dフラグ(duration)には、設定したいクリップの⻑さ(フレーム数)を指定。
なお、「et(endTime)」というフラグもありますが、これは照会モードのみで使えるフラグで、「クリップの相対的な終了時間を照会」するためのフラグです。
なんだかstartTimeに対してendTime…とペアで使ってしまいそうになりますが、これではクリップの⻑さは設定されないので注意が必要です。
timeEditorClipコマンドでクリップを作成すると、クリップのidが返り値で取得できる。
上記スクリプトでクリップを作成すると、その返り値としてクリップのidを取得することが出来ます。
はて。クリップのidって何なんでしょうか。
作成したtimeEditorClipノードをアトリビュートエディタで⾒てみると、Clipidアトリビュートがあります。
先ほどの返り値はここの数字を⽰しています。
なお、NodeEditorでtimeEditorClipノードを確認すると、Compositionノードのマルチアトリビュートから接続があります。
よもやClipidはこのインデックス番号と対応しているのでは?
…などと勘違いしてしまいそうですが、そうではありません。
実はこの接続、⼀旦クリップを消してから再度クリップを⽣成した際、改めて0番⽬から接続を作るのではなく、さらにマルチアトリビュートのインデックスを増やして新しい接続を作ろうとします。
つまり、Compositionの接続インデックスとClipidはそもそも別物、と認識しておいた⽅が良さそうです。余談になりますが、このClipidの数値、⼿⼊⼒で変更できてしまう点には注意が必要です。
しかも⾯⽩いことに、他のクリップと重複するidを指定しようとすると⾃動的に⼊⼒する数字が変わって、必ず⼀意の値になるようになっています。
あと、0を⼊⼒しようとすると、1以上に強制的に戻されるのですが、クリップはTimeEditorのGUIから姿を消してしまいます。
改めて1など、本来の数字の⼊⼒に戻してあげると、クリップの表⽰は復活するのですが…。
なんだか恐ろしいですね…。
実際の運⽤では、この部分の制御についてはオミットしましたが、予期せぬ不具合のことを考えて頭に⼊れておく必要があると思います。
場合によっては、何か制御を挿⼊するか、運⽤ルールなどを取り決めてもいいかもしれません。
クリップの⾊を設定する
クリップを紐づけるアセットの種類によってクリップをあらかじめ⾊分けしておくと、TimeEditorの視認性が上がって便利になります。
クリップの⾊設定に関するアトリビュートは、timeEditorClipノード内に内包されているので、こちらも settAttr で設定が可能です。
スクリプトは以下の通り。
cmds.setAttr('{}.clip[0].useClipColor'.format(clipNode), True)
cmds.setAttr('{}.clip[0].clipColor'.format(clipNode), 0.2, 0.5, 0.8)
・変数「clipNode」に対象のクリップノードの名前を指定
・アトリビュート「clipColor」に対して、任意のRGBの値を指定
このように、クリップに⾊を設定することができました。
クリップのリネーム?
前回のコラムでもふれたように、クリップのリネームは対象のtimeEditorClipノードをリネームするだけではダメです。
クリップ内にある「Clip Name」アトリビュートを操作しないと名前が変わらないため、少々ややこしい仕様です。
ですので、ここもスクリプト化して、ヒューマンエラーにならないようにしたほうが良さそうです。
例えば、「TimeEditor上で選択しているClipをリネームする」というスクリプトを作成してみると、以下の様になります。
selection = list(set(cmds.ls(sl=True, type='timeEditorClip', o=True)))
for clipNode in selection:
cmds.setAttr('{}.clip[0].clipName'.format(clipNode), NEW_NAME, type='string')
クリップはtimeEditorTracksノードのマルチアトリビュートだったトラックと違い、そもそもノードとしてシーンに存在しているため、「ls(sl = True)コマンド」で簡単に取得することができます。
ただし、返って来る値に注意が必要です。
例えば、クリップ「testClip」をTimeEditorのGUI経由で選択している場合に返される値は、以下のような形で返されます。
testClip.clip[0]
正確にはこれはノード名ではなく、timeEditorClipノードの中のアトリビュート名です。
ここで欲しいのは、アトリビュート名ではなくノード名なので、lsコマンドに「oフラグ(objectsOnly)」を⽴てて、アトリビュートを無視するようにします。
また、NodeEditorでclipノードを選択しながら、TimeEditorでも選択した状態で選択中のtimeEditorClipノードをリストアップすると、返って来るノード名に重複がある疑いがあります。
なぜなら、NodeEditor側で純粋にノード選択した情報と、TimeEditor経由でアトリビュート選択した情報が競合して、同じノード名を2回返す可能性があるからです。
実のところ、ユーザーがNodeエディター経由でClipノードを選択する可能性は低いかもしれませんが、念のため…
list(set(**))
…で括って、重複を除外しておきます。
あとはシンプルに「**.clip[0].clipName」アトリビュートに対して、任意の⽂字列でsetAttrしてしまえば、リネームは完了です。
コンポジションを作成する
同じMayaシーンで、複数のカットを作業することがあります。
こういう場合は、コンポジションを使って関連クリップを分けてデータを整理することにします。
コンポジションを作成するには以下のスクリプトで簡単に作成できます。
comp = cmds.timeEditorComposition(NEW_COMPOSIRION_NAME)
・変数「NEW_COMPOSIRION_NAME」にcompositionにつけたい名前を設定
返り値で作成したcompositon、すなわちtimeEditorTracksノードの名前を返す点でも、素直で使いやすいコマンドです。
命名ルールがはっきりしていない場合、とりあえずユーザーに対話形式で決めてもらうのもアリ︖
作成するコンポジションの名前は、あらかじめ命名規則を決めて置くことで容易にスクリプトによる⾃動化で設定することが可能になります。
しかし実際の運⽤では、仕様を作りながら作業を進めるケースも多く、命名の⾃動化に必要なルールが決まっていない事があります。
とはいえ、命名を完全にユーザ―に依存するのは管理上若⼲怖いものがあります。
そこで、ここでは対話式のdialogを使って、ユーザーにコンポジションの名前を⼊⼒してもらうことにしてみます。
こういう時、「promptDialog」コマンドが⼤変便利です。
https://help.autodesk.com/cloudhelp/2025/JPN/Maya-Tech-Docs/CommandsPython/promptDialog.html
このpromptDialogを使った対話式の⼊⼒で、ユーザーに
・シーン番号
・カット番号
・ラベル
・備考
を⼊⼒してもらい、コンポジションの名前を設定してもらいます。
命名規則は暫定的に、
「<シーン番号>_<カット番号>_<ラベル>」
とし、備考の情報はcompositionノードに追加アトリビュートを作って、そこに⼊⼒さされるようにしてみます。
…と、これくらい暫定でルール化しておけば、あとで命名規則が変わっても、まぁ何とかなるかもしれません。
スクリプトにすると、こんな感じです。
def getPromptDialogValue(title='',message='',*args,**kwargs):
"""プロンプトダイアログを生成し、値を取得します
指定されたタイトルとメッセージでプロンプトダイアログを作成します。
OKボタンが押された場合、入力された値を返します。
OKボタンが押されなかった場合、Falseを返します。
Args:
title(str):プロンプトダイアログに表示するタイトル文字
message(str):プロンプトダイアログに表示するメッセージ
Returns:
value(str/bool):プロンプトダイアログに入力された文字。
OKボタンが押されなかった場合はFalseを返す。
"""
result = cmds.promptDialog(t=title,
m=message,
b=['OK','Cancel'],
db='OK',
cb='Cancel')
if not result == 'OK':
return False
value = cmds.promptDialog(q = True,tx = True)
return value
def createCompositionName(*args,**kwargs):
"""新しくコンポジションの名前を生成する
新しいコンポジションの名前と、備考情報を生成します。
生成には暫定的にプロンプトダイアログの対話形式によるユーザー入力を求めます。
Args:
None
Returns:
value,value(str,str/bool,bool):名前となる文字列と、備考の内容を返します。
生成に失敗していると、False,Falseを返します。
"""
sceneNo = '000'
CutNo = '000'
label = 'labelName'
note = ''
sceneNo = getPromptDialogValue('Create composition','Please set sceneNo.')
if not sceneNo:
return False,False
CutNo = getPromptDialogValue('Create composition','Please set CutNo.')
if not CutNo:
return False,False
label = getPromptDialogValue('Create composition','Please set label')
if not label:
return False,False
note = getPromptDialogValue('Create composition','Please set Note.')
if not note:
note = ''
returnName = 'EVNT_{}_{}_{}'.format(sceneNo,CutNo,label)
return returnName,note
def createComposition(*args,**kwargs):
"""新しいコンポジションを生成する
コンポジションを作成します。
もしも同名のコンポジションがあった場合、ワーニング表示を出して生成しません。
Args:
None
Returns:
compositionNode(str):生成されたtimeEditorTracksノードの名前
"""
NEW_NAME,note = createCompositionName()
if not NEW_NAME:
return False
compositionList = cmds.ls(type = 'timeEditorTracks')
print(compositionList)
if NEW_NAME in compositionList:
cmds.warning('既に"{}"があります。別名にしてください。'.format(NEW_NAME))
return False
compositionNode = cmds.timeEditorComposition(NEW_NAME)
cmds.addAttr(compositionNode,ln = 'note',dt = 'string')
cmds.setAttr('{}.note'.format(compositionNode),note,type = 'string')
return compositionNode
createComposition()
動作させてみると、こんな感じです。
対話回数が気になるならば、「layoutDialog」コマンドでdialog上にレイアウトを作ってしまって、⼀気に⼊⼒させてもいいかもしれません。
https://help.autodesk.com/cloudhelp/2025/JPN/Maya-Tech-Docs/CommandsPython/layoutDialog.html
先ほどの処理のうち、関数「createCompositionName」を「layoutDialog」を使ったものにしてみます。スクリプトは以下の通りです。
def getPromptDialogValue(title='',message='',*args,**kwargs):
"""プロンプトダイアログを生成し、値を取得します
指定されたタイトルとメッセージでプロンプトダイアログを作成します。
OKボタンが押された場合、入力された値を返します。
OKボタンが押されなかった場合、Falseを返します。
Args:
title(str):プロンプトダイアログに表示するタイトル文字
message(str):プロンプトダイアログに表示するメッセージ
Returns:
value(str/bool):プロンプトダイアログに入力された文字。
OKボタンが押されなかった場合はFalseを返す。
"""
result = cmds.promptDialog(t=title,
m=message,
b=['OK','Cancel'],
db='OK',
cb='Cancel')
if not result == 'OK':
return False
value = cmds.promptDialog(q = True,tx = True)
return value
def createCompositionName(*args,**kwargs):
"""新しくコンポジションの名前を生成する
新しいコンポジションの名前と、備考情報を生成します。
生成には暫定的にプロンプトダイアログの対話形式によるユーザー入力を求めます。
Args:
None
Returns:
value,value(str,str/bool,bool):名前となる文字列と、備考の内容を返します。
生成に失敗していると、False,Falseを返します。
"""
sceneNo = '000'
CutNo = '000'
label = 'labelName'
note = ''
sceneNo = getPromptDialogValue('Create composition','Please set sceneNo.')
if not sceneNo:
return False,False
CutNo = getPromptDialogValue('Create composition','Please set CutNo.')
if not CutNo:
return False,False
label = getPromptDialogValue('Create composition','Please set label')
if not label:
return False,False
note = getPromptDialogValue('Create composition','Please set Note.')
if not note:
note = ''
returnName = 'EVNT_{}_{}_{}'.format(sceneNo,CutNo,label)
return returnName,note
def createComposition(*args,**kwargs):
"""新しいコンポジションを生成する
コンポジションを作成します。
もしも同名のコンポジションがあった場合、ワーニング表示を出して生成しません。
Args:
None
Returns:
compositionNode(str):生成されたtimeEditorTracksノードの名前
"""
NEW_NAME,note = createCompositionName()
if not NEW_NAME:
return False
compositionList = cmds.ls(type = 'timeEditorTracks')
print(compositionList)
if NEW_NAME in compositionList:
cmds.warning('既に"{}"があります。別名にしてください。'.format(NEW_NAME))
return False
compositionNode = cmds.timeEditorComposition(NEW_NAME)
cmds.addAttr(compositionNode,ln = 'note',dt = 'string')
cmds.setAttr('{}.note'.format(compositionNode),note,type = 'string')
return compositionNode
createComposition()
動作の様⼦は以下の通りです。
このように、レイアウトを組んだDialogが使えて便利です。
もちろん、作成⽤のGUIを⽤意して実装してもいいのですが、命名規則が決まった場合、シーン名やフォルダ構造から名前を推定できる可能性があります。
業務効率化の観点で、できるだけユーザーの⼊⼒は減らしたいので、⾃動で設定できるなら最⾼です。
ですので、「今は⼿⼊⼒だけど、そのうち完全⾃動にする」前提で設計を考えると、上記のスクリプトの様に変数に関数の返り値をぶち込む設計にしておいた⽅が、あらかじめ便利なわけです。
GUIの⼯数も節約できるし、完全⾃動にする場合、名前を設定する処理をその関数内に書いてしまえば解決するわけですから。
まとめ
さて、今回はTimeEditorのトラックやクリップをスクリプトで作成する⽅法を中⼼に紹介しました。
内容としては初⼼者向けの簡単なレベルだったかと思いますが、実際TimeEditorをカスタマイズしていて躓いた箇所や気づきのあった個所を重点的に取り上げてみました。
TimeEditorをカスタマイズして運⽤する際のTipsになれば幸いです。
次回はTimeEditorの変化球的なカスタマイズ例として…
・処理を軽量化するために、独⾃にアトリビュートを紐づける例
・TimeEditorをカスタマイズして、outlinerをくっつける例
・作業効率の効率化のため、ポップアップメニューをくっつける例
…についてご紹介したいと思います。ではまた次回!
TA応援隊の⼩話
【第2回】
みなさんごきげんよう。COYOTE TAチームの応援隊 小澤です。
今春、TAチームには3名の新卒メンバーが入社して一層賑やかになりました。
さて、今回は弊チームの歴史についてご紹介したいと思います。
前回でも軽く触れましたが、大元はゲームを中心とした3DCG開発を専門とする「COYOTE 3DCG STUDIO」に始まります。従来、人材派遣・紹介が中心だったクリークが初めて設けた「制作部隊」の一つで、その名の通り3DCGのモデリング・モーションを請け負うスタジオです。
同スタジオは2012年に設立し、そこから少しずつ案件数が増えていったのですが、それと同時にクオリティは上がっていくわ、それに伴って開発も長期化していくわ…と、ただ制作に向き合っていくだけではより良いソリューションを提供できないという状況に変わっていきました。…今思えば、TAが必要とされるに至った歴史そのものにも近いですね。
そこで注目したのがTAの存在で、TAを採用し始めたのが今日の弊チームの原型です。
そこからまずは同スタジオ内のモデリング・モーション案件のテクニカルサポートから始まり、そうしているうちにクライアント様から
「クリークさんってTAさんはいらっしゃらないですか? TAがいなくて困ってるんです」
「ここのテクニカルサポートをお願いできる人を探してるんですけど…」
とご相談をいただくようになり…それらにお応えしようとあれこれ試行錯誤しているうちに、気が付いたら現在の規模になっておりました。
まだまだ全てのご要望にはお応えできていませんし、やればやるほどやりたいことも増えていき、「もっとTA業界を盛り上げたい」という思いもどんどん増していきます。
実は大きな野望を抱えたチームですので、ぜひ注目していただけたら幸いです。
それではまた次回お会いしましょう!