せっかく調べたのでHoudini Advent Calender シリーズ2の記事にして供養します!
これはだいぶ自分自身へのメモ的な感じです。テスト結果を鵜呑みにしないようにしてください。
最初はNamespace Editingの話からになりますが、Composition Arcに新しい仲間(Relocates)が追加されています。
これによってLIVRPSだったものがLIVeRPSに変更になりました(rElocatesが追加)。RelocatesはReferenceよりも強いです。
Changelogを読むと以下のようになっていて、Houdini 21のUSDのバージョンは25.05なので使うことはできそうです。
25.02: 「用語と概念」ページに、コンポジションアークをRelocateするための例を含む基本的な概要を追加しました。 ---------- 24.08: RelocatesのCompositionを追加する作業を完了しました。
ただしこの機能はHoudini 21の段階ではまだノードとして実装されていないようです。 PythonとASCII (InlineUSD LOP等)での記述であれば、この機能を扱うことができます。
ちなみにNamespace Editingはあんどうさんがすでに記事にしてくれています。いつもありがとうございます!
NamespaceEditingの使い方 | Reincarnation+#Tech
なぜ便利か / やりたいか
まずこちらの話から、Houdini的にSolaris(LOP)に持ってきてしまうと階層の変更や追加が結構大変でした。
特にショットワークをしている場合や、タスクのレイヤーを作っている際に大量のデータを直接Reference LOPで読んでいてグループにまとめたくなってしまった場合など。
Restructure Scene Graph LOPが存在はしていましたが、Flattendされてしまったり、しない場合はうまく処理できないなどの問題を抱えていました。
かといってPrimitive Pathを全部書き換えるというのもそれはそれでしんどいはずです。(例:$OS→/group/$OSにする)
Namespace Editing
これはHoudiniで使う場合は現行のノードネットワーク上(Reference LOPやSOP Importを使って読み込んでいる場合)で使う、という想定で便利な機能です。
あんどうさんの記事からほぼそのまま流用の状態になってしまいますが(申し訳ないです)、以下のようにPython LOPで記述することで、StageにあるPrimをリネーム、削除、移動ができます。
Rename

from pxr import Usd
node = hou.pwd()
stage = node.editableStage()
#Rename
editor = Usd.NamespaceEditor(stage)
editor.RenamePrim(stage.GetPrimAtPath('/sphere1'), 'RenameSphere')
editor.ApplyEdits()
Delete

from pxr import Usd
node = hou.pwd()
stage = node.editableStage()
#Delete
editor = Usd.NamespaceEditor(stage)
editor.DeletePrimAtPath('/sphere1')
editor.ApplyEdits()
Move
※移動をする場合、移動先の階層が存在している必要があります。事前に作りましょう。
from pxr import Usd
node = hou.pwd()
stage = node.editableStage()
editor = Usd.NamespaceEditor(stage)
#Move
editor.MovePrimAtPath('/sphere1','/group/sphere1')
editor.ApplyEdits()
Namespace Editingを使用したグループ化

Namespace Editingの移動処理を使用するとこんな感じで上流につながっているものを一括でグループ化することもできそうです。(ここでは"/"直下ですが、ここも選択式にすることもできると思います。)
from pxr import Usd
#Get Parm
node = hou.pwd()
group_path = "/" + node.evalParm("group")
stage = node.editableStage()
editor = Usd.NamespaceEditor(stage)
#Get "/" Children
itr = iter(stage.GetPrimAtPath("/").GetAllChildren())
#Create Group Prim
group = stage.DefinePrim(group_path)
group.SetTypeName("Xform")
group.SetKind("group")
#Move Prims
for prim in itr:
if not prim.GetTypeName() == "HoudiniLayerInfo":
#print(prim.GetPath())
move_path = group_path + prim.GetPath().pathString
editor.MovePrimAtPath(prim.GetPath(),move_path)
editor.ApplyEdits()
問題
全てHoudini上で構成されているデータならさほど問題はないんですが、一度書き出されたファイルを読んでいる場合にNamespace Editingを行うと色々な問題が発生します。
以下のようなシーンを書き出しているとして、

