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

階層・グループ・レイヤ

  • 3dsMax
  • Python
  • pymxs
  • チートシート
3ds Max Python チートシート の章一覧

シーンを整理するための 3 つの仕組み(親子リンク・グループ・レイヤ)の操作です。 階層をたどる処理は、Python の 再帰関数やジェネレータ で書くとすっきりします。

親子リンク

parent = rt.getNodeByName("Box001")
child = rt.getNodeByName("Sphere001")

child.parent = parent            # MXS: child.parent = parent(リンク)
child.parent                     # 親ノード(無ければ None)
child.parent = None              # MXS: child.parent = undefined(リンク解除)
  • リンクしても ワールド上の位置は変わりません。子の pos はワールド座標のまま読み書きできます。
  • 親から見た変換行列(ローカルの変換行列。Matrix3)が欲しいときは、child.transform * rt.inverse(parent.transform) で計算します。位置だけなら、その .row4(または .translation)を見ます。rt.execute("in coordsys parent $Sphere001.pos") でも同じ値が取れます。

子を取得する / 階層をたどる

root = rt.getNodeByName("Box001")
for n in [rt.getNodeByName("Sphere001"), rt.getNodeByName("Teapot001")]:
    n.parent = root

kids = list(root.children)                       # 直下の子(MXS: root.children)

def descendants(node):
    """子孫をすべて返す(深さ優先)。"""
    for c in node.children:
        yield c
        yield from descendants(c)

def top_of(node):
    """階層の一番上のノード。"""
    while node.parent is not None:
        node = node.parent
    return node

all_below = list(descendants(root))
top = top_of(rt.getNodeByName("Teapot001"))
top_level = [o for o in rt.objects if o.parent is None]     # 親を持たないノード
  • ノード同士が同じかどうかは == で比較します(top == root のように)。is だと同じ Python オブジェクトかどうかの判定になり、期待通りに動かないことがあります。
  • シーン全体を木構造でたどるときは、見えない根ノード rt.rootNode の children から始められます。
  • MaxScript の $Box001/*(直下の子)のようなパス指定は、rt.execute で書くこともできます。

階層を文字で表示する

def print_tree(node, depth=0):
    for c in node.children:
        print("  " * depth + c.name)
        print_tree(c, depth + 1)

print_tree(rt.rootNode)

グループ

nodes = [rt.getNodeByName("Box001"), rt.getNodeByName("Sphere001")]
g = rt.group(nodes, name="MyGroup")      # MXS: g = group #($Box001, $Sphere001) name:"MyGroup"

rt.isGroupHead(g)                         # True(グループのまとめ役のダミー)
rt.isGroupMember(nodes[0])                # True
rt.setGroupOpen(g, True)                  # グループを開く(メンバーを個別に操作するとき・detach する前に必要)

rt.attachNodesToGroup([rt.getNodeByName("Teapot001")], g)   # 既存のグループに追加
rt.detachNodesFromGroup([rt.getNodeByName("Teapot001")])    # グループから外す(開いているグループのメンバーであること)
nodes = [rt.getNodeByName("Box001"), rt.getNodeByName("Sphere001")]
g = rt.group(nodes, name="MyGroup")
rt.ungroup(g)                             # 1 段だけ解除(MXS: ungroup g)
  • グループは「グループヘッド」という見えないダミーノードが親になる仕組みです。g.children でメンバーを取れます。
  • detachNodesFromGroup で外せるのは、開いているグループのメンバーだけです。閉じたグループのメンバーを外すときは、先に rt.setGroupOpen(g, True) で開いておきます。
  • 入れ子のグループを全部ばらすときは rt.explodeGroup(g) を使います。

レイヤを作る / ノードを移す

lm = rt.LayerManager

layer = lm.getLayerFromName("Props")           # MXS: LayerManager.getLayerFromName "Props"
if layer is None:
    layer = lm.newLayerFromName("Props")       # 無ければ作る

layer.addNode(rt.getNodeByName("Box001"))      # MXS: layer.addNode $Box001
rt.getNodeByName("Box001").layer.name          # 'Props'

ok, layer_nodes = layer.nodes(pymxs.byref(None))    # レイヤ内のノード(参照渡しで受け取る)
  • getLayerFromName は見つからないと None を返すので、「あれば使い、無ければ作る」形にしておくと何度実行しても安全です。newLayerFromName も、同じ名前のレイヤが既にあると None を返します。
  • layer.nodes は MaxScript の参照渡し(&)で結果を返すので、pymxs.byref で受け取ります。ノード数だけでよければ layer.getNumNodes()(3ds Max 2016 以降)の方が簡単です。

レイヤを一覧する・状態を変える

lm = rt.LayerManager
# MXS: for i = 0 to LayerManager.count-1 collect (LayerManager.getLayer i).name
names = [lm.getLayer(i).name for i in range(lm.count)]   # ⚠️ getLayer は 0 始まり

layer = lm.newLayerFromName("Hidden_Stuff")
layer.ishidden = True           # 非表示
layer.isfrozen = True           # フリーズ
layer.renderable = False        # レンダリングしない
layer.setname("Hidden_Items")   # 名前を変える(name プロパティは読み取り専用)

parent_layer = lm.newLayerFromName("Env")
layer.setParent(parent_layer)   # レイヤの入れ子
parent_layer.current = True     # 現在のレイヤにする(以後、作るものはここに入る)
  • ⚠️ LayerManager.getLayer は MaxScript でも 0 始まり の珍しい関数です(0 番はデフォルトレイヤ “0”)。
  • レイヤの削除は lm.deleteLayerByName("名前") です。中にノードがあるレイヤや現在のレイヤは削除できません。
  • 階層ごと・ノードが入っていても削除したいときは lm.deleteLayerHierarchy("名前", forceDelete=True)(3ds Max 2016 以降)を使います。ノードは既定の “0” レイヤに移ります。

落とし穴まとめ

  • 親子リンクしても子の pos はワールド座標のまま。
  • グループヘッドはダミーノード。rt.objects を回すとグループヘッドも含まれる(rt.isGroupHead で除外する)。
  • LayerManager.getLayer は 0 始まり。レイヤ名の変更は setname。
  • layer.nodes は pymxs.byref で受け取る。

関連

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