Maya Python

制御構文を使って実務で使える CUI を組む ~ ループ構文の基礎と実用と応用 ~


今回、長い です。長いですが、実務で使えるコードが書けるようになります。

具体的には制御構文を使った一括処理がテーマです。今までは 1行か 2行の文を書いてきましたが、今回から 文章 を書いていきます。つまずいたら、ゆっくり一単語ずつみていってください。

【for文】ループ処理の基礎

for 文という制御構文について解説していきます。繰り返しの処理を行うためのもので、短いコードで規則的な処理を行うことができるようになります。

下記のコードを実行すると大きさの違うポリゴンのキューブが 10個 とコーンが1つ作られます。

import maya.cmds as cmds

for i in range(10):
    size = i + 1
    cmds.polyCube(w=size, h=size, d=size)

cmds.polyCone(h=size, r=size/2, sh=0)

短いコードですが何かと複雑な構造をしています。ひとつひとつ丁寧にみていきます。

range 関数

range 関数は引数で渡した数に対して、その数の連番の値を作成する関数です。

range 関数は連続した連番の値を作る

戻り値は 0 からの連番 になるため、引数で渡した整数の 1つ前の数字までが連番 になります。主に for 文と組み合わせて「 同じことを〇回繰り返したい 」というときに使用します。引数で設定できるのは int型(整数)のみです。

range 関数のバージョンの違い

この関数も maya のバージョンで戻り値が違います。
( 戻り値とは関数を実行した際に戻ってくる値のことです。こちらで説明していますのでよければどうぞ。)

maya2022 から python3に移行した

range 関数の戻り値を print 関数で見てみます。

from __future__ import print_function

print(range(10))

