コンテンツにスキップ

Property/Attribute/Relationship

Universal Scene Description 3日目。
今回は、今までしっかり説明指定していなかった、USDの「Propertyの基本」について説明していこうと思います。

Propertyとは

USDのPropertyとは、

Properties are the other kind of namespace object in USD (Prims being the first).
引用: https://graphics.pixar.com/usd/release/glossary.html#usdglossary-property

とあるとおり、USDのネームスペースを構成する一種で、Primに指定される実データをを含む値のことです。
(MayaでいうところのAttribute)

USDのPropertyは、 「Attribute」「Relationship」 の種類があります。

Attribute

AttributeはいわゆるMayaのAttributeのように、
Primに定義された「TimeSampleすること」「コンポジションアークによって合成」ができるパラメーターです。
昨日のこちらの記事のアトリビュートについて でも触れられていましたが、
一般的に、3Dで使われる型などDataTypeで指定されたものが使用できます。

jsonやtoml、yaml、xmlといったフォーマットとは違い、Attributeに対しては必ず型指定必用なのが特徴です。

1
2
3
4
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim('/samplePrim')
attr = prim.CreateAttribute('sampleValuer',Sdf.ValueTypeNames.Bool).Set(True)
print(stage.ExportToString())
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#usda 1.0
(
    doc = """Generated from Composed Stage of root layer 
"""
)

def "samplePrim"
{
    custom bool sampleValuer = 1
}

シンプルなPrimを作成して、Attributeを追加・指定した例。
CreateAttributeで指定した型でAttributeが追加されていることがわかります。

余談ですが、このAttributeの型指定は、Pythonから指定する場合公式のDataTypesページのどこにも書かれていないという罠があります。
https://fereria.github.io/reincarnation_tech/11_Pipeline/10_USDTips/01_usd_py_docs/#usdattributetype
そんなトラップを回避するために、過去頑張って調べた一覧がこちら。
PythonとC++(Help記載の内容)だと違うという罠がごくまれにあるのがUSDの悲しいところ。

TimeSamplingとInterpolation

USDのAttributeでは、TimeCodeを指定することで、TimeSampling いわゆるアニメーションを
させることができます。
以下、シンプルなAttributeを作成して、動作を確認してみます。

1
2
3
4
5
6
7
stage = Usd.Stage.CreateInMemory()

prim = stage.DefinePrim('/samplePrim')
attr = prim.CreateAttribute('intValue',Sdf.ValueTypeNames.Float)
attr.Set(0,0)
attr.Set(10,10)
print(stage.ExportToString())

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#usda 1.0
(
    doc = """Generated from Composed Stage of root layer 
"""
)

def "samplePrim"
{
    custom float intValue
    float intValue.timeSamples = {
        0: 0,
        10: 10,
    }
}

Setするときに、 Set(\<Value>,\<Timecode>) とすることで、Attributeに対してキーを追加できました。
この状態で、

1
print(attr.Get(5))

5.0

.Get(\<TimeCode>) とすると、指定のTimeCodeの値を取得することができます。
GetしているのはKeyを打っていない5フレーム目ですが、0フレーム目と10フレーム目のちょうど中間
TimeCodeとTimeCodeの間が補間された値が取得されました。

AttributeのTimeSampleは、UsdStageのSetInterpolationTypeで指定することができます。
サポートされているのは Held と Linear の2つ。

デフォルトは Linearで、TimeCodeとTimeCodeの間をリニアに保管します。
Heldの場合は、MayaでいうところのStepTangentで、次のTimeCodeまでAttributeの値を一定に保ちます。

1
2
3
4
5
6
7
stage.SetInterpolationType(Usd.InterpolationTypeHeld) # デフォルトは、UsdInterpolationTypeLinear 
prim = stage.DefinePrim('/samplePrim')
attr = prim.CreateAttribute('intValue',Sdf.ValueTypeNames.Float)
attr.Set(0,0)
attr.Set(10,10)

print(attr.Get(5))

0.0

Holdに変えた場合の結果、補間されず直前の値が取得されました。

CustomAttribute

最後にCustomAttributeについて。
空のPrimに対して CreateAttributeをした場合、ExpotToStringをしてみると、Attributeの前に「custom」
が付いていることがわかります。
この custom がなにかというと、
その名の通りそのAttributeがSchemaによって定義されたものかユーザーによって定義されたものかどうか区別するためのものです。

まず、Schemaによって定義されたものとはどういうものかというと、
以下のKitchen_setにあるBall.usdで見てみます。

BallにあるMeshPrimは、UsdGeomMeshスキーマが指定されたPrimです。
USDのソースコード以下 USD/pxr/usd/usdGeom/generatedSchema.usda を読み進めると、Meshスキーマの classがあり
その中に、Meshスキーマに定義されたAttributeがあります。
これが、スキーマによって定義されているAttributeで たとえレイヤーにAttributeの記述がなかったとしても
スキーマで定義された値がフォールバック値として設定されます。

