Siggraph Presentation

This guide will be officially introduced at Siggraph 2023 - Houdini Hive on Wednesday, 9. of August 2023 at 11:00 AM PST.

Prim Cache Population (PCP) - Composition Cache

The Prim Cache Population module is the backend of what makes USD composition work. You could call it the composition core, that builds the prim index. The prim index is like an ordered list per prim, that defines where values can be loaded from. For example when we call Usd.Prim.GetPrimStack, the pcp gives us a list of value sources as an ordered list, with the "winning" value source as the first entry.

When USD opens a stage, it builds the prim index, so that it knows for each prim/property where to pull data from. This is then cached, and super fast to access.

When clients (like hydra delegates or C++/Python attribute queries) request data, only then the data is loaded. This way hierarchies can load blazingly fast, without actually loading the heavy attribute data.

Tip

To summarize: Composition (the process of calculating the value sources) is cached, value resolution is not, to allow random access data loading.

For a detailed explanation, checkout the Value Resolution docs page.

Table of Contents

  1. Prim Cache Population (PCP) In-A-Nutshell
  2. What should I use it for?
  3. Resources
  4. Overview
  5. Inspecting Composition
    1. Prim/Property Stack
    2. Prim Index
    3. Prim Composition Query

TL;DR - In-A-Nutshell

  • The Prim Cache Population module in USD computes and caches the composition (how different layers are combined) by building an index of value sources per prim called prim index.
  • This process of calculating the value sources is cached, value resolution is not, to allow random access to data. This makes accessing hierarchies super fast, and allows attribute data to be streamed in only when needed. This is also possible due to USD's binary crate format, which allows sparse "read only what you need" access from USD files.
  • The Prim Cache Population (Pcp) module is exposed via two ways:
    • High Level API: Via the Usd.PrimCompositionQuery class.
    • Low Level API: Via theUsd.Prim.GetPrimIndex/Usd.Prim.ComputeExpandedPrimIndex methods.
  • Notice how both ways are still accessed via the high level API, as the low level Sdf API is not aware of composition.

What should I use it for?

Tip

We'll be using the prim cache population module for inspecting composition. This is more of a deep dive topic, but you may at some point run into this in production.

An example scenario might be, that when we want to author a new composition arc, we first need to check if there are existing strong arcs, than the arc we intend on authoring. For example if a composition query detects a variant, we must also author at least a variant or a higher composition arc in order for our edits to come through.

Resources

Overview

This page currently focuses on the practical usage of the Pcp module, it doesn't aim to explain how the composition engine works under the hood. (As the author(s) of this guide also don't know the details 😉, if you know more in-depth knowledge, please feel free to share!)

There is a really cool plugin for the UsdView by chrizzftd called The Grill, that renders out the dot graph representation interactively based on the selected prim.

In the examples below, we'll look at how to do this ourselves via Python.

Inspecting Composition

To query data about composition, we have to go through the high level Usd API first, as the Sdf low level API is not aware of composition related data. The high level Usd API then queries into the low level Pcp (Prim cache population) API, which tracks all composition related data and builds a value source index called prim index.

The prim stack in simple terms: A stack of layers per prim (and therefore also properties) that knows about all the value sources (layers) a value can come from. Once a value is requested, the highest layer in the stack wins and returns the value for attributes. For metadata and relationships the value resolution can consult multiple layers, depending on how it was authored (see list editable ops as an example for a multiple layer averaged value).

Prim/Property Stack

Let's first have a look at the prim and property stacks with a simple stage with a cubes that has written values in two different layers. These return us all value sources for a prim or attribute.

