階層・グループ・レイヤ
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で受け取る。
関連
- ノードを集める: 「選択・取得」章
- 表示・フリーズをノード単位で変える: 「表示・名前・色・ユーザ定義」章
コメント