MODOではじめるPython_07 MODOで書いてみる

mihi
こんにちは、mihiです。
少し時間が空いてしまいましたが、
前回まではMODOでスクリプトを書く上での知っておいてほしい
Pythonの超基本の解説をしてきました。
今回からは、MODOでPythonスクリプトを書いていこうと思います。

MODOでPythonスクリプトを書いてみる

では、実際にMODOでスクリプトを書いてみましょう。
まずは「おまじない」だと思って、以下の通りの2行を書きます。

#python
import lx

1行目の「#python」は、”このスクリプトはPythonで書いていますよ”とMODOに知らせるために記述します。

2行目の「import lx」は、PythonからMODOを操作するために必要な機能の追加パックをインポートしています。
Pythonには直接MODOを操作するための機能というのは、用意されていません。
そこで、MODOがPythonにMODOを操作できるようにするための機能の詰め合わせパックを用意しています。
この機能の詰め合わせパックのことを「ライブラリ」と言います。
「import lx」は、MODOがPython用に用意している「lx」というライブラリをインポートする。という意味です。

立方体をスクリプトで作ってみる

MODOでスクリプトを書く初歩として、
「lx.eval()」を使ってコマンドを実行させるという方法から紹介します。

MODOはほとんどの操作に対して、コマンドが発行されています。
ショートカット「F5」を押して、「コマンド履歴」を表示します。

次にメニューバーから「形状」→「単位プリミティブ」→「新規メッシュに」→「立方体」を選択して、
単位立方体(ボックス)を作成します。

コマンド履歴を見てみます。

履歴の一番下に「単位立方体アイテム」と履歴が出ています。
以下画像のように▶を開いて中を見てみましょう。

すると、「レイヤーの作成」から「アイテム名称」まで、
下に向かってずらっと立方体を作成するためのコマンドが並んでいます。

このコマンドをスクリプトに書くことで、Pythonでもボックスを作成することができます。

lx.eval()

先ほどのコマンド履歴に表示されているコマンドをPythonスクリプトから実行するために、
MODOが用意している「lx」ライブラリの機能として「eval()」という機能があります。
お察しのように「eval()」は関数で、引数に実行したいコマンドを渡します

ライブラリの関数を実行するには、
基本的に、「ライブラリ名」.「関数名」とすることで、ライブラリの関数を実行できます。

まずは新規レイヤーを作成するコマンドの「layer.new」を記述してみます。

#python
import lx

lx.eval('layer.new')

「lx.eval()」関数の引数は「文字列」と決まっています。
ですので、コマンド履歴にある「layer.new」を「lx.eval()」の引数として渡す際には、
コマンドを「”」か「’」で囲む必要があります。
自分は「’」(シングルクォーテーション)を好んで使用しています。

スクリプトを実行してみると、新規レイヤーが作成されたはずです。

この調子で、コマンド履歴のコマンドを1行ずつ「lx.eval()」で実行していくことで、
単位立方体が作成できます。

では、記述してみましょう。

  1. コマンド履歴でコマンドを選択する。
  2. コマンドをコピーする。
  3. 「lx.eval()」関数の引数にペーストする。

この手順で、コマンド履歴の「アイテム名称」までをコピペしていきます。
関数の引数にペーストした際、引数を「’」で括るのを忘れないように注意してください。

#python
import lx

lx.eval('layer.new')
lx.eval('tool.set prim.cube on 0')
lx.eval('tool.reset prim.cube')

lx.eval('tool.setAttr prim.cube cenX 0.0')
lx.eval('tool.setAttr prim.cube cenY 0.0')
lx.eval('tool.setAttr prim.cube cenZ 0.0')

lx.eval('tool.setAttr prim.cube sizeX 1.0')
lx.eval('tool.setAttr prim.cube sizeY 1.0')
lx.eval('tool.setAttr prim.cube sizeZ 1.0')

lx.eval('tool.setAttr prim.cube segmentsX 1')
lx.eval('tool.setAttr prim.cube segmentsY 1')
lx.eval('tool.setAttr prim.cube segmentsZ 1')

lx.eval('tool.setAttr prim.cube radius 0.0')

lx.eval('tool.apply')
lx.eval('tool.set prim.cube off 0')
lx.eval('item.name Cube mesh')

以上のように、コマンドを「lx.eval()」関数によって上から順番にスクリプトで実行させます。
スクリプトを実行してみると、ボックスが作成されます。