from pxr import Sdf, Usd
# Create stage with two different layers
stage = Usd.Stage.CreateInMemory()
root_layer = stage.GetRootLayer()
layer_top = Sdf.Layer.CreateAnonymous("exampleTopLayer")
layer_bottom = Sdf.Layer.CreateAnonymous("exampleBottomLayer")
root_layer.subLayerPaths.append(layer_top.identifier)
root_layer.subLayerPaths.append(layer_bottom.identifier)
# Define specs in two different layers
prim_path = Sdf.Path("/cube")
stage.SetEditTarget(layer_top)
prim = stage.DefinePrim(prim_path, "Xform")
prim.SetTypeName("Cube")
stage.SetEditTarget(layer_bottom)
prim = stage.DefinePrim(prim_path, "Xform")
prim.SetTypeName("Cube")
attr = prim.CreateAttribute("debug", Sdf.ValueTypeNames.Float)
attr.Set(5, 10)
# Print the stack (set of layers that contribute data to this prim)
# For prims this returns all the Sdf.PrimSpec objects that contribute to the prim.
print(prim.GetPrimStack()) # Returns: [Sdf.Find('anon:0x7f6e590dc300:exampleTopLayer', '/cube'), 
                           #           Sdf.Find('anon:0x7f6e590dc580:exampleBottomLayer', '/cube')]
# For attributes this returns all the Sdf.AttributeSpec objects that contribute to the attribute.
# If we pass a non default time code value clips will be included in the result.
# This type of function signature is very unique and can't be found anywhere else in USD.
time_code = Usd.TimeCode.Default()
print(attr.GetPropertyStack(1001)) # Returns: [Sdf.Find('anon:0x7f9eade0ae00:exampleBottomLayer', '/cube.debug')]
print(attr.GetPropertyStack(time_code)) # Returns: [Sdf.Find('anon:0x7f9eade0ae00:exampleBottomLayer', '/cube.debug')]

In Houdini/USD view we can also view these stacks in the UI.

Prim Index

Next let's look at the prim index.

import os
from subprocess import call
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()
# Spawn temp layer
reference_layer = Sdf.Layer.CreateAnonymous("ReferenceExample")
reference_bicycle_prim_path = Sdf.Path("/bicycle")
reference_bicycle_prim_spec = Sdf.CreatePrimInLayer(reference_layer, reference_bicycle_prim_path)
reference_bicycle_prim_spec.specifier = Sdf.SpecifierDef
reference_bicycle_prim_spec.typeName = "Cube"
reference_layer_offset = Sdf.LayerOffset(offset=10, scale=1)
reference = Sdf.Reference(reference_layer.identifier, reference_bicycle_prim_path, reference_layer_offset)
bicycle_prim_path = Sdf.Path("/red_bicycle")
bicycle_prim = stage.DefinePrim(bicycle_prim_path)
references_api = bicycle_prim.GetReferences()
references_api.AddReference(reference, position=Usd.ListPositionFrontOfAppendList)
# You'll always want to use the expanded method,
# otherwise you might miss some data sources!
# This is also what the UIs use.
prim = bicycle_prim
print(prim.GetPrimIndex()) 
print(prim.ComputeExpandedPrimIndex())
# Dump the index representation to the dot format and render it to a .svg/.png image.
prim_index = prim.ComputeExpandedPrimIndex()
print(prim_index.DumpToString())
graph_file_path = os.path.expanduser("~/Desktop/usdSurvivalGuide_prim_index.txt")
graph_viz_png_file_path = graph_file_path.replace(".txt", ".png")
graph_viz_svg_file_path = graph_file_path.replace(".txt", ".svg")
prim_index.DumpToDotGraph(graph_file_path, includeMaps=False)
call(["dot", "-Tpng", graph_file_path, "-o", graph_viz_png_file_path])
call(["dot", "-Tsvg", graph_file_path, "-o", graph_viz_svg_file_path])

def iterator_child_nodes(root_node):
    yield root_node
    for child_node in root_node.children:
        for child_child_node in iterator_child_nodes(child_node):
            yield child_child_node

def iterator_parent_nodes(root_node):
    iter_node = root_node
    while iter_node:
        yield iter_node
        iter_node = iter_node.parent
            
print("Pcp Node Refs", dir(prim_index.rootNode))
for child in list(iterator_child_nodes(prim_index.rootNode))[::1]:
    print(child, child.arcType, child.path, child.mapToRoot.MapSourceToTarget(child.path))
""" The arc type will one one of:
Pcp.ArcTypeRoot
Pcp.ArcTypeInherit
Pcp.ArcTypeVariant
Pcp.ArcTypeReference
Pcp.ArcTypeRelocate
Pcp.ArcTypePayload
Pcp.ArcTypeSpecialize
"""