maya2022 以降 → (0, 10)
maya2020 以前 → [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

python3 からは range型というデータ型が返ってくるようになりました。
Python3とPython2でのrange関数の違い | Tech Teacher Blog

python3 からは rangeに統一

オブジェクトは非常にややこしく、論争を巻き起こす単語なのでほかのサイトを紹介しておきます。(逃)
オブジェクトとは | PYTHON 学習講座

for 文の基本構成

最もシンプルな for 文をみていきます。

for 文は繰り返しを行う

まずは簡単な文で for 文の機能を見てみたいと思います。
インデントは4つ分のスペース のことです。 TAB キーで作成できます。
タブキーのインデント化

from __future__ import print_function

for i in range(3):
	print('繰り返し')

print('繰り返さない')

上記のコードを実行すると「繰り返し」が 3回出力され、「繰り返さない」が 1回出力されるはずです。range 関数の引数を 3 にして for 文を使うと3回繰り返されます。数字を増やせばそれだけ繰り返される回数も増えます。「繰り返さない」の文はインデントがないため、for 文を抜けて1回しか実行されません。

POINT
  • TAB キーでインデント
  • range 関数で for 文を使うと任意の回数で繰り返しができる

for 文のループ内での変数の扱い

for 文における変数の扱いについてみていきます。
次は range 型ではなく list 型で for 文を使ってみます。list 型も複数の値を持ちますので、for 文でループさせることができます。

from __future__ import print_function

print('\n\n\n-------------------------------------\n\n')

test_list = [ 1, 'two', 3, 'four', 5 ]

for i in test_list :
    
    print( i )

print( 'ここからループを抜ける' )
print( i )

「 '\n' 」は改行です。出力された文を見やすくするために冒頭で改行とハイフンを出しています。

見やすいようにしている文を除外して解説します。今回重要なのは下記の部分です。

リストの中の値が順番に変数に代入される

変数 i はループの毎に test_list の中身がひとつずつ順番に代入されています。そのため、ループを抜けた後は最後にループした時の値になっています。

  • 変数は自由自在に何度でも値を代入できる
  • 代入したとき、古い値は消える

では、下記のようなコードではどうなるでしょうか?
結果を予想してからコードを実行してみてください。

from __future__ import print_function

print('\n\n\n-------------------------------------\n\n')

num = 10

for i in range(10) :
    
    print( num + i )

print( 'ここからループを抜ける' )
print( i )
print( num )

予想通りの結果になりましたでしょうか?
for 文の外で作られた変数 num の挙動がポイントです。

ここまでくると冒頭のコードが読めるはずです。同じ流れで作られる値を、関数の引数として使用しています。

import maya.cmds as cmds

for i in range(10):
    size = i + 1
    cmds.polyCube(w=size, h=size, d=size)

cmds.polyCone(h=size, r=size/2, sh=0)

変数 size で + 1 しているのは range 関数が 0 からの連番だからです。サイズが 0 のキューブを作らないための + 1 です。

上記のコードの range 関数の引数を変えるとそれに合わせてキューブ数や大きさ、コーンの大きさが変わります。

スクリプトのテスト用シーンを作る

for 文を使うとテスト用のシーンデータをすぐに作れます。今回はライトを大量に置くスクリプトを組んでみました。
下記のコードを実行すると range 関数で渡した数だけライトが作られます。

import maya.cmds as cmds

for i in range(10):
	
	light = cmds.spotLight( )
	distance = i * 2
	cmds.move( distance, 0, 0, light )

cube = cmds.polyCube( w=distance + 5, h=10 )
cmds.move( distance/2, 0, -2, cube )

renge の値を変更すると作成されるライトやキューブの幅が変わります。

Viewport のオプションから Light Limit の値を増やして 7キーでライト表示にしておくとわかりやすいと思います。

コードに関してですが、for 文に関しては前半のものと同じような作り方をしています。しかし、何点かこれまでとは違った書き方をしている箇所があるので解説していきます。

spotLight コマンド

スポットライトを作成するコマンドはそのまま spotLight コマンドです。手動でスポットライトを作った場合、スクリプトエディタには「defaultSportLight」と出てきますが、そんなコマンドはありません。スクリプトエディタにはたまに存在しないコマンドが表示されます
そんなときはネットで「 maya python スポットライト 」と検索すればコマンド名やヒントが出てきてくれます。

作成系コマンドの戻り値

今まで何度も polyCube コマンドを利用していきましたが、このコマンドには戻り値があります。

コマンドリファレンスで polyCube の項目を見てみましょう。

上の方にある戻り値の項目に、「 string[] オブジェクト名とノード名 」とあります。とりあえず戻り値の中身を見てみます。

from __future__ import print_function
import maya.cmds as cmds

cube = cmds.polyCube(w=10, h=10, d=10)
print( cube )

作成されたトランフォームノードと同時に作成された polyCube ノードの名前が返ってきます。作られたノードを選択して Hypergraph Connection を開くと、返ってきているノードがどれかがわかります。

polyCube に限らず、スフィアやコーン、ライトを作った場合も作成されたノードが返ってきます。
そして返ってきた値を move コマンドの引数として利用しています。

move コマンド

move コマンドは任意のトランスフォームノードを移動させることができるコマンドです。このコマンドは引数の渡し方がこれまでのものと違います。

cmds.move( distance, 0, 0, light )

上記のコードには フラグがありません。コマンドリファレンスで move コマンドを調べてみます。

今回確認したいのは概要欄の一番上です。

ここはフラグや引数の渡し方、つまりこのコマンドの記述方法が書かれています。このさらに一番冒頭の部分で簡単に上記の部分が説明されています。

記載のされ方がプログラマー向けになっていますが、ここと併せて一番下のサンプルコードを見ることでコマンドの使い方がわかるようになっています。

フラグを指定する際に順番は関係ありませんが、引数には渡す際に順番があります。そのため第一引数、第二引数、と番号で呼ばれることがあります。

例えば move コマンドの場合、移動値の前に対象のノードを指定するとエラーが起こります。

import maya.cmds as cmds
sphere = cmds.polySphere(r=10)
cmds.move(sphere, 2,4,10)

ですが、Y軸の移動値やZ軸の移動値を省くことは可能です。下記のコードの場合、第一引数の 20 はX軸の移動値と受け取られ、第二引数の sphere は対象物と受け取られます。

import maya.cmds as cmds
sphere = cmds.polySphere(r=10)
cmds.move(20,sphere)

概要欄にある通り、フラグの指定は引数のあとに続いて記載していきます。

import maya.cmds as cmds
sphere = cmds.polySphere(r=10)
cmds.move(20,sphere, relative=True)

このあたり記述方法がややこしいですが、慣れです。このコマンドはこんな感じで値を渡しそうだ、というのがなんとなく掴めるようになっていきます。
(それでも未だに「何この渡し方」っていうコマンドがあったりします。最近だと uvLink コマンドとか)

POINT
  • フラグのない引数には順序のルールがある
  • 引数の設定方法が特殊なコマンドがある

一括で処理を行うスクリプトを組む

for 文の典型的な使い方が、選択しているノードに対して一括で処理を行う、というものです。
今回は作成したテストシーンで、選択したライトの値を一括で変更するスクリプトを組もうと思います。

選択しているノードを取得する | ls コマンド

まずはここからです。現在選択中のノードを取得します。
選択しているノードを取得するには、「 ls コマンド」という cmds コマンドの関数を使います。コマンドリファレンスで ls コマンドを開いてみてください。

恐らくほとんどの方にとって、この ls コマンドは最も使用頻度の高いコマンド になるはずです。スクリプトはこれがないと始まらない、と言っても過言ではないくらいです。

from __future__ import print_function
import maya.cmds as cmds

targetNode_list = cmds.ls(sl=True)
print( targetNode_list ) 

上記のコードを実行すると選択しているノードがスクリプトエディタに出力されるはずです。

これを for 文でループさせれば、選択しているノードに対して一括で処理を行うことができます。

from __future__ import print_function
import maya.cmds as cmds

targetNode_list = cmds.ls(sl=True)

for targetNode in targetNode_list :
    print( targetNode )

値を設定する | setAttr コマンド

値を変更するためのコマンドを探します。
手動でスポットライトの intensity の値を変更した場合、下記の MEL コードがスクリプトエディタへ出力されます。

setAttr "spotLightShape1.intensity" 1.823204;

では、コマンドリファレンスで setAttr コマンドを調べましょう。この setAttr コマンドも実務で python を扱う上で欠かせないコマンドです。

setAttr コマンドのページは長い表がついていて複雑そうに見えますが、基本的な扱い方であればページから受ける印象よりもシンプルです。
リファレンスのサンプルコードを見ながら解説します。

import maya.cmds as cmds

cmds.sphere( n="sphere" )
cmds.setAttr( 'sphere.translateX', 5 )

サンプルの上の方にある数行の実行コードを抜き出しました。これは NURBS のスフィアを作成し、Xの移動値に 5 を設定する、というコードです。
( polySphere だとポリゴンのスフィア、sphere だと NURBS のスフィアが作成されます )

上記のページで、ピリオドは「~の」と訳す というお話をしました。setAttr の文を見るとそのままです。

第一引数でどのノードのどのアトリビュートを対象にするのかを指定し、第二引数でそのアトリビュートに設定する値を渡します。

一括で値を設定する

既に選択しているノードをリストにすることができています。あとは for 文でループさせて上記の setAttr を行えばいいだけです。

import maya.cmds as cmds

targetNode_list = cmds.ls(sl=True)

for targetNode in targetNode_list:
	cmds.setAttr(targetNode + '.intensity', 50)

上記のコードで選択したスポットライトの intensity の 値を 50 に一括で設定できます。ここまでの復習を兼ねて図解していきます。

CUI を作る

このコードを CUI 化してみます。CUI(キャラクターユーザインターフェース)とは、プログラミングで行う UI のことです。要はもっと汎用性があって扱いやすいコードにする、ということです。このコードだと下記のことができれば色々と応用が利きそうです。

  • 他のアトリビュートでも使いやすくする
  • 他の値でも設定しやすくする
  • undo を一回で行えるようにする

まずは上二つから始めます。
方法としては該当の部分を変数に置き換え、コードの冒頭部分で指定できるようにします。

import maya.cmds as cmds

value = 50
attribute = 'intensity'
targetNode_list = cmds.ls(sl=True)


for targetNode in targetNode_list:
    cmds.setAttr(targetNode + '.' + attribute, value)

9行目の値を変数にし、コードの冒頭でその変数を指定できるようにしました。これだけのことですが、随分使いやすくなっていると思います。

例えば Cone Angle の値を 70 で揃えたいと思ったとき。
手動で Cone Angle を操作してアトリビュート名を確認、3行目と 4行目を変更すれば一括で設定することができます。

import maya.cmds as cmds

value = 70
attribute = 'coneAngle'
targetNode_list = cmds.ls(sl=True)


for targetNode in targetNode_list:
    cmds.setAttr(targetNode + '.' + attribute, value)

他のアトリビュートにも同様の方法で一括設定が可能になっています。3行目と 4行目を書き換えて試してみてください。

undoInfo コマンド

一括で処理を行えるようになりましたが、このままではこのスクリプトには決定的な欠点が残っています。

スクリプトを実行した後にctrl + Zを押していただくとわかりますが、コードで行った作業を全て取り消すには、for 文で繰り返した回数分 ctrl + Z を押す必要があります。これを1回で取り消せるようにします。

undoInfo コマンドを2行追加するだけです。

import maya.cmds as cmds


value = 70
attribute = 'coneAngle'
targetNode_list = cmds.ls(sl=True)


cmds.undoInfo(openChunk=True)

for targetNode in targetNode_list:
    cmds.setAttr(targetNode + '.' + attribute, value)

cmds.undoInfo(closeChunk=True)

9行目と14行目に undoInfo コマンドを書きました。このコマンドは undo 情報を管理できるコマンドです。
使い方は openChunk フラグを指定した undoInfoコマンドと closeChunk フラグを指定した undoInfo コマンドで文章を挟みます。すると挟まれた部分が 1回分の Undo として記録されます。

シーン内のすべてのスポットライトを対象にする

選択すら行わず、コードを実行するだけでシーン内にあるすべてのスポットライトに対して一括で処理することも可能です。
ls コマンドのフラグを変更します。

import maya.cmds as cmds

value = 10
attribute = 'penumbraAngle'
targetNode_list = cmds.ls(type='spotLight')


cmds.undoInfo(openChunk=True)

for targetNode in targetNode_list:
    cmds.setAttr(targetNode + '.' + attribute, value)

cmds.undoInfo(closeChunk=True)

ls コマンドの type フラグにノードタイプを文字列で指定すると、そのタイプのノードに対して一括で処理を行うことができます。ノードタイプはアトリビュートエディタで確認できます。

上記のコードではシーン内にある、ノードタイプが「 spotLight 」となっているすべてのノードが 変数 targetNode_list に代入されています。

from __future__ import print_function
import maya.cmds as cmds

targetNode_list = cmds.ls(type='spotLight')

for targetNode in targetNode_list :
    print( targetNode )

変数の中身を確認したいときは print 関数を使います。

他の種類の値を設定する

ここまで setAttr で 単体の数値 を設定してきました。
しかし、複数の値を設定しようとした場合、 setAttr コマンドの文でエラーがでます。

例えば、スポットライトの色を一括で変えようと思ったとき。
色の値は RGB の 3つの値を渡す必要があります。そのため今回組んだスクリプトだと setAttr の文で エラーが発生します。

複数の値を設定できる CUI

複数の値を渡すには move コマンドと同様、引数を増やします。コマンドリファレンスのサンプルコードに似たようなものがあります。

cmds.setAttr('sphere.rotate', 0, 45, 90, type="double3")

これは回転値の値を設定する文になっています。move コマンドのように引数を増やして渡していることがわかります。
( type フラグに関しては後述します )

これを踏まえてライトのカラーを変更できる CUI スクリプトを組んでみます。
下記はシーン内のスポットライトのカラーの値を一括で変更します。 value_1 に R値、value_2 に G値、value_3 に B値を設定します。

import maya.cmds as cmds

value_1 = 0
value_2 = 0.5
value_3 = 1
attribute = 'color'
targetNode_list = cmds.ls(type='spotLight')


cmds.undoInfo(openChunk=True)

for targetNode in targetNode_list:
    cmds.setAttr(targetNode + '.' + attribute, value_1, value_2, value_3 )

cmds.undoInfo(closeChunk=True)

これで 3つの値を設定できるコードができました。サンプルにあった回転値( XYZ )の値もこれで設定できるようになっています。

単体の値でも複数の値でも設定できる CUI

値の数によって実行するスクリプトを変えないといけない、とうのは不便ですよね。少し発展させて、単体でも複数でも値を設定できる CUI を組んでみたいと思います。

方法としては、リスト型の性質を利用します。

from __future__ import print_function
list = [0, 'one', 2]
print(*list)

上記のコードを実行すると、リストが展開されていることがわかります。これを リストのアンパック といいます。アンパックされたリストは中身をひとつずつ取り出された状態になります。

POINT
  • 「 * 」をリストの前につけるとリストをアンパックできる

関数内でアンパックされたリストを使用すると、それぞれの値を引数として指定できます。
そのため、下記のようなコードにすれば値の数に関わらず同じコードが使えます。

import maya.cmds as cmds

value_list = [1, 0.5, 1]
attribute = 'color'
targetNode_list = cmds.ls(type='spotLight')


cmds.undoInfo(openChunk=True)

for targetNode in targetNode_list:
    cmds.setAttr(targetNode + '.' + attribute, *value_list)

cmds.undoInfo(closeChunk=True)

3行目の value をリスト型にし、11行目でこの変数を使う際にアンパックして使っています。

注意点としては、単体の値であってもリストで渡す必要があります。

import maya.cmds as cmds

value_list = [10]
attribute = 'coneAngle'
targetNode_list = cmds.ls(type='spotLight')


cmds.undoInfo(openChunk=True)

for targetNode in targetNode_list:
    cmds.setAttr(targetNode + '.' + attribute, *value_list)

cmds.undoInfo(closeChunk=True)

3行目の value_list です。アンパックは複数の値を持てるデータ型にしか使えません。int や float でアンパックしようとするとエラーが起きますので、単体の値であってもリスト型で渡してください。

文字列を渡す場合の注意点

文字列型を渡す際は 「 type = 'string' 」を渡してください。

コマンドリファレンスの setAttr の type フラグの説明にあはこうあります。

type フラグが指定されていない場合は、数値型と想定されます。

maya2018 コマンドリファレンス | setAttr

type フラグを設定していない場合、渡している文字列が数値型と認識されるためエラーが起こります。

例えばファイルノードのパスを設定したい場合は次のコードになります。
※ 選択中のファイルノードのテクスチャパスを書き換えます。必ず新規のファイルノードを作るか、不要なファイルノードを選択して実行してください。

import maya.cmds as cmds

path = 'test'
targetNode_list = cmds.ls(sl=True)
for targetNode in targetNode_list:

    cmds.setAttr(targetNode + '.fileTextureName', path, type='string')

type = 'string' を消すとエラーで止まるはずです。

まとめ

今回は for文でループさせる方法についてみてきました。複雑なコードになってきましたが、できることは格段に広がっているのではないでしょうか。複雑だと感じたコードは一単語ずつ、丁寧にみていくことが重要です。

今回も練習問題と応用問題を置いておきます。練習問題に関しては、今回制作したスクリプトの意図を理解できれば数秒で終わります。

練習問題 Render Stats

選択したメッシュノードの Render Stats の Double Sided のチェックボックスを一括で制御してみてください。

Render Stats は手動ではどうやっても一括での設定ができない項目です。スクリプトを組めるとこういったアトリビュートに対しても一括処理ができます。

応用問題 アウトライナの色を変える

アウトライナの項目は色を変えられることをご存じでしょうか?
選択したノードに対して、アウトライナ上の文字の色を変更するスクリプトを組んでください。
また、元の状態に戻せるスクリプトも組んでください。

-Maya Python
-