コマンドの一覧を調べるために初めに作成した「Cube」に重なってしまうので、
初めに作った「Cube」はレイヤーをオフにしてきましょう。

スクリプトらしく!

スクリプトでボックスを作成することはできましたが、
これでは「マクロ」と変わりありません。
実際、単位立方体を作成するMODOの機能も「マクロ」が実行されています。

そこで、一気に1mのボックスを10個作成して2m間隔でX方向へ並べてみます。

スクリプトの処理方法の考え方

  1. 1mのボックスを指定したX座標の位置に作る
  2. 「for文」で繰り返し処理を行いボックスを10個作成する

という処理が考えられます。
この時、「for文」でボックスを繰り返し処理して作成する際に、
「X座標」を変化させることが、このスクリプトのミソです。

range()関数

このスクリプトを書くにあたり、新しい関数「range()」を紹介します。
この関数はPythonの組み込み関数で、
引数に指定した数の要素を含む配列を作成する関数です。

たとえば、

array = range(10)

とした場合、変数「array」には「0から9」までのint型の要素が入った配列が作成されます。

10個の要素の配列を「for文」で繰り返し処理することで、10回処理が繰り返され、
ボックスが10個作成できるという仕組みです。

for文でボックスを10個作る

では、先ほどのスクリプトに「for文」を追加します。

#python
import lx

array = range(10)

for i in array:

	lx.eval('layer.new')
	lx.eval('tool.set prim.cube on 0')
	lx.eval('tool.reset prim.cube')
	
	lx.eval('tool.setAttr prim.cube cenX 0.0')
	lx.eval('tool.setAttr prim.cube cenY 0.0')
	lx.eval('tool.setAttr prim.cube cenZ 0.0')
	
	lx.eval('tool.setAttr prim.cube sizeX 1.0')
	lx.eval('tool.setAttr prim.cube sizeY 1.0')
	lx.eval('tool.setAttr prim.cube sizeZ 1.0')
	
	lx.eval('tool.setAttr prim.cube segmentsX 1')
	lx.eval('tool.setAttr prim.cube segmentsY 1')
	lx.eval('tool.setAttr prim.cube segmentsZ 1')
	
	lx.eval('tool.setAttr prim.cube radius 0.0')
	
	lx.eval('tool.apply')
	lx.eval('tool.set prim.cube off 0')
	lx.eval('item.name Cube mesh')

まずは変数「array」を宣言し、
「range()」関数で引数に「10」を渡し、
10個の要素の配列を作成します。

「for文」で、変数「i」を宣言して、先ほど作成した「array」をセットします。
以下にボックスを作成する処理を記述します。

「for文」のルールを思い出してください。
「for文」は繰り返し処理をする範囲は「インデント」で「字下げ」がルールーでしたね。
ですので、ボックスを作る処理の部分はインデントによる字下げでブロックを定義します。

まとめて字下げしたい場合は上記画像の通り、
字下げしたいブロックをまとめて字下げできます。

一番最終行の「lx.eval(‘item.name Cube mesh’)」の部分を、
「lx.eval(‘item.name Box mesh’)」と変更しておきます。
こうすると、作成されるメッシュレイヤーの名称が「Box」になります。

スクリプトを実行すると、ボックスが10個作成されていれば、成功です!

ですが、X方向にずらす処理をしていないので、同じ位置にボックスが10個出来上がてしまいました。
一度スクリプトの実行を「取り消し」(Undo)(Ctrl+Z)して
ボックスを作成する前に戻ります。

X方向へ2m間隔でボックスを作成するためには、
「for文」によって繰り返しボックスを作成する際に、
ボックスが作成される「X座標」を2mずつ増やしていかなければいけません。
ですので、スクリプトにもう一工夫必要です。

考え方は、「for文」の変数「i」の値に注目します。

「for文」は指定された配列の要素の「最初から最後まで」の値を、
宣言された変数へ順次代入しながら繰り返し処理を行います。

ですので今回の場合、変数「i」の値は

1回目→「0」
2回目→「1」
3回目→「2」



10回目→「9」

と変化していきます。

ということは、変数「i」に「2」を掛けていけば、

1回目→「0m」
2回目→「2m」
3回目→「4m」



10回目→「18m」

とすることができます。

この値を使って、
「lx.eval(‘tool.setAttr prim.cube cenX 0.0’)」
の「0.0」の部分をスクリプトで変化させてあげれば、
2mずつボックスをずらして作成することができます。

%s 演算子