The prim index class can dump our prim index graph to the dot file format. The dot commandline tool ships with the most operating systems, we can then use it to visualize our graph as a .svg/.png file.

Result of: print(prim_index.DumpToString()) | Click to view content

Node 0:
    Parent node:              NONE
    Type:                     root
    DependencyType:           root
    Source path:              </bicycle>
    Source layer stack:       @anon:0x7f9eae9f2400:tmp.usda@,@anon:0x7f9eae9f1000:tmp-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
    Prim stack:
      </bicycle> anon:0x7f9eae9f2400:tmp.usda - @anon:0x7f9eae9f2400:tmp.usda@
Node 1:
    Parent node:              0
    Type:                     reference
    DependencyType:           non-virtual, purely-direct
    Source path:              </bicycle>
    Source layer stack:       @anon:0x7f9eae9f2b80:ReferenceExample@
    Target path:              </bicycle>
    Target layer stack:       @anon:0x7f9eae9f2400:tmp.usda@,@anon:0x7f9eae9f1000:tmp-session.usda@
    Map to parent:
        SdfLayerOffset(10, 1)
        /bicycle -> /bicycle
    Map to root:
        SdfLayerOffset(10, 1)
        /bicycle -> /bicycle
    Namespace depth:          1
    Depth below introduction: 0
    Permission:               Public
    Is restricted:            FALSE
    Is inert:                 FALSE
    Contribute specs:         TRUE
    Has specs:                TRUE
    Has symmetry:             FALSE
    Prim stack:
      </bicycle> anon:0x7f9eae9f2b80:ReferenceExample - @anon:0x7f9eae9f2b80:ReferenceExample@

Result of writing the graph to a dot .txt file | Click to view content

digraph PcpPrimIndex {
	140321959801344 [label="@anon:0x7f9ec906e400:tmp.usda@,@anon:0x7f9ec8f85a00:tmp-session.usda@</red_bicycle> (0)\n\ndepth: 0", shape="box", style="solid"];
	140321959801448 [label="@anon:0x7f9ec906e900:ReferenceExample@</bicycle> (1)\n\ndepth: 1", shape="box", style="solid"];
	140321959801344 -> 140321959801448 [color=red, label="reference"];
}

For example if we run it on a more advanced composition, in this case Houdini's pig asset:

Python print output for Houdini's pig asset | Click to view content

Pcp Node Ref
<pxr.Pcp.NodeRef object at 0x7f9ed3ad19e0> Pcp.ArcTypeRoot /pig /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3ad17b0> Pcp.ArcTypeInherit /__class__/pig /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3ad1cf0> Pcp.ArcTypeReference /pig /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3ad1970> Pcp.ArcTypeInherit /__class__/pig /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3ad1890> Pcp.ArcTypeVariant /pig{geo=medium} /pig{geo=medium}
<pxr.Pcp.NodeRef object at 0x7f9ed3ad1270> Pcp.ArcTypePayload /pig /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3ad1660> Pcp.ArcTypeReference /pig /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3ad1510> Pcp.ArcTypeVariant /pig{geo=medium} /pig{geo=medium}
<pxr.Pcp.NodeRef object at 0x7f9ed3ad13c0> Pcp.ArcTypeReference /ASSET_geo_variant_1/ASSET /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3abbd60> Pcp.ArcTypeVariant /ASSET_geo_variant_1/ASSET{mtl=default} /pig{mtl=default}
<pxr.Pcp.NodeRef object at 0x7f9ed3abb6d0> Pcp.ArcTypeReference /ASSET_geo_variant_1/ASSET_mtl_default /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3ad1a50> Pcp.ArcTypeReference /pig /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3ad15f0> Pcp.ArcTypeReference /ASSET_geo_variant_2/ASSET /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3abbe40> Pcp.ArcTypeVariant /ASSET_geo_variant_2/ASSET{geo=medium} /pig{geo=medium}
<pxr.Pcp.NodeRef object at 0x7f9ed3ad1ac0> Pcp.ArcTypeReference /ASSET_geo_variant_1/ASSET /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3abbf90> Pcp.ArcTypeVariant /ASSET_geo_variant_1/ASSET{geo=medium} /pig{geo=medium}
<pxr.Pcp.NodeRef object at 0x7f9ed3abb430> Pcp.ArcTypeReference /ASSET_geo_variant_0/ASSET /pig
<pxr.Pcp.NodeRef object at 0x7f9ed3abb9e0> Pcp.ArcTypeVariant /ASSET_geo_variant_0/ASSET{geo=medium} /pig{geo=medium}

