Source code for unilab.base.scene

"""UniLab configuration owner layered over the package-neutral UniSim scene."""

from __future__ import annotations

from collections.abc import Mapping
from dataclasses import dataclass, field

import numpy as np
from unisim.dr.types import FixedVariantLayout, FixedVariantPlan, ModelSourceDescriptor
from unisim.entities import EntityInitialState, EntityVariantBinding, SceneEntitySpec
from unisim.scene import (
    SceneCfg as _UniSimSceneCfg,
)
from unisim.scene import (
    TerrainSceneCfg,
    resolve_scene_default_qpos,
    resolve_scene_fragment_path,
)

from unilab.base.entity import EntityCfg


[docs] @dataclass class SceneCfg(_UniSimSceneCfg): """Task-owned scene declaration with UniLab entity materialization. Physics scene fields and all backend-facing behavior are implemented by UniSim. UniLab only converts Hydra/OmegaConf entity mappings into the manager facade's :class:`EntityCfg` records on the cold configuration path. """ model_file: str = "" fragment_files: list[str] = field(default_factory=list) terrain: TerrainSceneCfg | None = None entities: dict[str, object] = field(default_factory=dict) entity_assets: tuple[SceneEntitySpec, ...] = () entity_variant: EntityVariantBinding | None = None primary_entity: str | None = None
[docs] def materialize_entities(self) -> None: """Convert task/Hydra physical records without importing a physics SDK.""" physical = [] for raw in self.entity_assets: if isinstance(raw, SceneEntitySpec): physical.append(raw) continue if not isinstance(raw, Mapping): raise TypeError("entity_assets entries must be SceneEntitySpec or mappings") values = dict(raw) source = values.get("source") if isinstance(source, Mapping): values["source"] = ModelSourceDescriptor(**source) state = values.get("initial_state") if isinstance(state, Mapping): values["initial_state"] = EntityInitialState( **{key: tuple(value) for key, value in state.items()} ) physical.append(SceneEntitySpec(**values)) self.entity_assets = tuple(physical) if isinstance(self.entity_variant, Mapping): binding = dict(self.entity_variant) plan = binding.get("plan") if isinstance(plan, Mapping): data = dict(plan) data["assignment"] = np.asarray(data["assignment"]) data["variants"] = tuple( ModelSourceDescriptor(**source) if isinstance(source, Mapping) else source for source in data["variants"] ) if "layout" in data: data["layout"] = FixedVariantLayout(data["layout"]) binding["plan"] = FixedVariantPlan(**data) self.entity_variant = EntityVariantBinding(**binding) if ( self.entity_assets and self.primary_entity is not None and self.primary_entity not in {entity.name for entity in self.entity_assets} ): raise ValueError("primary_entity must name a declared physical entity") self.validate_composition()
def __post_init__(self) -> None: self.materialize_entities() materialized: dict[str, object] = {} for name, value in self.entities.items(): if isinstance(value, EntityCfg): materialized[name] = value elif isinstance(value, Mapping): materialized[name] = EntityCfg(**value) else: materialized[name] = value self.entities = materialized super().__post_init__()
__all__ = [ "SceneCfg", "TerrainSceneCfg", "resolve_scene_default_qpos", "resolve_scene_fragment_path", ]