リファレンス / 3ds Max / 3ds Max Python チートシート

選択・取得

  • 3dsMax
  • Python
  • pymxs
  • チートシート
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。

関連

← 3ds Max Python チートシート へ