Result of writing the graph to a dot .txt file for Houdini's pig asset | Click to view content

digraph PcpPrimIndex {
	140319730187168 [label="@anon:0x7f9ec9290900:LOP:rootlayer@,@anon:0x7f9ec906e680:LOP:rootlayer-session.usda@</pig> (0)\n\ndepth: 0", shape="box", style="solid"];
	140319730187272 [label="@anon:0x7f9ec9290900:LOP:rootlayer@,@anon:0x7f9ec906e680:LOP:rootlayer-session.usda@</__class__/pig> (1)\n\ndepth: 1", shape="box", style="dotted"];
	140319730187168 -> 140319730187272 [color=green, label="inherit", style=dashed];
	140319730187272 -> 140319730187480 [style=dotted label="origin" constraint="false"];
	140319730187376 [label="@pig.usd@</pig> (2)\n\ndepth: 1", shape="box", style="solid"];
	140319730187168 -> 140319730187376 [color=red, label="reference"];
	140319730187480 [label="@pig.usd@</__class__/pig> (3)\n\ndepth: 1", shape="box", style="solid"];
	140319730187376 -> 140319730187480 [color=green, label="inherit"];
	140319730187584 [label="@pig.usd@</pig{geo=medium}> (4)\n\ndepth: 1", shape="box", style="solid"];
	140319730187376 -> 140319730187584 [color=orange, label="variant"];
	140319730187688 [label="@payload.usdc@</pig> (5)\n\ndepth: 1", shape="box", style="solid"];
	140319730187376 -> 140319730187688 [color=indigo, label="payload"];
	140319730187792 [label="@mtl.usdc@</pig> (6)\n\ndepth: 1", shape="box", style="solid"];
	140319730187688 -> 140319730187792 [color=red, label="reference"];
	140319730187896 [label="@mtl.usdc@</pig{geo=medium}> (7)\n\ndepth: 1", shape="box", style="solid"];
	140319730187792 -> 140319730187896 [color=orange, label="variant"];
	140319730188000 [label="@mtl.usdc@</ASSET_geo_variant_1/ASSET> (8)\n\ndepth: 1", shape="box", style="solid"];
	140319730187896 -> 140319730188000 [color=red, label="reference"];
	140319730188104 [label="@mtl.usdc@</ASSET_geo_variant_1/ASSET{mtl=default}> (9)\n\ndepth: 2", shape="box", style="solid"];
	140319730188000 -> 140319730188104 [color=orange, label="variant"];
	140319730188208 [label="@mtl.usdc@</ASSET_geo_variant_1/ASSET_mtl_default> (10)\n\ndepth: 2", shape="box", style="solid"];
	140319730188000 -> 140319730188208 [color=red, label="reference"];
	140319730188312 [label="@geo.usdc@</pig> (11)\n\ndepth: 1", shape="box", style="solid"];
	140319730187688 -> 140319730188312 [color=red, label="reference"];
	140319730188416 [label="@geo.usdc@</ASSET_geo_variant_2/ASSET> (12)\n\ndepth: 1", shape="box", style="solid"];
	140319730188312 -> 140319730188416 [color=red, label="reference"];
	140319730188520 [label="@geo.usdc@</ASSET_geo_variant_2/ASSET{geo=medium}> (13)\n\ndepth: 2", shape="box", style="dotted"];
	140319730188416 -> 140319730188520 [color=orange, label="variant"];
	140319730188624 [label="@geo.usdc@</ASSET_geo_variant_1/ASSET> (14)\n\ndepth: 1", shape="box", style="solid"];
	140319730188312 -> 140319730188624 [color=red, label="reference"];
	140319730188728 [label="@geo.usdc@</ASSET_geo_variant_1/ASSET{geo=medium}> (15)\n\ndepth: 2", shape="box", style="solid"];
	140319730188624 -> 140319730188728 [color=orange, label="variant"];
	140319730188832 [label="@geo.usdc@</ASSET_geo_variant_0/ASSET> (16)\n\ndepth: 1", shape="box", style="solid"];
	140319730188312 -> 140319730188832 [color=red, label="reference"];
	140319730188936 [label="@geo.usdc@</ASSET_geo_variant_0/ASSET{geo=medium}> (17)\n\ndepth: 2", shape="box", style="dotted"];
	140319730188832 -> 140319730188936 [color=orange, label="variant"];
}