感の良い方なら、「0.0」の部分に「i * 2」とすれば良いじゃないか。
と気づかれると思います。
ですが、「lx.eval()」の引数は「文字列」でなければなりません。

lx.eval('tool.setAttr prim.cube cenX i * 2')

と書いた場合、これはアウトです。
なぜなら、「’」で囲われている部分はすべて文字列なので、
「i」は変数とはみなされませんし、「i * 2」も数式として処理されません。
すべて文字として扱われます。
なので、MODOからすると、そんなコマンドは無いよ!
ってことでエラーとなります。

じゃあ、

lx.eval('tool.setAttr prim.cube cenX ' + i * 2)

とすればどうかというと、これもアウトです。
文字列と数値は足し算ができないからです。

じゃあどうするか?
方法はいくつかありますが、最もスマートなのが「%s」演算子を使います。

まずは以下のように置き換えます。

lx.eval('tool.setAttr prim.cube cenX %s')

もともと「0.0」だった部分を「%s」と置き換えます。
続けて、以下のように追記します。

lx.eval('tool.setAttr prim.cube cenX %s' %(i * 2))

「’」の後に「%(i * 2)」とします。

このように「%s」演算子を利用すると、
文字列の中に書いた「%s」の部分が、
文字列の後ろに書いた「%()」の内容に「文字列」として置き換えてくれます。

ですので、上記の場合は変数「i」の値が「1」だった場合、
「%s」には「文字列」として「2」と置き換えられます。

すごく便利ですね!

最終的なスクリプトは、以下のようになります。

#python
import lx

array = range(10)

for i in array:

	lx.eval('layer.new')
	lx.eval('tool.set prim.cube on 0')
	lx.eval('tool.reset prim.cube')
	
	lx.eval('tool.setAttr prim.cube cenX %s' %(i * 2))
	lx.eval('tool.setAttr prim.cube cenY 0.0')
	lx.eval('tool.setAttr prim.cube cenZ 0.0')
	
	lx.eval('tool.setAttr prim.cube sizeX 1.0')
	lx.eval('tool.setAttr prim.cube sizeY 1.0')
	lx.eval('tool.setAttr prim.cube sizeZ 1.0')
	
	lx.eval('tool.setAttr prim.cube segmentsX 1')
	lx.eval('tool.setAttr prim.cube segmentsY 1')
	lx.eval('tool.setAttr prim.cube segmentsZ 1')
	
	lx.eval('tool.setAttr prim.cube radius 0.0')
	
	lx.eval('tool.apply')
	lx.eval('tool.set prim.cube off 0')
	lx.eval('item.name Box mesh')

スクリプトを実行すると、2m間隔でボックスが作成されていれば成功です!

ちなみにですが、

array = range(10)

として、先に配列を作成していますが、
実は、「for文」に直接以下のように書いてもかまいません。

for i in range(10):
#python
import lx

for i in range(10):

	lx.eval('layer.new')
	lx.eval('tool.set prim.cube on 0')
	lx.eval('tool.reset prim.cube')
	
	lx.eval('tool.setAttr prim.cube cenX %s' %(i * 2))
	lx.eval('tool.setAttr prim.cube cenY 0.0')
	lx.eval('tool.setAttr prim.cube cenZ 0.0')
	
	lx.eval('tool.setAttr prim.cube sizeX 1.0')
	lx.eval('tool.setAttr prim.cube sizeY 1.0')
	lx.eval('tool.setAttr prim.cube sizeZ 1.0')
	
	lx.eval('tool.setAttr prim.cube segmentsX 1')
	lx.eval('tool.setAttr prim.cube segmentsY 1')
	lx.eval('tool.setAttr prim.cube segmentsZ 1')
	
	lx.eval('tool.setAttr prim.cube radius 0.0')
	
	lx.eval('tool.apply')
	lx.eval('tool.set prim.cube off 0')
	lx.eval('item.name Box mesh')

変数の宣言を省略できて、さらにスマートですね。


mihi

いかがでしょうか?

「%s」演算子がすこし難しく感じられたかもしれません。
「%s」演算子はよく使いますので、挙動をよく確認してみてください。

lx.eval(‘tool.setAttr prim.cube sizeY 1.0’)

の「1.0」の部分もいじると、Y方向のサイズを変更しながら作成なども可能ですね!

>Profile

Profile

■mihi■

愛猫家

フリーランスとして建築CGを制作しています。

「MODO」と「3dsMax」を使用した
建築CGのテクニックや
日々の雑記を発信していきます。

よろしくお願いいたします。

CTR IMG