Skip to content

Options

Every configurable piece of Eden (the env, the scene and its entities, materials, sensors, managers and their terms, runners) is described by an options class: a Pydantic model that validates when you construct it and serializes into the run's saved config. This page documents the base classes; the pages in this section document each family.

from eden.options.envs import EnvOptions
from eden.options.entities import BoxOptions, SceneOptions

Strict and dynamic options

ConfigurableOptions defaults to extra="forbid": a misspelled field raises at construction and names the fields the class does accept. Containers that hold named children (SceneOptions holds entities, *ManagerOptions hold terms, SensorsOptions and CamerasOptions hold sensors and cameras) subclass DynamicConfigurableOptions, which allows extra fields; each extra is one child, keyed by its name. Genesis' own options classes (gs.morphs.*, gs.sensors.*) keep Genesis' strict validation.

Copy an options object with clone() (or copy.deepcopy), not with Pydantic's model_copy(deep=True); and use updated(**changes) to get a validated copy with some fields changed. lock() freezes an options tree and unlock() thaws it. The task configuration guide covers serialization.

ConfigurableOptions: base class for all Eden options.

:class:ConfigurableOptions extends the Genesis Options Pydantic model with Eden conventions: lock()/unlock() to freeze/unfreeze a config, clone() for a lock-aware deep copy, and a best-effort __deepcopy__ that tolerates unpicklable objects (e.g. USD stages).