<asset/pig2>を</group/pigB>に移動すると、
def Xform "asset" (
kind = "group"
)
{
def "pig2" (
instanceable = true
prepend references = @houdini/usd/assets/pig/pig.usd@
)
というScene Graph Detailを見ていると元々の記述がなくなり、
over "group"
{
def "pigB" (
instanceable = true
prepend references = @houdini/usd/assets/pig/pig.usd@
)
のような記述に差し代わってしまいます。元々のpig2の記述がなくなってしまうので、破壊的変更になってしまうということです。
もちろん以下のようなエラーを返します。
Failed to update the following targets and/or connections for the namespace edit: Fixing the relationship paths [ /asset/pig2/mtl/Eyes ] for the relationship at '/asset/pig2/geo/shape/PigEyes.material:binding' would require '/asset/pig2'to be relocated but we do not introduce relocates for prims that do not have opinions across composition arcs.
Relationshipなども壊れてしまうから元のPrim(pig2)を再配置しろということですね。
そして、この状態だと現状HoudiniのScene Graphの更新が焼きついたりしてしまうエラーのような現象が発生します。(元のファイルをリロードすると一度直る)
Referenceを読み込んでいる外部ファイルに対してNamespace Editingを行うのは良くないということがわかります。
では元ファイルも同時に編集して保存しなおせばいいのか?というとそれはそれで破壊編集になってしまうのであまりやりたくないです。
Referenceの子階層はどうなるか?
Referenceで一度読まれてしまうと下流では何もできないのか?ということになります。
試しに先ほどのデータから、</asset/pig2/geo>を</group/pigNew/geo>に移動してみます。
特にエラーも出ずに移動することができます。

USDの記述を見るとこうなっています。
#sdf 1.4.32
(
framesPerSecond = 24
metersPerUnit = 1
timeCodesPerSecond = 24
upAxis = "Y"
relocates = {
</asset/pig2/geo>: </group/pigNew/geo>
}
)
{
}
def Xform "group"
{
def Xform "pigNew"
{
}
}
ここでrelocatesが出てきます。
子階層なら移動できるということは、pigの上に/root/pigとして1階層追加してあげることで/pigを変更できるのでは?というのを下の画像のような階層を作ってやってみます。

</asset/pig>を</group/pigB>に移動してみると、エラーも出ずに移動できました。

記述もrelocateになっています。
#sdf 1.4.32
(
framesPerSecond = 24
metersPerUnit = 1
timeCodesPerSecond = 24
upAxis = "Y"
relocates = {
</asset/pig>: </group/pigB>
}
)
Relocates
じゃあこのRelocatesがどういうことなのかというと、
Relocates を使用すると、プリムを直接編集できない状況でも、プリムの名前変更や親子関係の変更を行うことができます。 通常、コンポジションアークの PrimSpec を基盤とするプリムは、直接親子関係を変更することはできません。基盤となる「コンポジションソース」プリムを直接親子関係に変更することは可能ですが、そのような変更は破壊的な変更となり、そのシーン記述を共有する他のすべてのインスタンスに影響を及ぼします。 Relocates は、ソースネームスペースパスをローカルネームスペース内のターゲットネームスペースパスにマッピングすることで、コンポジションアークのソースが変更されないようにし、プリムの名前を非破壊的に親子関係または名前変更する方法を提供します。
パスの再マッピングを行なって非破壊で移動とリネーム(のようなこと)ができ、このPrimはこっちに移動(リネーム)しました。ということが記述できそうです。
Relocatesを使ってみる
確認してみると、確かに移動前の/assetに対してReferenceしているという状態は変わっていないようです。

Relocatesの参考コードを実行してみます。
</asset/pig>を</group/pigD>へ移動してみました。Session Layerに対して書き込みをしてあげるとHoudini的には動きます。とりあえずうまくいって、Scene Graphの焼きつきも起きてはいません。(Root Layerに書き込まれてしまうと焼きつく)

動作はするんですが、今のところPythonでRelocatesを記述するよりもHoudiniとしてはNamespace Editで記述して勝手にRelocatesになってる方が楽な感じです。この辺はやり方も良くないとは思うので今後に期待です。
Relocateの制限(ざっくり)
- ルートプリムを移動することはできません。 Relocateのソースパスはルートプリムにすることはできません。 RelocateはComposition Arcを介して導入されたプリムをマッピングするためにのみ使用できるためです。
- ソースパスとターゲットパスは、完全なシーンパスである必要があります。 バリアント選択のパス(例: /Prim{var=sel}Child)はサポートされていません。
- 無効または競合する名前空間パスを作成する再配置は許可されていません。
- プリムを既存の祖先に移転する:
<Prim/Child/Grandchild> : <Prim/Child> - プリムをソースパスの子孫に再配置する:
</Prim/Child>:</Prim/Child/Grandchild> - 同じ名前空間の別のリロケーションのソースパスにプリムを再配置する:
</Prim1>:</Prim/Prim2>をさらに</Prim/Prim2>:</Prim/Prim3>は許可されていません。前の例では、</Prim/Prim1>:</Prim/Prim3>を使用する
- プリムを既存の祖先に移転する:
- ソースパスが再配置されると、その元のソースパスは現在の名前空間で有効ではなくなったと見なされます。 したがって、をに移転した場合、でLocalのOpinionを作成することはできません。
出展
あんどうさんのNamespace Editingの記事
NamespaceEditingの使い方 | Reincarnation+#Tech
Relocatesの仕様
USD Terms and Concepts — Universal Scene Description 25.11 documentation
Namespace Editingの仕様
Namespace Editing — Universal Scene Description 25.11 documentation