例として、 doubleSided。
Ball.usd内には doubleSided Attributeに値をセットする記述はありませんが、usdviewを見ると
セットされていることがわかります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
stage = Usd.Stage.CreateInMemory()
layer = stage.GetRootLayer()
layer.subLayerPaths = [r"D:\Kitchen_set\assets\Ball\Ball.usd"]

prim = stage.GetPrimAtPath('/Ball/Geom/Ball')
prim.GetAttribute('doubleSided').Set(True)

prim.CreateAttribute('sample',Sdf.ValueTypeNames.Int).Set(0)

print(layer.ExportToString())

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#usda 1.0
(
    subLayers = [
        @D:\Kitchen_set\assets\Ball\Ball.usd@
    ]
)

over "Ball"
{
    over "Geom"
    {
        over "Ball"
        {
            uniform bool doubleSided = 1
            custom int sample = 0
        }
    }
}

このようなAttributeに値をSetした場合は custom にはなりません。

1
prim.GetAttribute('doubleSided').IsCustom()

False

IsCustomで確認しても、Falseになります。

1
prim.GetAttribute('sample').IsCustom()

True

対する、スキーマにはないAttributeの場合は
customが追加され、 IsCustomで確認してもTrueになります。

Relationship

Relationshipとは、その名の通り、あるPrimへの「リレーション」を持つプロパティのことです。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
stage = Usd.Stage.CreateInMemory()

basePrim = stage.DefinePrim('/samplePrim')
primA = stage.DefinePrim('/primA')
primB = stage.DefinePrim('/primB')
# Relationを作成
rel = basePrim.CreateRelationship('sample')
rel.AddTarget(primA.GetPath())
rel.AddTarget(primB.GetPath())
print(stage.ExportToString())
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#usda 1.0
(
    doc = """Generated from Composed Stage of root layer 
"""
)

def "samplePrim"
{
    custom rel sample = [
        </primA>,
        </primB>,
    ]
}

def "primA"
{
}

def "primB"
{
}

同じPropertyですが、Attributeとは違いリレーションは指定のPrimまでのSdfPathを持ちます。
上の例でいうと、 basePrimの sampleプロパティは primA と primB へのRelationshipを持ちます。

1
2
3
4
5
# Relationで接続したPrimを取得できる
print(rel.GetTargets())
# そのPathから、Primを取得
for path in rel.GetTargets():
    print(stage.GetPrimAtPath(path))

Relationshipは、ターゲットまでのSdfPathを持っているので
このように、別のPrimとのコネクションを作成できます。(Property - 複数Primの関係)

このRelationshipは、主にMaterialAssignCollectionといった、あるPropertyによって指定のPrimとを関連付けるときなどに
使用します。

Namespace

最後にPropertyのNamespaceについて。
USDのProperty名には Namespace:PropName = Value のように、 : を使用することでNamespaceを指定することができます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
stage = Usd.Stage.CreateInMemory()

prim = stage.DefinePrim('/samplePrim')
attr = prim.CreateAttribute('sampleA:hoge',Sdf.ValueTypeNames.Bool).Set(False)
attr = prim.CreateAttribute('sampleA:ns:fuga',Sdf.ValueTypeNames.Bool).Set(True)
attr = prim.CreateAttribute('sampleA:ns:fugaB',Sdf.ValueTypeNames.Bool).Set(True)

attr = prim.CreateAttribute('sampleB:foo',Sdf.ValueTypeNames.Bool).Set(False)
attr = prim.CreateAttribute('sampleB:bar',Sdf.ValueTypeNames.Bool).Set(True)

print(prim.GetPropertiesInNamespace('sampleA'))
print(prim.GetPropertiesInNamespace('sampleA:ns'))
print(prim.GetPropertiesInNamespace('sampleB'))

print(stage.ExportToString())

このNamespaceをつけると何ができるかというと、指定のNamespace以下のPropertyを GetPropertiesInNamespaceできるようになります。
サンプルのように、1つだけでなく複数Namespaceをつけることもできるので
これを利用して、Propertyをグループとして扱うなどができます。

まとめ

まとめると、PropertyとはPrimに指定された実データを含む値のことで

AttributeとRelationshipの2種類が存在しています。

Attributeは、型をもった値(実データ)で、任意のTimeCodeを与えることでTimeSampling(アニメーション)することができます。
TimeSamplingがある場合、Stageで指定されたInterpolation(補間)でTimeCodeとTimeCodeの間が補間されます。
そして、Attributeは、Schemaによって定義されたものと、ユーザーによって定義されたもの2種類が存在し、
Schemaによって定義されたものはレイヤーに値が設定されていない場合でもデフォルトが指定されます。

Relationshipは、

あるPrimへのリレーションを作ることができます。
これを利用して、MaterialAssignやCollectionといったような他Primとのコネクションを
表現することができます。

以上が Property/Attribute/Relationshipの基本でした。

このあたりは、コンポジションがかかわってきた場合さらにいろいろややこしくなりますが、こちらの記事 で技師長師匠に盛大に丸投げされてしまったので、近いうちに別途詳しく書こうと思います。


最終更新日: 2021-12-05 04:32:17

Tag
Back to top