Copying. clone() is the canonical way to duplicate an options object — prefer it (or bare copy.deepcopy) over pydantic's model_copy(deep=True), because only the former routes through this class's __deepcopy__ and therefore inherits the unpicklable-safe fallback (a value that can't be deep-copied is shared by reference rather than raising). __deepcopy__ is faithful: the clone mirrors the source's lock state. clone(unlock=True) is the explicit "copy so I can edit it" path — it returns a mutable clone regardless of the source, leaving the source untouched. updated(**fields) is the copy-with-changes path: a deep copy with those fields replaced, whose names are validated like construction (so is model_copy(update=...)).

Locking. Locking is tree-scoped and downward-propagating: lock()/unlock() apply to the node they are called on and recurse into all nested options (through declared fields, extras, and any list/tuple/dict of options). They never walk upward, so a child can lock/unlock its own subtree without affecting its parent — reaching a mixed state (e.g. a locked config with one locally-unlocked child) is intentional and supported; the __setattr__ guard keys off each node's own _locked. Both calls are idempotent per node (a no-op if the node is already in the target state), so a redundant parent lock() does not clobber a child that was toggled locally. The lock is not part of an options object's value: a locked object == the same values unlocked.

The default is extra="forbid" so every fixed-schema options class (EnvOptions, entity/material/camera options, rsl_rl configs, ...) rejects misspelled fields loudly. Classes that legitimately carry dynamically-named children (scene entities, manager terms, observation terms, cameras, sensors, splat PCDs) or receive framework-injected .configure() params (name / params) as extras subclass :class:DynamicConfigurableOptions instead, which restores extra="allow".

Attributes

T module-attribute

T = TypeVar('T', bound=Options)

Classes

ConfigurableOptions

ConfigurableOptions(**data)

Bases: Options

Base class for configurable options.

Use with ConfigurableMixin.

Source code in eden/options/options.py
def __init__(self, **data):
    # Seed type metadata as construction data so BaseModel validates it into
    # __pydantic_extra__ (no-op for extra="forbid" classes, which use
    # the serialization envelope instead).
    data.update(option_metadata(self))
    # NOTE: all of the option modifications/validations should be done in the __init__ method
    try:
        BaseModel.__init__(self, **data)
    except ValidationError as exc:
        # A misspelled field on an extra="forbid" class should teach the field set, not just
        # name the typo. Only rewritten when every error is extra_forbidden.
        enriched = _enrich_extra_forbidden(type(self), exc)
        if enriched is None:
            raise
        raise enriched from None

Attributes

keys cached property
keys

Mimic dict.keys(): declared field names + dynamic extras, without triggering value serialization.

Methods:

lock
lock()

Freeze this options node against further mutation, recursing into all children.

Tree-scoped and downward-propagating: applies to this node and every nested options object (declared fields, extras, and options inside list/tuple/dict). It never walks upward, so locking a child does not lock its parent. Idempotent (a no-op if already locked). See the module docstring for the full lock model.

Source code in eden/options/options.py
def lock(self):
    """Freeze this options node against further mutation, recursing into all children.

    Tree-scoped and downward-propagating: applies to this node and every nested
    options object (declared fields, extras, and options inside list/tuple/dict).
    It never walks upward, so locking a child does not lock its parent. Idempotent
    (a no-op if already locked). See the module docstring for the full lock model.
    """
    if self._locked:
        return

    self._locked = True

    def lock_recursive(obj):
        if isinstance(obj, ConfigurableOptions):
            obj.lock()
        elif isinstance(obj, (list, tuple)):
            for item in obj:
                lock_recursive(item)
        elif isinstance(obj, dict):
            for item in obj.values():
                lock_recursive(item)

    for value in self.__dict__.values():
        lock_recursive(value)
    if self.__pydantic_extra__:
        for value in self.__pydantic_extra__.values():
            lock_recursive(value)
unlock
unlock()

Re-enable modification of this options node, recursing into all children.

Exact mirror of :meth:lock (same downward propagation, no upward walk, same idempotency), so a child can unlock its own subtree without affecting its parent. _locked is exempt from the __setattr__ guard, so this clears it even on a currently-locked object.

Source code in eden/options/options.py
def unlock(self):
    """Re-enable modification of this options node, recursing into all children.

    Exact mirror of :meth:`lock` (same downward propagation, no upward walk, same
    idempotency), so a child can unlock its own subtree without affecting its
    parent. ``_locked`` is exempt from the ``__setattr__`` guard, so this clears it
    even on a currently-locked object.
    """
    if not self._locked:
        return

    self._locked = False

    def unlock_recursive(obj):
        if isinstance(obj, ConfigurableOptions):
            obj.unlock()
        elif isinstance(obj, (list, tuple)):
            for item in obj:
                unlock_recursive(item)
        elif isinstance(obj, dict):
            for item in obj.values():
                unlock_recursive(item)

    for value in self.__dict__.values():
        unlock_recursive(value)

    if self.__pydantic_extra__:
        for value in self.__pydantic_extra__.values():
            unlock_recursive(value)
clone
clone(unlock: bool = False) -> ConfigurableOptions

Return a deep copy of this options object — the canonical way to duplicate options.

Prefer this (or bare copy.deepcopy) over model_copy(deep=True): only these route through :meth:__deepcopy__ and thus tolerate unpicklable values (shared by reference instead of raising). By default the clone mirrors this object's lock state (a copy of a locked config stays locked). Pass unlock=True for a mutable clone regardless of the source — the explicit "copy so I can edit it" path, so re-enabling mutation is visible at the call site rather than a silent side effect of copying. The source is never modified.

Source code in eden/options/options.py
def clone(self, unlock: bool = False) -> "ConfigurableOptions":
    """Return a deep copy of this options object — the canonical way to duplicate options.

    Prefer this (or bare ``copy.deepcopy``) over ``model_copy(deep=True)``: only these
    route through :meth:`__deepcopy__` and thus tolerate unpicklable values (shared by
    reference instead of raising). By default the clone mirrors this object's lock
    state (a copy of a locked config stays locked). Pass ``unlock=True`` for a mutable
    clone regardless of the source — the explicit "copy so I can edit it" path, so
    re-enabling mutation is visible at the call site rather than a silent side effect
    of copying. The source is never modified.
    """
    new_obj = copy.deepcopy(self)
    if unlock:
        new_obj.unlock()
    return new_obj
updated
updated(**updates: Any) -> Self

Return a deep copy with the given fields replaced — the way to override inherited options.

base.track_linear_velocity.updated(weight=0.0) reads as what it does, and a misspelled name raises on that line (see :meth:model_copy, which this builds on: deep=True so the copy shares nothing with the inherited default, keywords so the names are not buried in a dict).

The copy is always unlocked, even when this object is locked. A variant is made to be edited further, and the call already says "copy with changes" at the call site — the same visibility clone(unlock=True) asks for. There is no unlock= switch because every keyword here is a field name. model_copy stays faithful and mirrors the lock state; the source is never modified.

PARAMETER DESCRIPTION
**updates

Field values to change on the copy. Unknown names raise ValueError.

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
Self

The updated, unlocked deep copy.

Source code in eden/options/options.py
def updated(self, **updates: Any) -> Self:
    """Return a deep copy with the given fields replaced — the way to override inherited options.

    ``base.track_linear_velocity.updated(weight=0.0)`` reads as what it does, and a misspelled
    name raises on that line (see :meth:`model_copy`, which this builds on: ``deep=True`` so the
    copy shares nothing with the inherited default, keywords so the names are not buried in a dict).

    The copy is always **unlocked**, even when this object is locked. A variant is made to be
    edited further, and the call already says "copy with changes" at the call site — the same
    visibility ``clone(unlock=True)`` asks for. There is no ``unlock=`` switch because every
    keyword here is a field name. ``model_copy`` stays faithful and mirrors the lock state; the
    source is never modified.

    Parameters
    ----------
    **updates
        Field values to change on the copy. Unknown names raise ``ValueError``.

    Returns
    -------
    Self
        The updated, unlocked deep copy.
    """
    copied = self.model_copy(update=updates, deep=True)
    copied.unlock()
    return copied
dict
dict(include_extra: bool = False)
Source code in eden/options/options.py
def dict(self, include_extra: bool = False):
    def clean_field(input_dict):
        return {
            key: clean_field(value) if isinstance(value, dict) else value
            for key, value in input_dict.items()
            if key not in OPTION_METADATA_KEYS
        }

    dumped = self.model_dump()
    if include_extra:
        return dumped
    return clean_field(dumped)
to_dict
to_dict()

Interface for rsl_rl.

Source code in eden/options/options.py
def to_dict(self):
    """Interface for rsl_rl."""
    return self.dict()

DynamicConfigurableOptions

DynamicConfigurableOptions(**data)

Bases: ConfigurableOptions

ConfigurableOptions variant that keeps extra="allow".

Only the classes that genuinely need it subclass this:

  • Dynamic named children — holders that store their children as extra fields: SceneOptions (entities), ManagerOptions (terms), ObservationGroupOptions (obs terms), CamerasOptions / SensorsOptions, and the splat holders SplatOptions / NyxSplatOptions (which collect named PCDOptions children).
  • Framework-injected params — the *TermOptions and other .configure() targets, whose per-term parameters land in __pydantic_extra__.

Open extras are NOT unvalidated: manager term options subclass :class:~eden.options.managers.base.ManagerTermOptions, which validates extras against the registered term's parameter names at construction time whenever name resolves in the kind's term registry (RewardTermOptions(name="upright", wieght=2.0) raises), and ConfigurableMixin.__init__ re-checks at build time. Non-term .configure() targets (extension options) rely on the .configure()-path name validation only.

Source code in eden/options/options.py
def __init__(self, **data):
    # Seed type metadata as construction data so BaseModel validates it into
    # __pydantic_extra__ (no-op for extra="forbid" classes, which use
    # the serialization envelope instead).
    data.update(option_metadata(self))
    # NOTE: all of the option modifications/validations should be done in the __init__ method
    try:
        BaseModel.__init__(self, **data)
    except ValidationError as exc:
        # A misspelled field on an extra="forbid" class should teach the field set, not just
        # name the typo. Only rewritten when every error is extra_forbidden.
        enriched = _enrich_extra_forbidden(type(self), exc)
        if enriched is None:
            raise
        raise enriched from None

Functions:

Pages

Page Module Contents
Environment eden.options.envs EnvOptions (simulation, solver, batching), CudaGraphOptions
Entities eden.options.entities entity, primitive, terrain, grouped and composite options, SceneOptions
Materials eden.options.materials rigid, kinematic, MPM, SPH, PBD, FEM and Mochi materials
Surfaces eden.options.surfaces surface appearance presets
Scenes eden.options.scenes scene presets
Robots eden.options.robots robot presets and composite rigs
Actuators eden.options.actuators actuator specs and motor presets
Sensors and cameras eden.options.sensors, eden.options.camera sensor and camera options
Splats and renderers eden.options.splat, eden.options.renderer Gaussian splats, point clouds, ray tracer, batch renderer
Managers eden.options.managers manager and term options
Learning eden.options.learning rsl_rl and rl_games runner options

Dataset options are on the datasets page and extension options on each extension page.

Resolving options classes by name

eden.options.catalog answers "which options class does this name mean?" for the eden view and eden snap commands and for robots: ROBOT_REGISTRY maps a robot key to its class, and the curated MANIFEST describes assets semantically (category, placement, tags).

Resolving entity Options classes by name — two layers over one "module:Class" pointer.

Eden assets (eden.options.objects.*, eden.options.robots.*) know how to load a mesh (their EntityOptions carry up/front/scale etc.) but the question "which Options class does this name mean?" is answered here, two ways, both lazily importing only the one module they need:

  • Auto-scan index (:func:find_entity_options / :func:entity_options_index) — exhaustive. A filesystem walk of the asset packages, cached on disk and keyed by a source fingerprint, maps every class name (RoboLabYcbBanana) and family-registry key (robolab/ycb/banana) to its "module:Class" pointer. This backs the eden view / eden snap CLI, where a warm lookup imports only the single defining module.

  • Curated manifest (:data:MANIFEST) — a hand-curated semantic subset. An asset is curated by decorating its Options class::

    @MANIFEST.curate("sofa", "furniture/seating", tags=("seating",), description="a couch")
    @REPLICA_CAD_OBJECT_REGISTRY.register(name="frl_apartment_sofa")
    class FRLApartmentSofa(ReplicaCADObjectOptions): ...
    

What's curated is semantic only (no EntityOptions field is re-declared): category (/-delimited taxonomy path), placement (floor | surface | wall | ceiling), support_surface (place-on target), container (place-in target), tags, description. Physical pose stays on the Options class (up/front/scale); AssetSpec.movable is derived at curation time from the constructed Options (not is_fixed_base), never hand-curated, so it can't drift from the class.

Variant families (one Options class serving many mesh variants, e.g. Objaverse) curate each variant with the direct-call form and make_kwargs — the variant selector baked into :meth:AssetSpec.make (explicit make() overrides still win)::

    MANIFEST.curate("apple_3", "food/fruit", make_kwargs={"variant": 3})(ObjaverseApple)

Population is never eager — so importing Eden stays cheap no matter how many assets exist: a @curate decorator registers its asset only when that family module is imported, and re-import is idempotent (a true name collision raises); :meth:AssetManifest.build imports the known families on demand; :meth:ensure is the build-if-empty entry point for consumers.

Both layers share one "module:Class" spelling and one lazy resolver (:func:_resolve): AssetSpec stores options_path (so :meth:AssetManifest.summary rows are JSON-ready and :meth:make imports only the one class it constructs), and the auto-scan index stores the same.

from eden.options.catalog import MANIFEST, find_entity_options
MANIFEST.ensure()                                  # populate (imports asset families)
MANIFEST.facets()                                  # what can I query? axes → valid values
MANIFEST.names(category="furniture/seating")       # drill down → ['armchair','chair','sofa']
MANIFEST.search("mug")                             # free text → ['coffee_cup', ...]
opts = MANIFEST.make("sofa", default_root_pos=(0, 0, 0))   # lazily imports + constructs
cls = find_entity_options("RoboLabYcbBanana")      # exhaustive auto-scan lookup

Attributes

ROBOT_REGISTRY module-attribute

ROBOT_REGISTRY = RobotRegistry('robot', populated_by=('eden.options.robots',))

MANIFEST module-attribute

MANIFEST = AssetManifest('asset')

Classes

RobotRegistry

RobotRegistry(name: str, populated_by: Sequence[str] = ())

Bases: Registry

Friendly name -> RobotOptions subclass lookup for robots.

A plain :class:~eden.utils.registry.Registry that only adds a type guard: every entry must be a :class:~eden.options.entities.RobotOptions subclass. The check lives in :meth:_do_register, the single chokepoint both the decorator and the direct-call registration forms funnel through, so the inherited register() API is extended (not disabled — unlike :class:AssetManifest, which forbids it).

Robots register themselves in (or alongside) their option modules::

@ROBOT_REGISTRY.register(name="g1")
class G1Dofs29(RobotOptions): ...

ROBOT_REGISTRY.register(FrankaEmikaPanda)   # direct-call form

Keys default to to_snake_case(cls.__name__); pass name= for a cleaner alias. The exhaustive :func:find_entity_options index discovers this registry too and surfaces its entries as robot/<key> (plus a bare <key> when unambiguous).

Source code in eden/utils/registry.py
def __init__(self, name: str, populated_by: Sequence[str] = ()) -> None:
    self._name: str = name
    self._obj_map: dict[str, Any] = {}
    self._populated_by: tuple[str, ...] = tuple(populated_by)
    self._populated: bool = False
    #: Thread ids currently inside :meth:`_populate`. A set, not a flag, because the only
    #: thread that may skip the import is one already doing it — see :meth:`_populate`.
    self._populating: set[int] = set()
    #: The exception a failed :meth:`_populate` raised, replayed to every later reader
    #: rather than retried — see :meth:`_populate`.
    self._populate_error: BaseException | None = None
    #: That exception's traceback as first caught, kept separately because re-raising the
    #: exception rebinds its ``__traceback__`` to a longer chain each time — see
    #: :meth:`_populate`.
    self._populate_error_tb: TracebackType | None = None

AssetManifest

AssetManifest(name: str, populated_by: Sequence[str] = ())

Bases: Registry

Tree-structured registry of :class:AssetSpec, keyed by friendly name.

Not populated on import: call :meth:ensure (or :meth:build) first. :meth:make imports the chosen asset's class lazily.

Source code in eden/utils/registry.py
def __init__(self, name: str, populated_by: Sequence[str] = ()) -> None:
    self._name: str = name
    self._obj_map: dict[str, Any] = {}
    self._populated_by: tuple[str, ...] = tuple(populated_by)
    self._populated: bool = False
    #: Thread ids currently inside :meth:`_populate`. A set, not a flag, because the only
    #: thread that may skip the import is one already doing it — see :meth:`_populate`.
    self._populating: set[int] = set()
    #: The exception a failed :meth:`_populate` raised, replayed to every later reader
    #: rather than retried — see :meth:`_populate`.
    self._populate_error: BaseException | None = None
    #: That exception's traceback as first caught, kept separately because re-raising the
    #: exception rebinds its ``__traceback__`` to a longer chain each time — see
    #: :meth:`_populate`.
    self._populate_error_tb: TracebackType | None = None

Methods:

curate
curate(name: str, category: str, *, placement: Placement = 'floor', support_surface: bool = False, container: bool = False, tags=(), description: str = '', make_kwargs: dict | None = None)

Curate the decorated EntityOptions subclass as an asset.

Usable as a stacked decorator, or in direct-call form for variant families (one class, many curated names): curate("apple_3", ..., make_kwargs={"variant": 3})(cls).

Source code in eden/options/catalog.py
def curate(
    self,
    name: str,
    category: str,
    *,
    placement: Placement = "floor",
    support_surface: bool = False,
    container: bool = False,
    tags=(),
    description: str = "",
    make_kwargs: dict | None = None,
):
    """Curate the decorated ``EntityOptions`` subclass as an asset.

    Usable as a stacked decorator, or in direct-call form for variant families
    (one class, many curated names): ``curate("apple_3", ..., make_kwargs={"variant": 3})(cls)``.
    """
    kwargs = dict(make_kwargs or {})

    def deco(cls):
        # Construct the Options once, here where the class is in hand, to derive `movable`
        # the way make() actually builds it (running model_post_init's fixed/is_fixed_base
        # unification). This also fail-fasts at the curation site if make_kwargs is wrong,
        # rather than at a downstream make()/query. Options construction is offline (no
        # mesh load — that happens at entity build) and cheap.
        try:
            movable = not cls(**kwargs).is_fixed_base
        except Exception as exc:  # noqa: BLE001 — re-raise with the curation site attached
            raise ValueError(f"curate({name!r}): cannot construct {cls.__name__}(**{kwargs!r}): {exc}") from exc
        self._curate(
            AssetSpec(
                name=name,
                options_path=f"{cls.__module__}:{cls.__name__}",
                category=category,
                placement=placement,
                support_surface=support_surface,
                container=container,
                tags=tuple(tags),
                description=description,
                make_kwargs=kwargs,
                movable=movable,
            )
        )
        return cls

    return deco
register
register(obj=None, name=None)

Disable the inherited register entry point.

The base :meth:Registry.register would store a raw class where an :class:AssetSpec is expected, silently corrupting every query method. Curate assets (with their semantic metadata) via :meth:curate instead.

Source code in eden/options/catalog.py
def register(self, obj=None, name=None):  # noqa: ARG002
    """Disable the inherited register entry point.

    The base :meth:`Registry.register` would store a raw class where an :class:`AssetSpec`
    is expected, silently corrupting every query method. Curate assets (with their semantic
    metadata) via :meth:`curate` instead.
    """
    raise NotImplementedError(
        "AssetManifest is populated via @MANIFEST.curate(name, category, ...), not register()."
    )
make
make(name: str, **overrides) -> EntityOptions
Source code in eden/options/catalog.py
def make(self, name: str, **overrides) -> EntityOptions:
    return self.get(name).make(**overrides)
build
build(modules: tuple[str, ...] | None = None) -> AssetManifest

Import the asset families so their @curate decorators populate the manifest.

Source code in eden/options/catalog.py
def build(self, modules: tuple[str, ...] | None = None) -> AssetManifest:
    """Import the asset families so their ``@curate`` decorators populate the manifest."""
    for module in modules or ASSET_MODULES:
        importlib.import_module(module)
    return self
ensure
ensure(modules: tuple[str, ...] | None = None) -> AssetManifest

Populate from the asset families if empty (idempotent); the consumer entry point.

Source code in eden/options/catalog.py
def ensure(self, modules: tuple[str, ...] | None = None) -> AssetManifest:
    """Populate from the asset families if empty (idempotent); the consumer entry point."""
    if not self._obj_map:
        self.build(modules)
    return self
names
names(*, category: str | None = None, placement: str | None = None, tag: str | None = None, support_surface: bool | None = None, container: bool | None = None, movable: bool | None = None) -> list[str]

Asset names matching every given filter. Filter axes/values come from :meth:facets.

Source code in eden/options/catalog.py
def names(
    self,
    *,
    category: str | None = None,
    placement: str | None = None,
    tag: str | None = None,
    support_surface: bool | None = None,
    container: bool | None = None,
    movable: bool | None = None,
) -> list[str]:
    """Asset names matching every given filter. Filter axes/values come from :meth:`facets`."""
    # string axes treat ""/None as "no filter"; the boolean axes need `is not None` since
    # False is a real filter value.
    out = []
    for name, spec in self:
        if category and not _is_under(spec.category_path, category):
            continue
        if placement and spec.placement != placement:
            continue
        if tag and tag not in spec.tags:
            continue
        if support_surface is not None and spec.support_surface != support_surface:
            continue
        if container is not None and spec.container != container:
            continue
        if movable is not None and spec.movable != movable:
            continue
        out.append(name)
    return sorted(out)
search
search(query: str) -> list[str]

Find asset names by free text.

Case-insensitive substring match over each asset's name, category path, tags, and description — the loosest entry point for an agent resolving task language ("a mug", "trash") to catalog names. An empty query raises (use :meth:names or :meth:summary to list the whole catalog).

Source code in eden/options/catalog.py
def search(self, query: str) -> list[str]:
    """Find asset names by free text.

    Case-insensitive substring match over each asset's name, category path, tags, and
    description — the loosest entry point for an agent resolving task language
    ("a mug", "trash") to catalog names. An empty query raises (use :meth:`names` or
    :meth:`summary` to list the whole catalog).
    """
    q = query.strip().lower()
    if not q:
        raise ValueError("search() needs a non-empty query; use names() or summary() to list everything")
    return sorted(
        name for name, spec in self if q in " ".join((name, spec.category, *spec.tags, spec.description)).lower()
    )
facets
facets() -> dict[str, list]

Describe the available queries: each filter axis :meth:names accepts → its present values.

Source code in eden/options/catalog.py
def facets(self) -> dict[str, list]:
    """Describe the available queries: each filter axis :meth:`names` accepts → its present values."""
    cats, places, tags, supports, containers, movables = set(), set(), set(), set(), set(), set()
    for _, spec in self:
        cats.add(spec.category)
        places.add(spec.placement)
        tags.update(spec.tags)
        supports.add(spec.support_surface)
        containers.add(spec.container)
        movables.add(spec.movable)
    return {
        "category": sorted(cats),
        "placement": sorted(places),
        "tag": sorted(tags),
        "support_surface": sorted(supports),
        "container": sorted(containers),
        "movable": sorted(movables),
    }
summary
summary() -> list[dict]

Flat per-asset records (JSON-ready; handy to dump into an agent's context).

Source code in eden/options/catalog.py
def summary(self) -> list[dict]:
    """Flat per-asset records (JSON-ready; handy to dump into an agent's context)."""
    return [{**asdict(spec), "tags": list(spec.tags)} for _, spec in sorted(self, key=lambda kv: kv[0])]
tree
tree() -> dict

Nested taxonomy: {node: {child: {...}, '__assets__': [names]}} from category paths.

Source code in eden/options/catalog.py
def tree(self) -> dict:
    """Nested taxonomy: ``{node: {child: {...}, '__assets__': [names]}}`` from category paths."""
    root: dict = {}
    for name, spec in self:
        node = root
        for part in spec.category_path:
            node = node.setdefault(part, {})
        node.setdefault(_LEAF, []).append(name)
    return root

AssetSpec dataclass

AssetSpec(name: str, options_path: str, category: str = 'object', placement: Placement = 'floor', support_surface: bool = False, container: bool = False, tags: tuple[str, ...] = tuple(), description: str = '', make_kwargs: dict = dict(), movable: bool = True)

Semantic metadata for one curated asset (the physical pose lives on the Options class).

Attributes

name instance-attribute
name: str
options_path instance-attribute
options_path: str
category class-attribute instance-attribute
category: str = 'object'
placement class-attribute instance-attribute
placement: Placement = 'floor'
support_surface class-attribute instance-attribute
support_surface: bool = False
container class-attribute instance-attribute
container: bool = False
tags class-attribute instance-attribute
tags: tuple[str, ...] = field(default_factory=tuple)
description class-attribute instance-attribute
description: str = ''
make_kwargs class-attribute instance-attribute
make_kwargs: dict = field(default_factory=dict)
movable class-attribute instance-attribute
movable: bool = True
category_path property
category_path: tuple[str, ...]

Methods:

make
make(**overrides) -> EntityOptions

Construct the asset's Options (lazily importing its class; canonical pose from it).

Curated make_kwargs are applied first; explicit overrides win.

Source code in eden/options/catalog.py
def make(self, **overrides) -> EntityOptions:
    """Construct the asset's Options (lazily importing its class; canonical pose from it).

    Curated ``make_kwargs`` are applied first; explicit ``overrides`` win.
    """
    return _resolve(self.options_path)(**{**self.make_kwargs, **overrides})

Functions:

find_entity_options

find_entity_options(name: str) -> type[BaseEntityOptions]

Resolve an Options class by class name (RoboLabYcbBanana) or registry key (robolab/ycb/banana). Warm lookups import only the defining module.

Source code in eden/options/catalog.py
def find_entity_options(name: str) -> type[BaseEntityOptions]:
    """Resolve an Options class by class name (``RoboLabYcbBanana``) or registry
    key (``robolab/ycb/banana``). Warm lookups import only the defining module.
    """
    index = entity_options_index()
    target = index.get(name)
    if target is None:
        close = difflib.get_close_matches(name, list(index), n=5)
        hint = f" Did you mean: {', '.join(close)}?" if close else ""
        raise ValueError(f"No entity options found for {name}.{hint}")
    return _resolve(target)

entity_options_index

entity_options_index(rebuild: bool = False) -> dict[str, str]

Name -> "module:Class" map of all discoverable entity Options.

Source code in eden/options/catalog.py
def entity_options_index(rebuild: bool = False) -> dict[str, str]:
    """Name -> ``"module:Class"`` map of all discoverable entity Options."""
    global _index
    if rebuild:
        _index = None
    if _index is None:
        _index = None if rebuild else _load_cached_index()
        if _index is None:
            _index = _build_index()
            _save_index(_index)
    return _index

See also