What’s in Fabric? Example Kit extension¶
About¶
The Kit extension registry has an extension that can be installed named What’s in Fabric?. This is a small Python extension that demonstrates a few concepts of working with the USDRT Scenegraph API:
Inspecting Fabric data using the USDRT Scenegraph API
Manipulating prim transforms in Fabric using the RtXformable schema
Deforming Mesh geometry on the GPU with USDRT and Warp
Enabling the extension¶
The What’s in Fabric? extension can be installed and enabled in the Extensions window.
Search for “usdrt” and find the extension titled WHATS IN FABRIC? USDRT EXAMPLE. Click the INSTALL button and enable the extension by clicking the ENABLED toggle.
The extension will launch as a free-floating window, which can be docked anywhere you find convenient (or not at all).
Inspecting Fabric data¶
You can inspect data in Fabric by selecting any prim in the viewport or stage window, and then clicking the button labeled “What’s in Fabric?”.
Note that in the current implementation of the USDRT Scenegraph API, any creation
of a usdrt UsdPrim (ex: stage.GetPrimAtPath()
) will populate data for
that prim into Fabric if has not yet been populated.
If you create a Torus Mesh in an empty scene…
and click “What’s in Fabric?”, you should see something like this in the UI:
The code to get all of the prim attribute values from Fabric
is straightforward and familiar to those with USD experience.
The get_fabric_data_for_prim
function does all the work
using the USDRT Scenegraph API,
with is_vtarray
and condensed_vtarray_str
encapsulating some
work around discovering VtArray instances and generating a
printed representation for them.
import omni.usd
from usdrt import Usd, Sdf, Gf, Vt, Rt
def get_fabric_data_for_prim(stage_id, path):
"""Get the Fabric data for a path as a string
"""
if path is None:
return "Nothing selected"
stage = Usd.Stage.Attach(stage_id)
# If a prim does not already exist in Fabric,
# it will be fetched from USD by simply creating the
# Usd.Prim object. At this time, only the attributes that have
# authored opinions will be fetch into Fabric.
prim = stage.GetPrimAtPath(Sdf.Path(path))
if not prim:
return f"Prim at path {path} is not in Fabric"
# This diverges a bit from USD - only attributes
# that exist in Fabric are returned by this API
attrs = prim.GetAttributes()
result = f"Fabric data for prim at path {path}\n\n\n"
for attr in attrs:
try:
data = attr.Get()
datastr = str(data)
if data is None:
datastr = "<no value>"
elif is_vtarray(data):
datastr = condensed_vtarray_str(data)
except TypeError:
# Some data types not yet supported in Python
datastr = "<no Python conversion>"
result += "{} ({}): {}\n".format(attr.GetName(), str(attr.GetTypeName().GetAsToken()), datastr)
return result
def is_vtarray(obj):
"""Check if this is a VtArray type
In Python, each data type gets its own
VtArray class i.e. Vt.Float3Array etc.
so this helper identifies any of them.
"""
return hasattr(obj, "IsFabricData")
def condensed_vtarray_str(data):
"""Return a string representing VtArray data
Include at most 6 values, and the total items
in the array
"""
size = len(data)
if size > 6:
datastr = "[{}, {}, {}, .. {}, {}, {}] (size: {})".format(
data[0], data[1], data[2], data[-3], data[-2], data[-1], size
)
else:
datastr = "["
for i in range(size-1):
datastr += str(data[i]) + ", "
datastr += str(data[-1]) + "]"
return datastr
The stage ID for the active stage in Kit can be retrieved using
omni.usd.get_context().get_stage_id()
- this is the ID of the
USD stage in the global UsdUtilsStageCache, where Kit automatically
adds all loaded stages.
Applying OmniHydra transforms using Rt.Xformable¶
The OmniHydra scene delegate that ships with Omniverse
includes a fast path for prim transform manipulation with Fabric.
The USDRT Scenegraph API provides the Rt.Xformable
schema
to facilitate those transform manipulations in Fabric. You can
learn more about this in Working with OmniHydra Transforms
In this example, the Rotate it in Fabric! button will
use the Rt.Xformable
schema to apply a random world-space
orientation to the selected prim. You can push the button
multiple times to make the prim dance in place!
The code to apply the OmniHydra transform in Fabric is simple enough:
from usdrt import Usd, Sdf, Gf, Vt, Rt
def apply_random_rotation(stage_id, path):
"""Apply a random world space rotation to a prim in Fabric
"""
if path is None:
return "Nothing selected"
stage = Usd.Stage.Attach(stage_id)
prim = stage.GetPrimAtPath(Sdf.Path(path))
if not prim:
return f"Prim at path {path} is not in Fabric"
rtxformable = Rt.Xformable(prim)
# If this is the first time setting OmniHydra xform,
# start by setting the initial xform from the USD stage
if not rtxformable.HasWorldXform():
rtxformable.SetWorldXformFromUsd()
# Generate a random orientation quaternion
angle = random.random()*math.pi*2
axis = Gf.Vec3f(random.random(), random.random(), random.random()).GetNormalized()
halfangle = angle/2.0
shalfangle = math.sin(halfangle)
rotation = Gf.Quatf(math.cos(halfangle), axis[0]*shalfangle, axis[1]*shalfangle, axis[2]*shalfangle)
rtxformable.GetWorldOrientationAttr().Set(rotation)
return f"Set new world orientation on {path} to {rotation}"
Deforming a Mesh on the GPU with Warp¶
The USDRT Scenegraph API can leverage Fabric’s support for mirroring data to the GPU for array-type attributes with its implementation of VtArray. This is especially handy for integration with warp, NVIDIA’s open-source Python library for running Python code on the GPU with CUDA.
In addition to a fast path for transforms,
OmniHydra also provides a fast rendering path for Mesh point data
with Fabric. Any Mesh prim in Fabric with the Deformable
tag will have its
point data pulled from Fabric for rendering by OmniHydra, rather than
the USD stage.
If you push the Deform it with Warp! button, the extension will apply a Warp kernel to the point data of any selected prim - this data modification will happen entirely on the GPU with CUDA, which makes it possible to do operations on substantial Mesh data in very little time. If you push the button repeatedly, you will see the geometry oscillate as the deformation kernel is repeatedly applied.
The warp kernel used here is very simple, but it is possible to achieve beautiful and complex results with Warp. Warp is amazing!
from usdrt import Usd, Sdf, Gf, Vt, Rt
import warp as wp
wp.init()
@wp.kernel
def deform(positions: wp.array(dtype=wp.vec3), t: float):
tid = wp.tid()
x = positions[tid]
offset = -wp.sin(x[0])
scale = wp.sin(t)*10.0
x = x + wp.vec3(0.0, offset*scale, 0.0)
positions[tid] = x
def deform_mesh_with_warp(stage_id, path, time):
"""Use Warp to deform a Mesh prim
"""
if path is None:
return "Nothing selected"
stage = Usd.Stage.Attach(stage_id)
prim = stage.GetPrimAtPath(Sdf.Path(path))
if not prim:
return f"Prim at path {path} is not in Fabric"
if not prim.HasAttribute("points"):
return f"Prim at path {path} does not have points attribute"
# Tell OmniHydra to render points from Fabric
if not prim.HasAttribute("Deformable"):
prim.CreateAttribute("Deformable", Sdf.ValueTypeNames.PrimTypeTag, True)
points = prim.GetAttribute("points")
pointsarray = points.Get()
warparray = wp.array(pointsarray, dtype=wp.vec3, device="cuda")
wp.launch(
kernel=deform,
dim=len(pointsarray),
inputs=[warparray, time],
device="cuda"
)
points.Set(Vt.Vec3fArray(warparray.numpy()))
return f"Deformed points on prim {path}"