Switch Stored Patterns#

Change geometry, ray counts and receiver groups without replacing their stored arrays.

Before you start. Create the base sensor and output in Set Up a Sensor. The USD Python tab below adds the alternative patterns to that stage.

Stored pattern alternatives#

Store alternatives when different captures should use different geometry, ray counts or receiver connections. A stored transmitter pattern may contain several transmitter elements; selecting it activates that complete pattern, not one element within it. Receiver patterns are selected independently.

Each transmitter pattern also assigns its ray bundle and receiver-mapping rule. Changing the transmitter selector also selects that pattern’s bundle and mapping rule. When using map, a mapping-page change selects another page of receiver groups. Refer to patterns and selection for the role-local indices and packed family arrays.

The layer below combines Free/Grid transmitter and receiver patterns, Free/Grid/Gaussian ray bundles, and two mapping pages. Switching uses the three scalar selectors rather than replacing the arrays:

#usda 1.0
(
    defaultPrim = "World"
    metersPerUnit = 1
    upAxis = "Z"
    timeCodesPerSecond = 10
    subLayers = [@generic_wpm_base.usda@]
)

over "World"
{
    over "Sensor"
    {
        # Role order consumes successive rows from each named family.
        token[] omni:sensor:GenericWpm:tx:patternTypes = ["free", "grid", "free"]
        uint[] omni:sensor:GenericWpm:freePatterns:tx:numElementsPerPattern = [1, 1]
        float3[] omni:sensor:GenericWpm:freePatterns:tx:positions = [(0, 0, 0), (0, 0.2, 0)]
        float4[] omni:sensor:GenericWpm:freePatterns:tx:orientations = [(0, 0, 0, 1), (0, 0, 0, 1)]
        token[] omni:sensor:GenericWpm:gridPatterns:tx:modes = ["PLANAR"]
        uint[] omni:sensor:GenericWpm:gridPatterns:tx:numRows = [1]
        uint[] omni:sensor:GenericWpm:gridPatterns:tx:numCols = [2]
        float[] omni:sensor:GenericWpm:gridPatterns:tx:startRowUnits = [0]
        float[] omni:sensor:GenericWpm:gridPatterns:tx:endRowUnits = [0]
        float[] omni:sensor:GenericWpm:gridPatterns:tx:startColUnits = [-0.5]
        float[] omni:sensor:GenericWpm:gridPatterns:tx:endColUnits = [0.5]
        token[] omni:sensor:GenericWpm:gridPatterns:tx:layouts = ["rowMajor"]
        float3[] omni:sensor:GenericWpm:gridPatterns:tx:positions = [(0, 0, 0)]
        float4[] omni:sensor:GenericWpm:gridPatterns:tx:orientations = [(0, 0, 0, 1)]

        token[] omni:sensor:GenericWpm:rx:patternTypes = ["free", "grid"]
        uint[] omni:sensor:GenericWpm:freePatterns:rx:numElementsPerPattern = [3]
        float3[] omni:sensor:GenericWpm:freePatterns:rx:positions = [(0, -1, 0), (0, 0, 0), (0, 1, 0)]
        float4[] omni:sensor:GenericWpm:freePatterns:rx:orientations = [(0, 0, 0, 1), (0, 0, 0, 1), (0, 0, 0, 1)]
        token[] omni:sensor:GenericWpm:gridPatterns:rx:modes = ["PLANAR"]
        uint[] omni:sensor:GenericWpm:gridPatterns:rx:numRows = [1]
        uint[] omni:sensor:GenericWpm:gridPatterns:rx:numCols = [2]
        float[] omni:sensor:GenericWpm:gridPatterns:rx:startRowUnits = [0]
        float[] omni:sensor:GenericWpm:gridPatterns:rx:endRowUnits = [0]
        float[] omni:sensor:GenericWpm:gridPatterns:rx:startColUnits = [-0.5]
        float[] omni:sensor:GenericWpm:gridPatterns:rx:endColUnits = [0.5]
        token[] omni:sensor:GenericWpm:gridPatterns:rx:layouts = ["rowMajor"]
        float3[] omni:sensor:GenericWpm:gridPatterns:rx:positions = [(0, 0, 0)]
        float4[] omni:sensor:GenericWpm:gridPatterns:rx:orientations = [(0, 0, 0, 1)]

        token[] omni:sensor:GenericWpm:ray:patternTypes = ["free", "grid", "gaussian"]
        uint[] omni:sensor:GenericWpm:freePatterns:ray:numElementsPerPattern = [1]
        float3[] omni:sensor:GenericWpm:freePatterns:ray:positions = [(0, 0, 0)]
        float4[] omni:sensor:GenericWpm:freePatterns:ray:orientations = [(0, 0, 0, 1)]
        token[] omni:sensor:GenericWpm:gridPatterns:ray:modes = ["SPHERICAL"]
        uint[] omni:sensor:GenericWpm:gridPatterns:ray:numRows = [1]
        uint[] omni:sensor:GenericWpm:gridPatterns:ray:numCols = [3]
        float[] omni:sensor:GenericWpm:gridPatterns:ray:startRowUnits = [0]
        float[] omni:sensor:GenericWpm:gridPatterns:ray:endRowUnits = [0]
        float[] omni:sensor:GenericWpm:gridPatterns:ray:startColUnits = [-5]
        float[] omni:sensor:GenericWpm:gridPatterns:ray:endColUnits = [5]
        token[] omni:sensor:GenericWpm:gridPatterns:ray:layouts = ["rowMajor"]
        float3[] omni:sensor:GenericWpm:gridPatterns:ray:positions = [(0, 0, 0)]
        float4[] omni:sensor:GenericWpm:gridPatterns:ray:orientations = [(0, 0, 0, 1)]
        uint[] omni:sensor:GenericWpm:gaussianPatterns:ray:numElements = [9]
        uint[] omni:sensor:GenericWpm:gaussianPatterns:ray:seeds = [7]
        float[] omni:sensor:GenericWpm:gaussianPatterns:ray:stdDevsHorizontal = [1]
        float[] omni:sensor:GenericWpm:gaussianPatterns:ray:stdDevsVertical = [0.5]
        float3[] omni:sensor:GenericWpm:gaussianPatterns:ray:positions = [(0, 0, 0)]
        float4[] omni:sensor:GenericWpm:gaussianPatterns:ray:orientations = [(0, 0, 0, 1)]

        # Tx role patterns 0, 1, 2 select Free, Grid, Gaussian ray bundles.
        uint[] omni:sensor:GenericWpm:tx:rayPatternIndices = [0, 1, 2]
        token[] omni:sensor:GenericWpm:tx:mappingModes = ["map", "map", "map"]
        uint[] omni:sensor:GenericWpm:tx:mappingTargetRxGroups = [1, 0, 1]
        uint[] omni:sensor:GenericWpm:txRxMappings:numRxGroupsPerPages = [2, 2]
        uint[] omni:sensor:GenericWpm:txRxMappings:numRxPerGroups = [3, 2, 1, 2]
        # Page 0: [0,1,2], [2,0]. Page 1: [1], [0,1].
        uint[] omni:sensor:GenericWpm:txRxMappings:rxIndices = [0, 1, 2, 2, 0, 1, 0, 1]
    }
}
sensor = stage.GetPrimAtPath("/World/Sensor")
ns = "omni:sensor:GenericWpm:"
# Role order consumes successive rows from each named family.
sensor.CreateAttribute(ns + "tx:patternTypes", Sdf.ValueTypeNames.TokenArray, custom=False).Set(
    ["free", "grid", "free"]
)
sensor.CreateAttribute(ns + "rx:patternTypes", Sdf.ValueTypeNames.TokenArray, custom=False).Set(["free", "grid"])
sensor.CreateAttribute(ns + "ray:patternTypes", Sdf.ValueTypeNames.TokenArray, custom=False).Set(
    ["free", "grid", "gaussian"]
)
for role, counts, positions in (
    ("tx", [1, 1], [(0, 0, 0), (0, 0.2, 0)]),
    ("rx", [3], [(0, -1, 0), (0, 0, 0), (0, 1, 0)]),
    ("ray", [1], [(0, 0, 0)]),
):
    free = ns + "freePatterns:" + role + ":"
    sensor.CreateAttribute(free + "numElementsPerPattern", Sdf.ValueTypeNames.UIntArray, custom=False).Set(counts)
    sensor.CreateAttribute(free + "positions", Sdf.ValueTypeNames.Float3Array, custom=False).Set(positions)
    sensor.CreateAttribute(free + "orientations", Sdf.ValueTypeNames.Float4Array, custom=False).Set(
        [(0, 0, 0, 1)] * len(positions)
    )
