選択・取得
3ds Max Python チートシート の章一覧
ほとんどのスクリプトは「対象のノードを集める」ところから始まります。
Python では list にしてから内包表記で絞り込む のが一番読みやすい書き方です。
全部・現在の選択を取得する
all_nodes = list(rt.objects) # MXS: objects(シーンの全ノード)
sel = list(rt.selection) # MXS: selection(現在の選択)
first = rt.selection[0] # MXS: selection[1](先頭の 1 つ)
count = len(rt.selection) # MXS: selection.count
rt.objectsやrt.selectionは MaxScript の ObjectSet です。list()で Python のリストにしておく と、あとで選択やシーンが変わっても影響を受けず、内包表記やスライスも使えます。- 何も選んでいないときは
rt.selection[0]で先頭を取れません。先にlen(rt.selection)で空でないことを確かめます。
種類ごとのコレクション
geos = list(rt.geometry) # MXS: geometry(ジオメトリ)
shps = list(rt.shapes) # MXS: shapes(スプライン等)
lts = list(rt.lights) # MXS: lights
cams = list(rt.cameras) # MXS: cameras
hlps = list(rt.helpers) # MXS: helpers
- ⚠️
rt.lights/rt.camerasには、ライトやカメラ本体だけでなく ターゲット(注視点)オブジェクト も含まれます。本体だけに絞りたいときはrt.superClassOf(o) == rt.Lightのように調べます。
名前で取得する
b = rt.getNodeByName("Box001") # MXS: $Box001 / getNodeByName "Box001"
b2 = rt.getNodeByName("Box001", ignoreCase=False) # 大文字小文字を区別する(既定は区別しない)
same = rt.getNodeByName("Box001", all=True) # 同名のノードをすべて(配列)
- 見つからないときは
Noneが返ります。if b is None:で確かめてから使います。 ignoreCase(既定True)のほかにexact(既定False)という引数もあります。名前の一致条件を細かく指定したいときに使います。- ⚠️ 3ds Max では 同じ名前のノードが複数ある ことがあります。確実に全部欲しいときは
all=Trueを使います。
名前のパターンでまとめて取得する
import fnmatch
import re
boxes = [o for o in rt.objects if fnmatch.fnmatch(o.name, "Box*")] # MXS: $Box* as array
tagged = [o for o in rt.objects if re.search(r"\d{3}$", o.name)] # 正規表現も使える
by_mxs = list(rt.execute("$*00? as array")) # MaxScript のワイルドカード
- Python の
fnmatch/reで自由に絞り込めます。reは大文字小文字を区別します(MaxScript のワイルドカードは区別しません)。fnmatch.fnmatchは Windows では区別しないので、区別したいときはfnmatch.fnmatchcaseを使います。区別したくなければre.IGNORECASEを使うかo.name.lower()と比べます。 o.nameは Python のstrとして返ってきます。
選択を操作する
b = rt.getNodeByName("Box001")
s = rt.getNodeByName("Sphere001")
rt.select(b) # MXS: select b(選択を置き換え)
rt.selectMore(s) # MXS: selectMore s(追加)
rt.deselect(b) # MXS: deselect b(外す)
rt.clearSelection() # MXS: clearSelection()
rt.select([b, s]) # MXS: select #(b, s)(リストで複数を選べる)
rt.select(rt.objects) # MXS: select objects(全部選ぶ)
rt.execute("max select invert") # MXS: max select invert(選択の反転)
rt.selectには 1 つのノードでも Python のlistでも渡せます。- 反転のように
maxコマンドにしかない操作はrt.executeで呼びます。max ...はメニュー操作などを呼び出す MaxScript のコマンドです(「MaxScript との連携」章参照)。
種類(クラス)で絞り込む
geos = [o for o in rt.objects if rt.superClassOf(o) == rt.GeometryClass] # ジオメトリだけ
lights = [o for o in rt.objects if rt.superClassOf(o) == rt.Light] # ライトだけ
boxes = [o for o in rt.objects if rt.classOf(o) == rt.Box] # Box だけ
polys = [o for o in rt.objects if rt.classOf(o.baseObject) == rt.Editable_Poly]
convertible = [o for o in rt.objects if rt.canConvertTo(o, rt.Editable_Mesh)]
classOfはクラス(Box)、superClassOfは大分類(GeometryClass/Shape/Light/Camera/Helper)を返します。比べるのは==です。- ⚠️
classOf(ノード)は モディファイヤを適用した後の結果 のクラスを返します。元のオブジェクトの種類を見たいときはo.baseObjectのクラスを調べます。
条件で絞り込む(表示・レイヤ・マテリアルなど)
visible = [o for o in rt.objects if not o.isHidden] # 表示中
unfrozen = [o for o in rt.objects if not o.isFrozen] # フリーズしていない
on_default = [o for o in rt.objects if o.layer.name == "0"] # レイヤ "0"
no_mat = [o for o in rt.objects if o.material is None] # マテリアル未割り当て
heavy = [o for o in rt.geometry if rt.getPolygonCount(o)[0] > 500] # 面数が 500 超(ジオメトリ以外は面を持たないので rt.geometry を使う)
getPolygonCountは[面数, 頂点数]の配列を返します。Python から読むので 0 始まり([0]が面数)です。
名前付き選択セット
nodes = [rt.getNodeByName("Box001"), rt.getNodeByName("Sphere001")]
rt.myToolNodes = rt.Array(*nodes) # MaxScript の配列にしてから入れる(list のままだと使えない)
rt.execute('selectionSets["MyToolSet"] = myToolNodes') # 作成・上書き
ss = rt.execute('selectionSets["MyToolSet"]') # 取得
names = [o.name for o in ss] # 中身
all_sets = [rt.selectionSets[i].name for i in range(rt.selectionSets.count)]
- 名前付き選択セットは
selectionSets["名前"]で読み書きします。pymxs の添字は番号(0 始まり)なので、名前で指定するときは MaxScript の書き方を使い、rt.executeとグローバル変数を組み合わせます。 rt.selectionSets[i]のように 番号で読むときは 0 始まり です。
ループ中に選択を変える / 削除するときの定石
targets = [o for o in rt.objects if o.name.startswith("Sphere")]
rt.delete(targets) # まとめて削除(MXS: delete targets)
for o in list(rt.selection): # 先に list() でコピーしてから回す
rt.deselect(o)
- ⚠️
rt.selectionやrt.objectsを 直接回しながら削除・選択変更すると、途中で中身が変わって取りこぼします。list()でコピーしてから回します。 - 削除済みのノードを触るとエラーになります。持っているノードがまだ有効かは
rt.isValidNode(o)で確かめられます。 - 単に選択を全部外したいだけなら
rt.clearSelection()で十分です。ここではループ中に選択を変える例としてdeselectを使っています。
落とし穴まとめ
rt.objects/rt.selectionはまずlist()にする。直接回しながら変更しない。- 同じノードかは
==で比べる(isは使わない)。pymxs は同じノードでも読むたびに別のラッパーオブジェクトを返すことがあるためです。 classOf(ノード)はモディファイヤ適用後のクラス。元の種類はo.baseObjectで見る。getNodeByNameは見つからないとNone。同名ノードがありえるならall=True。
関連
- 集めたノードを動かす: 「変形・配置」章
- 表示やフリーズの状態を変える: 「表示・名前・色・ユーザ定義」章
- ユーザ定義プロパティで目印を付け、そのノードだけを集める: 「表示・名前・色・ユーザ定義」章
コメント