Alt text

Tip

We can also access the Pcp.Cache of the stage via: pcp_cache = stage._GetPcpCache()

Prim Composition Query

Next let's look at prim composition queries. Instead of having to filter the prim index ourselves, we can use the Usd.PrimCompositionQuery to do it for us. More info in the USD API docs.

The query works by specifying a filter and then calling GetCompositionArcs.

USD provides these convenience filters, it returns a new Usd.PrimCompositionQuery instance with the filter applied:

  • Usd.PrimCompositionQuery.GetDirectInherits(prim): Returns all non ancestral inherit arcs
  • Usd.PrimCompositionQuery.GetDirectReferences(prim): Returns all non ancestral reference arcs
  • Usd.PrimCompositionQuery.GetDirectRootLayerArcs(prim): Returns arcs that were defined in the active layer stack.

These are the sub-filters that can be set. We can only set a single token value per filter:

  • ArcTypeFilter: Filter based on different arc(s).
    • Usd.PrimCompositionQuery.ArcTypeFilter.All
    • Usd.PrimCompositionQuery.ArcTypeFilter.Inherit
    • Usd.PrimCompositionQuery.ArcTypeFilter.Variant
    • Usd.PrimCompositionQuery.ArcTypeFilter.NotVariant
    • Usd.PrimCompositionQuery.ArcTypeFilter.Reference
    • Usd.PrimCompositionQuery.ArcTypeFilter.Payload
    • Usd.PrimCompositionQuery.ArcTypeFilter.NotReferenceOrPayload
    • Usd.PrimCompositionQuery.ArcTypeFilter.ReferenceOrPayload
    • Usd.PrimCompositionQuery.ArcTypeFilter.InheritOrSpecialize
    • Usd.PrimCompositionQuery.ArcTypeFilter.NotInheritOrSpecialize
    • Usd.PrimCompositionQuery.ArcTypeFilter.Specialize
  • DependencyTypeFilter: Filter based on if the arc was introduced on a parent prim or on the prim itself.
    • Usd.PrimCompositionQuery.DependencyTypeFilter.All
    • Usd.PrimCompositionQuery.DependencyTypeFilter.Direct
    • Usd.PrimCompositionQuery.DependencyTypeFilter.Ancestral
  • ArcIntroducedFilter: Filter based on where the arc was introduced.
    • Usd.PrimCompositionQuery.ArcIntroducedFilter.All
    • Usd.PrimCompositionQuery.ArcIntroducedFilter.IntroducedInRootLayerStack
    • Usd.PrimCompositionQuery.ArcIntroducedFilter.IntroducedInRootLayerPrimSpec
  • HasSpecsFilter: Filter based if the arc has any specs (For example an inherit might not find any in the active layer stack)
    • Usd.PrimCompositionQuery.HasSpecsFilter.All
    • Usd.PrimCompositionQuery.HasSpecsFilter.HasSpecs
    • Usd.PrimCompositionQuery.HasSpecsFilter.HasNoSpecs
from pxr import Sdf, Usd
stage = Usd.Stage.CreateInMemory()
prim = stage.DefinePrim("/pig")
refs_API = prim.GetReferences()
refs_API.AddReference("/opt/hfs19.5/houdini/usd/assets/pig/pig.usd")
print("----")
def _repr(arc):
    print(arc.GetArcType(), 
          "| Introducing Prim Path", arc.GetIntroducingPrimPath() or "-",
          "| Introducing Layer", arc.GetIntroducingLayer() or "-",
          "| Is ancestral", arc.IsAncestral(),
          "| In Root Layer Stack", arc.IsIntroducedInRootLayerStack())