for role, mode, columns, first, last in (
    ("tx", "PLANAR", 2, -0.5, 0.5),
    ("rx", "PLANAR", 2, -0.5, 0.5),
    ("ray", "SPHERICAL", 3, -5, 5),
):
    grid = ns + "gridPatterns:" + role + ":"
    sensor.CreateAttribute(grid + "modes", Sdf.ValueTypeNames.TokenArray, custom=False).Set([mode])
    sensor.CreateAttribute(grid + "numRows", Sdf.ValueTypeNames.UIntArray, custom=False).Set([1])
    sensor.CreateAttribute(grid + "numCols", Sdf.ValueTypeNames.UIntArray, custom=False).Set([columns])
    sensor.CreateAttribute(grid + "startRowUnits", Sdf.ValueTypeNames.FloatArray, custom=False).Set([0])
    sensor.CreateAttribute(grid + "endRowUnits", Sdf.ValueTypeNames.FloatArray, custom=False).Set([0])
    sensor.CreateAttribute(grid + "startColUnits", Sdf.ValueTypeNames.FloatArray, custom=False).Set([first])
    sensor.CreateAttribute(grid + "endColUnits", Sdf.ValueTypeNames.FloatArray, custom=False).Set([last])
    sensor.CreateAttribute(grid + "layouts", Sdf.ValueTypeNames.TokenArray, custom=False).Set(["rowMajor"])
    sensor.CreateAttribute(grid + "positions", Sdf.ValueTypeNames.Float3Array, custom=False).Set([(0, 0, 0)])
    sensor.CreateAttribute(grid + "orientations", Sdf.ValueTypeNames.Float4Array, custom=False).Set([(0, 0, 0, 1)])
