みなさんはじめまして、土屋です!
初めてのブログ投稿で、テクニカルな記事を書くというのも少し緊張しますが、この記事を見てくださった方の参考になればと思います!
今回何について書くか悩んだのですが、過去にTransformノードの取得で詰まった話があったのでその事についてお話したいと思います。
似たような問題に直面している方々の参考になれば幸いです!
環境
Autodesk Maya2024.2
※別のバージョンでも基本的に同じ操作が可能です。
取得の際に起きた問題点
MayaでメッシュやRig階層をまとめる時、空のtransformを作って纏める…いう事をよくやるのですが、
グループ化の為に作った「純粋なtransformノード」だけ取得したいと思った事はありませんか?
この「純粋なtransformノード」だけを取得する というのが以外に難しく、どうやって取得すればわからないという状況になりました。
シンプルにlsコマンドでtransformノードを指定して取得した際、想定していないメッシュのtransformやコンストレイントなどのオブジェクトが取得されてしまいます。
では、子にshapeを持つtransformを除外すれば良いじゃない、と思ってやった結果が上記の図です。
▼実行したコマンド
result = []
for transformNode in cmds.ls(type="transform"):
if cmds.listRelatives(transformNode,c=True,type="shape"):
continue
result.append(transformNode)
print(result)
▼結果
['GroupNode', 'joint1', 'joint2', 'locator1_orientConstraint1']
結果を見てみるとtransformノード以外にもジョイントやコンストレイントなども取得されてしまう事がわかります。
では、そのジョイントもコンストレイントも除外すれば…!とも思ったのですが、想定外のノードが取れてしまった以上、今のシーン上に無いだけで、他の想定外のノードも取得されてしまう可能性が出てきました。
となるとすべてのノードを調べて、type=”transform”で取れてしまうノードを調べ上げて、1つ1つ除外するスクリプトを記述していって…という作業が必要になってしまいます。
さすがにすべてのノードを作成して検証して…というのはメンドクs… 現実的ではないので別の解決策を探すことにしました。
どうやって解決したか
まずは結果だけ知りたい人の為に、先に解決方法を書いておきます。
result = []
# transformノードを取得
for transformNode in cmds.ls(type="transform"):
# transformノードをタイプ取得
nodeType = cmds.ls(transformNode,showType=True)[-1]
# transform以外のノードタイプなら除外
if not nodeType == "transform":
continue
# 子にシェイプがあれば除外
if cmds.listRelatives(transformNode,c=True,type="shape"):
continue
# 結果
result.append(transformNode)
print(result)
内容としては、取ってきたtransformのノードタイプをもう一度チェックしています。
lsコマンドのshowTypeオプションで取得すると、オブジェクトの名前と共にタイプを返してくれます。
この時に返ってくるタイプが”joint”だったり”orientConstraint”だったりと、より詳細な結果で帰ってくるので、transform以外を除外する事で、実際に欲しかったtransformノードを得られる事ができました。
めでたしめでたし。
以下、解決までの紆余曲折
※ここからは小難しい処理の話になるので、興味のある方だけどうぞ~
なんでメッシュやカメラもtransformで取れるの?
これはMayaをつかっている人なら聞いたことあるかもしれませんが、オブジェクトの構造に由来します。
一例として「pCube1」を作成して見てみましょう。
アウトライナを右クリックして、シェイプの項目にチェックを入れます。
するとノード名の左側に+マークが表示されます。
この+マーククリックで開いてみると、今までメッシュのアイコンだった「pCube1」がtransformのアイコンと同じになり、その子にメッシュのアイコンで「pCubeShape1」が表示されました。
このtransformとshapeはそれぞれ別の役割を持っていて、
・移動や回転などの位置情報はtransformノードに格納される。
・形状や設定情報などはshapeに格納される。
といった役割があります。
オブジェクトは「位置情報」と「形状の情報」が必要なため、生成時にtransformとshapeノードが作成される仕様になっています。
※公式の説明を見てみると
・shapeとかには移動や回転情報は持たせてないよ
・shapeの親には必ずtransformノードが必要だよ
といった旨の記載があります。
▼公式の説明
トランスフォームとシェイプ
つまりざっくり言うと
・画面上で移動できるヤツはみんなtransformノードがくっ付いてるんだ~
・type=”transform”で取れてたのは、この親にくっついてるtransformノードだったんだ~
という訳だったんですね。 なるほど。
ジョイントやコンストレイントとかは何者?
ふむふむ。shapeの構造はわかった。
でもじゃあ、ジョイントやコンストレイントは何者なのさ?
jointノードには移動や回転情報が必要なのに親にtransformノードはついてないし、
constraintノードは計算して出力するノードなのにtransformノード扱いになってるし、
shapeも持ってないし、なんでtransformノード扱いされてるの? って思いますよね?
私も気になったのでノードリファレンスを読み漁って調べてみる事にしました。
▼公式ノードリファレンス
https://help.autodesk.com/view/MAYAUL/2024/ENU/?guid=__Nodes_index_html
まずはジョイントから。
このparentという項目を確認すると、親クラスはtransformだよーと記載されています。
jointノードはtransformを継承しているノードだったみたいです。なるほど?
つぎにorientConstraintはどうでしょう?
こちらはjointと異なり、親クラスがconstraintとなっています。
なんだtransformじゃないじゃんー と思ったのですが、念のため、さらに親を辿ってみます。
constraintの親クラスはtransformと記載されていました。
この事から、orientConstraintノードはtransformを継承したconstraintを継承したノードという事が分かります。(文章で書くとややこしいですね・・・)
図解するとこんな感じです。
なるほど、jointもconstraintも元をたどればtransformという事みたいです。
typeオプションってもしかして…?
typeコマンドで取得されてしまうノードがtransformを継承したノードなのは分かりました。
ここまでくると、lsコマンドのtypeオプションの挙動について1つ予想が立ちます。
継承された子クラスのノードも全部取ってきてしまうのでは?
この予想が合っているか確認するために、実験していきましょう。
検証
試しにpCubeを生成した状態から、lsコマンドのtypeを変えて何が取得されるのか見てみます。
pCubeを生成するとtransformとshapeが作成されます。
この2つの親クラスを調べるとdagNodeから継承して作成されているノードだと判明しました。
▼結果
■type=”dagNode” で取得した場合
予想通り、pcube1(transformノード)とpCubeShape1(shapeノード)が両方取れています。
■type=”transform” で取得した場合
pcube1(transformノード)だけ取得され、shapeの方は取得されませんでした。
■type=”shape” で取得した場合
pCubeShape1(shapeノード)は取得が出来、transformは除外された結果になりました。
この事から、lsコマンドのtypeオプションは継承された子クラスのノードも取得する事がわかりました。
なるほど、そういう挙動だったのか。へぇ~
挙動は分かった!あとは処理を書くだけだ!
これまでの内容から
・shapeの親にもtransformノードが付いている。
・type=”transform”では継承された子クラスのノードも取得される。
という事が判明しました。
あとは本来取得したかったtransformノードだけ取得できるように処理を書くだけです!
考えた処理の流れとしては以下の通り。
1. type=”transform”のノードを取得する。
2. 取ってきたノードが本当にtransformノードかshowTypeオプションでチェック
3. 子にshapeがあるかチェック
4. チェックを通過したモノがtransformノード!
その処理をPythonで記述したものがこちら。(冒頭のモノと同じです)
result = []
# transformノードを取得
for transformNode in cmds.ls(type="transform"):
# transformノードをタイプ取得
nodeType = cmds.ls(transformNode,showType=True)[-1]
# transform以外のノードタイプなら除外
if not nodeType == "transform":
continue
# 子にシェイプがあれば除外
if cmds.listRelatives(transformNode,c=True,type="shape"):
continue
# 結果
result.append(transformNode)
print(result)
実行してみると、無事transformノードだけ取得する事が出来ました!
めでたしめでたし。
まとめ
ここまで読んでいただきありがとうございます。
スクリプトを書く上で特定のモノを取得したい という事がよくあるのですが、その中でもこのグループ用に作ったtransformノードだけは上手く取得できずに過去に何度も苦戦をしていました。
名前にルールを決めたり、特定の階層にだけ作るなど、あの手この手でやりくりしていたのですが、今回調べ直してみて、しっくり来る記述方法が見つかったんじゃないかなと思っています。
記述方法は1つではないので、他にもいい方法があるよ!こうやってるよ!などありましたらコメントなどいただければ幸いです!
・・・
最後に一言。
type=”transform”って言ったらtransformノードだけ返してくれよぉ!!!!!!!
おわり。