print(">-> Direct Root Layer Arcs")
query = Usd.PrimCompositionQuery.GetDirectRootLayerArcs(prim)
for arc in query.GetCompositionArcs():
    _repr(arc)
print(">-> Direct Inherits")
query = Usd.PrimCompositionQuery.GetDirectInherits(prim)
for arc in query.GetCompositionArcs():
    _repr(arc)
print(">-> Direct References")
query = Usd.PrimCompositionQuery.GetDirectReferences(prim)
for arc in query.GetCompositionArcs():
    _repr(arc)
"""Returns:
>-> Direct Root Layer Arcs
Pcp.ArcTypeRoot | Introducing Prim Path - | Introducing Layer - | Is ancestral False | In Root Layer Stack True
Pcp.ArcTypeReference | Introducing Prim Path /pig | Introducing Layer Sdf.Find('anon:0x7f9b60d56b00:tmp.usda') | Is ancestral False | In Root Layer Stack True
>-> Direct Inherits
Pcp.ArcTypeInherit | Introducing Prim Path /pig | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/pig.usd') | Is ancestral False | In Root Layer Stack False
Pcp.ArcTypeInherit | Introducing Prim Path /pig | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/pig.usd') | Is ancestral False | In Root Layer Stack False
>-> Direct References
Pcp.ArcTypeReference | Introducing Prim Path /pig | Introducing Layer Sdf.Find('anon:0x7f9b60d56b00:tmp.usda') | Is ancestral False | In Root Layer Stack True
Pcp.ArcTypeReference | Introducing Prim Path /pig | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/payload.usdc') | Is ancestral False | In Root Layer Stack False
Pcp.ArcTypeReference | Introducing Prim Path /pig{geo=medium} | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/mtl.usdc') | Is ancestral False | In Root Layer Stack False
Pcp.ArcTypeReference | Introducing Prim Path /ASSET_geo_variant_1/ASSET | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/mtl.usdc') | Is ancestral False | In Root Layer Stack False
Pcp.ArcTypeReference | Introducing Prim Path /pig | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/payload.usdc') | Is ancestral False | In Root Layer Stack False
Pcp.ArcTypeReference | Introducing Prim Path /pig | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/geo.usdc') | Is ancestral False | In Root Layer Stack False
Pcp.ArcTypeReference | Introducing Prim Path /pig | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/geo.usdc') | Is ancestral False | In Root Layer Stack False
Pcp.ArcTypeReference | Introducing Prim Path /pig | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/geo.usdc') | Is ancestral False | In Root Layer Stack False
"""
# Custom filter
# For example let's get all direct payloads, that were not introduced in the active root layer stack.
query_filter = Usd.PrimCompositionQuery.Filter()
query_filter.arcTypeFilter = Usd.PrimCompositionQuery.ArcTypeFilter.Payload
query_filter.dependencyTypeFilter = Usd.PrimCompositionQuery.DependencyTypeFilter.Direct
query_filter.arcIntroducedFilter = Usd.PrimCompositionQuery.ArcIntroducedFilter.All
query_filter.hasSpecsFilter = Usd.PrimCompositionQuery.HasSpecsFilter.HasSpecs
print(">-> Custom Query (Direct payloads not in root layer that have specs)")
query = Usd.PrimCompositionQuery(prim)
query.filter = query_filter
for arc in query.GetCompositionArcs():
    _repr(arc)
"""Returns:
>-> Custom Query (Direct payloads not in root layer that have specs)
Pcp.ArcTypePayload | Introducing Prim Path /pig | Introducing Layer Sdf.Find('/opt/hfs19.5/houdini/usd/assets/pig/pig.usd') | Is ancestral False | In Root Layer Stack False
"""

The returned filtered Usd.CompositionArc objects, allow us to inspect various things about the arc. You can find more info in the API docs