Replacing sentinel fallback values with None in schema definitions

We’re exploring using None as an explicit schema fallback value as an alternative to sentinel values (-inf, -1) in 3rd party schema definitions. We want to check whether this is a supported/recommended pattern before adopting it.

Background

Several official schemas use out-of-range constants as sentinels for “unopinionated” where the “engine” picks its own fallback:

• UsdPhysics: centerOfMass = (-inf,-inf,-inf), gravityMagnitude = -inf
• UsdLux: shadow:distance = -1, shadow:falloff = -1

These work, but create awkward documentation (is -1 a valid distance?) and force consumers to special-case values before doing math.

So far we are following this pattern, as most of our schemas inherit & extend existing UsdPhysics schemas.

What we’re considering

Switching from sentinal values to explicit None fallbacks gives us clean valid ranges, simple attr.Get() is None checks in consumer code, and clearer documented intent. It also mirrors our runtime (non-usd) defaults, since we’re a python project (where None is the natural choice).

We do intend to keep values like inf where it’s a real physical value (e.g. maxEffort = inf where clamp(x, -inf, inf) is unbounded).

Why not just omit the fallback value?

Omitting the fallback is ambiguous. It could mean “unset is valid”, “must be authored”, or “someone forgot.”

The questions

  1. Does USD formally distinguish = None from an omitted fallback value, or is this purely a source-level convention?
  2. Do USD-aware UIs (usdview, Solaris, etc.) handle None-fallback typed attrs gracefully?
  3. Would AOUSD WGs consider this pattern for future attrs where the current approach is a sentinel value?

Reference

Here’s an example PR showing it can work on a real 3rd-party (codeless) schema: newton-physics/newton-usd-schemas#69

I like how pandas handles this now with “missing” values: Working with missing data — pandas 3.0.4 documentation

Which handles things consistently independently of the fundamental type (floating, complex, int, etc)

@andrew , thanks for the nice analysis. After discussing on the team, the TLDR is Yes to explicit None fallbacks in schemas, but we need to do some work first for it to be safe (you’ve made us realize it’s currently possible but unsafe).

Firstly, we’d like to revise our guidance on requiring sentinel values as fallbacks; that advice was based on characteristics of our internal execution engine that we thought would carry over to OpenExec, but we’ve since realized we can and will make OpenExec robust to receiving no value for inputs.

Secondly, your argument for explicitly stating an attribute has None/no fallback value resonates. However, we strongly believe that, value-resolution-wise for UsdAttribute::Get() the results must be the same as if there just was no fallback provided (return false and no value is set). Otherwise (if callers might now receive an SdfValueBlock in C++ as a result), it injects the same chaos you rightly pointed out about arbitrary sentinel values. And that is what would happen today if you set a fallback to None in the schema.

Instead, we want to enhance the SchemaRegistry’s PropertyDefinition logic so that a specified None fallback becomes an SdfValueBlock that the PropertyDefinition logic consumes to potentially block weaker fallbacks in the process of "schema composition (e.g. where a child schema overrides the fallback of a parent schema class, or one API schema overrides another).

Are you OK with the difference between “explicit None” and “possibly forgot” being primarily a documentation thing, or do you believe this needs to be a queryable behavior on UsdAttribute, from its backing PropertyDefinition?

Once we’re agreed, we can file an Issue to queue up the work.

@mkoubaa , welcome, and thanks for the link!

For simulators, a common idiom is that “explicit none” corresponding to “I don’t have a preference but I trust the simulator to do Sensible Thing” and so the simulator might need to know “Is the input telling me that I should choose a heuristic or is it an ill-formed input?”.

Are you OK with the difference between “explicit None” and “possibly forgot” being primarily a documentation thing,

Yes I was assuming this is mainly about documentation (which ends up being quite important when working with agents to be honest, I find they tend to respect documented intent).

I do find it quite nice that Usd.Attribute.Get() == None works in python, I wouldn’t want to lose that. The C++ side needing to check the return bool is fine though.

I also find it a bit cumbersome that Usd.Attribute.Set(None) doesn’t work… it’d be nice if it could be made to work (it could just call Block under the hood perhaps). Otherwise I need to teach folks (who might otherwise get away without learning) about Clear/Block semantics, when what they’re actually thinking is “I want to explicitly set the default value”.

Usd.Attribute.Set(None) would never have the semantics “I want to explicitly set the default value”. If it were meaningful, it would have to mean exactly the same thing as Block(), which is not setting a default value - it is clearing weaker opinions out, such that if an attribute has a schema-provided fallback, that’s what you will then get from Get(). Apologies if I’m misunderstanding.