ray = ns + "gaussianPatterns:ray:"
sensor.CreateAttribute(ray + "numElements", Sdf.ValueTypeNames.UIntArray, custom=False).Set([9])
sensor.CreateAttribute(ray + "seeds", Sdf.ValueTypeNames.UIntArray, custom=False).Set([7])
sensor.CreateAttribute(ray + "stdDevsHorizontal", Sdf.ValueTypeNames.FloatArray, custom=False).Set([1])
sensor.CreateAttribute(ray + "stdDevsVertical", Sdf.ValueTypeNames.FloatArray, custom=False).Set([0.5])
sensor.CreateAttribute(ray + "positions", Sdf.ValueTypeNames.Float3Array, custom=False).Set([(0, 0, 0)])
sensor.CreateAttribute(ray + "orientations", Sdf.ValueTypeNames.Float4Array, custom=False).Set([(0, 0, 0, 1)])
# Tx role patterns 0, 1, 2 select Free, Grid, Gaussian ray bundles.
sensor.CreateAttribute(ns + "tx:rayPatternIndices", Sdf.ValueTypeNames.UIntArray, custom=False).Set([0, 1, 2])
sensor.CreateAttribute(ns + "tx:mappingModes", Sdf.ValueTypeNames.TokenArray, custom=False).Set(
    ["map", "map", "map"]
)
sensor.CreateAttribute(ns + "tx:mappingTargetRxGroups", Sdf.ValueTypeNames.UIntArray, custom=False).Set([1, 0, 1])
mapping = ns + "txRxMappings:"
sensor.CreateAttribute(mapping + "numRxGroupsPerPages", Sdf.ValueTypeNames.UIntArray, custom=False).Set([2, 2])
sensor.CreateAttribute(mapping + "numRxPerGroups", Sdf.ValueTypeNames.UIntArray, custom=False).Set([3, 2, 1, 2])
# Page 0: [0,1,2], [2,0]. Page 1: [1], [0,1].
sensor.CreateAttribute(mapping + "rxIndices", Sdf.ValueTypeNames.UIntArray, custom=False).Set(
    [0, 1, 2, 2, 0, 1, 0, 1]
)

The stored pages contain groups [0, 1, 2] and [2, 0] on page 0, and [1] and [0, 1] on page 1. The following selections are valid:

Tx / Rx / page

Active Tx / Rx

Rays per Tx

Ordered Rx IDs

Slots

0 / 0 / 0

1 / 3

1 (Free)

[2, 0]

2

1 / 1 / 1

2 / 2

3 (Grid)

[1]

6

2 / 1 / 1

1 / 2

9 (Gaussian)

[0, 1]

18

For the initial selection, transmitter pattern 0 assigns ray bundle 0 and page-local group 1. Page 0’s first group consumes the first three entries of rxIndices, so group 1 reads entries 3 and 4: receiver IDs [2, 0]. Its two slots address elements of receiver pattern 0, not stored global IDs.

Requesting 0 / 1 / 0 is invalid because page 0 includes receiver ID 2 while receiver pattern 1 has only two elements. If the previous accepted selection was 2 / 1 / 1, it remains active with 18 slots. Selecting 0 / 0 / 0 again returns to two slots. This demonstrates why page and receiver selections must be coordinated and why output metadata reports the accepted selectors.

Publish coordinated edits#

Write all related selectors at one new ordinal, finish the writes, and advance the write floor once before rendering. Do not step between those writes. The paired Python and C example shows this sequence. Read the accepted selector parameters from the completed output before deciding that an edit took effect. Attribute meanings are on USD Attributes; complete types and defaults are in Sensor API Schemas.

Verification#

  1. Run the example with live edits.

  2. The requested selector triplets are 0/0/0, 1/1/1, 2/1/1, 0/1/0 and 0/0/0.

  3. Read the accepted triplet and capacity after each request: capacities are 2, 6, 18, 18 and 2.

  4. The invalid fourth request must retain 2/1/1; the final request must restore 0/0/0.

  5. This verifies configuration acceptance rather than a scene-dependent valid-return count.

Potential issues#

Use the Generic wpm checks for confirming checks and recovery.

  • The accepted selectors stay unchanged. A rejected batch retains the complete previously accepted configuration while the scene continues to advance. Refer to configuration acceptance.