PCPでコンポジションアークの構造を解析・編集対象を取得する
このサイトのほかページでも何度か説明している通り、USD は複数のファイルを合成して 1つのシーングラフに構築することができます。
たとえば、毎度おなじみキッチンセットでみてみます。
このキッチンセットは、 Kitchen_set.usd を開いた段階だとこのように1つのシーングラフに なっていますが、
この usd は1つの usd ではなく、複数の USD をコンポジションアークを使用して1つの usd に合成して 上のようなキッチンセットになっているわけです。 (この1つのアセットを構成する usd ファイルは 228 ファイルありました)
では、この複雑なファイルの組み合わせでできあがったシーングラフが
- どのようなファイルによって構成されているのか
- どのファイルを編集すればどこに書き込まれるのか
を調べるにはどうしたらよいのだろう???というのが今回の記事の内容です。
サブレイヤーの場合
コンポジションが「サブレイヤー」によって合成されている場合は、
for spec in prim.GetPrimStack():
print(spec.layer)
指定の Prim の PrimStack を取得して、その Prim を構築するための Spec を取得する方法つかったり、 編集対象を取得して切り替える場合は、以前に説明を書いたEditTarget で Layer を操作するを利用して 編集レイヤーを取得して Target を切り替えて編集する...という手を使うことができます。
しかし、サブレイヤー以外のコンポジションの場合はこの手段を使うことができません。
では、この場合はどうしたらよいかというと 今まで使っていた Usd ネームスペースの API では実行できませんが、 PcpAPI を使うことで実現することができます。
PCP とは
PCP とは 「Prim Cache Population」 の略で、これが何をするものかというと、 コンポジションアーク(シーン合成)を司る機構 になります。
ある Prim がシーンディスクリプション(usd ファイルすべて)がこの Prim にどのような影響を与えているか どのように構成されているのかを、PCP を使用することで知ることができます。
文章だけだとわかりにくいので具体的にみてみます。
サンプルのキッチンセットから、冷蔵庫アセットを選択します。 この Prim がどのようなファイルによって構成されているのか PCP をつかって調べてみます。
調べるには、DumpToIndex を使って Index 情報を Dump してみます。
Node 0:
Parent node: NONE
Type: root
DependencyType: root
Source path: </Kitchen_set/Props_grp/North_grp/FridgeArea_grp/Refridgerator_1>
Source layer stack: @d:/Kitchen_set/Kitchen_set.usd@,@anon:0000027614F5B360:Kitchen_set-session.usda@
Target path: <NONE>
Target layer stack: NONE
Map to parent:
/ -> /
Map to root:
/ -> /
Namespace depth: 0
Depth below introduction: 0
Permission: Public
Is restricted: FALSE
Is inert: FALSE
Contribute specs: TRUE
Has specs: TRUE
Has symmetry: FALSE
Node 1:
Parent node: 0
Type: reference
DependencyType: non-virtual, purely-direct
Source path: </Refridgerator>
Source layer stack: @d:/Kitchen_set/assets/Refridgerator/Refridgerator.usd@
Target path: </Kitchen_set/Props_grp/North_grp/FridgeArea_grp/Refridgerator_1>
Target layer stack: @d:/Kitchen_set/Kitchen_set.usd@,@anon:0000027614F5B360:Kitchen_set-session.usda@
Map to parent:
/Refridgerator -> /Kitchen_set/Props_grp/North_grp/FridgeArea_grp/Refridgerator_1
Map to root:
/Refridgerator -> /Kitchen_set/Props_grp/North_grp/FridgeArea_grp/Refridgerator_1
Namespace depth: 5
Depth below introduction: 0
Permission: Public
Is restricted: FALSE
Is inert: FALSE
Contribute specs: TRUE
Has specs: TRUE
Has symmetry: FALSE
長いので全部は貼りませんが、 Index を Dump するとこのような情報が表示されます。
この PrimIndex は、シーンディスクリプションのうち、ある特定の Prim に主張(Opinion)がある Index で、 「ある Prim に影響を与えている要素」を取得するために使用します。
これを見ると、この Prim がどのようにコンポジションされているのか。 コンポジションする要素(Opinion)がどうなっているのかを見ることができます。
例えば、 Type を見ればどのコンポジションを使用しているのか
Type: reference
Source layer stack を見ればどのレイヤーかどうか、
Source layer stack: @d:/Kitchen_set/assets/Refridgerator/Refridgerator.usd@
Target Path は、そのレイヤーのどの Prim が対象か
Target path: </Kitchen_set/Props_grp/North_grp/FridgeArea_grp/Refridgerator_1>
など。
Map To Parent / Map To Rot
または、 Map to Parent という情報もここから取得できます。
たとえば、Node1 のレイヤーを見てみると、冷蔵庫 Prim は /Refridgerator という Prim が あります。 それがリファレンスされると、
Target Path のネームスペースにマップされている... というのがわかります。
このように、PCP を見ることで、 複数のレイヤーをコンポジションして最終的な Prim が出来上がるまで どのようなコンポジションが行われているのか取得できているのがわかるわけです。
!!! info PcpPrimIndex はコンポジション機能だけを担うもので 、シーングラフの親子関係(階層構造) は持ちません。 Pcp 自体は、ある Prim に誰が意見を持っているかを提供しているだけで 最終的な値はなにか とか、シーングラフのオブジェクトの型であったりスキーマ は UsdPrim の役割として分離されています。
PCPNodeRef
この Dump した情報をみると「Node」というキーワードが出てきます。
この Node (PcpNodeRef)は、コンポジションの木構造を表現するためのノードで、
たとえば、
- どのレイヤーか
- どのようにコンポジションしていくか
- どのネームスペースにマップするか
という ある Prim を構成する要素を確認することができます。
この構造を視覚化してみます。
from pxr import Usd,Pcp
stage = Usd.Stage.Open(r"D:\Kitchen_set\Kitchen_set.usd")
prim = stage.GetPrimAtPath("/Kitchen_set/Props_grp/North_grp/FridgeArea_grp/Refridgerator_1")
index = prim.GetPrimIndex()
index.DumpToDotGraph("D:/graph.dot")
まず、構成をしらべたい Prim を選択して、PrimIndex を取得します。
Index の DumpToDotGraph を使用すると GraphViz のフォーマットで情報を出力できるので、 それを使用してグラフを表示してみると、
このようになります。 rootNode から複数のコンポジションアークが木構造で表されてるのがわかります。
コンポジションの構造が木構造になっているので、再帰を使用することで
def traverse(node):
# コンポジションタイプ
print(node.arcType) #CompositionArc
print(node.path) #SdfPath
print(node.site) #Layer + SdfPath
print(node.GetRootNode()) # RootNode
layer = node.layerStack.layers[0] # Layer取得
print(layer)
for child in node.children:
traverse(child)
traverse(rootRef)
Prim を構成する各レイヤーを検索することができます。