Skip to content

macros

Set of macros to use globally for OmniGibson. These are generally magic numbers that were tuned heuristically.

NOTE: This is generally decentralized -- the monolithic @settings variable is created here with some global values, but submodules within OmniGibson may import this dictionary and add to it dynamically

MacroDict

Bases: Dict

Source code in OmniGibson/omnigibson/macros.py
class MacroDict(Dict):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self["_read"] = set()
        object.__setattr__(self, "__locked", True)  # disallow value updates after key is read

    def __setattr__(self, name, value):
        if name in self.get("_read", set()) and self.is_locked:
            raise AttributeError(
                f"Cannot set attribute {name} in MacroDict, it has already been used. "
                f"If knowingly overwriting value, set value within context `with unlocked()`"
            )
        # Use the super's setattr for setting attributes, but handle _read directly to avoid recursion.
        if name == "_read":
            self[name] = value
        else:
            super().__setattr__(name, value)

    def __getattr__(self, item):
        # Directly check and modify '_read' to avoid going through __getattr__ or __setattr__.
        if item != "_read":
            self["_read"].add(item)
        # Use direct dictionary access to avoid infinite recursion.
        try:
            return self[item]
        except KeyError:
            raise AttributeError(f"'MacroDict' object has no attribute '{item}'")

    @property
    def is_locked(self):
        """
        Returns True if the config is locked (no value updates allowed after initial read).
        """
        return object.__getattribute__(self, "__locked")

    def lock(self):
        """
        Lock the config. Afterward, key values cannot be updated after initial read
        """
        object.__setattr__(self, "__locked", True)

        for k in self:
            if isinstance(self[k], MacroDict):
                self[k].lock()

    def unlock(self):
        """
        Unlock the config. Afterward, key values can be updated even after initial read
        """
        object.__setattr__(self, "__locked", False)

        for k in self:
            if isinstance(self[k], MacroDict):
                self[k].unlock()

    def _get_lock_state(self):
        """
        Retrieves the lock state of this config.

        Returns:
            lock_state (dict): a dictionary with a "locked" key that is True
                if value updates are locked after the value has been read during runtime
        """
        return {
            "locked": self.is_locked,
        }

    def _set_lock_state(self, lock_state):
        """
        Sets the lock state for this config.

        Args:
            lock_state (dict): a dictionary with an "locked" key that is True
                if value updates are locked after the value has been read during runtime
        """
        if lock_state["locked"]:
            self.lock()

    @contextlib.contextmanager
    def unlocked(self):
        """
        A context scope for modifying a Config object. Within the scope, values can be updated, even after an initial
        read. Upon leaving the scope, the initial level of locking is restored.
        """
        lock_state = self._get_lock_state()
        self.unlock()
        yield
        self._set_lock_state(lock_state)

is_locked property

Returns True if the config is locked (no value updates allowed after initial read).

lock()

Lock the config. Afterward, key values cannot be updated after initial read

Source code in OmniGibson/omnigibson/macros.py
def lock(self):
    """
    Lock the config. Afterward, key values cannot be updated after initial read
    """
    object.__setattr__(self, "__locked", True)

    for k in self:
        if isinstance(self[k], MacroDict):
            self[k].lock()

unlock()

Unlock the config. Afterward, key values can be updated even after initial read

Source code in OmniGibson/omnigibson/macros.py
def unlock(self):
    """
    Unlock the config. Afterward, key values can be updated even after initial read
    """
    object.__setattr__(self, "__locked", False)

    for k in self:
        if isinstance(self[k], MacroDict):
            self[k].unlock()

unlocked()

A context scope for modifying a Config object. Within the scope, values can be updated, even after an initial read. Upon leaving the scope, the initial level of locking is restored.

Source code in OmniGibson/omnigibson/macros.py
@contextlib.contextmanager
def unlocked(self):
    """
    A context scope for modifying a Config object. Within the scope, values can be updated, even after an initial
    read. Upon leaving the scope, the initial level of locking is restored.
    """
    lock_state = self._get_lock_state()
    self.unlock()
    yield
    self._set_lock_state(lock_state)

create_module_macros(module_path)

Creates a dictionary that can be populated with module macros based on the module's @module_path

Parameters:

Name Type Description Default
module_path str

Relative path from the package root directory pointing to the module. This will be parsed to generate the appropriate sub-macros dictionary, e.g., for module "dirty" in omnigibson/object_states_dirty.py, this would generate a dictionary existing at macros.object_states.dirty

required

Returns:

Type Description
MacroDict

addict/macro dictionary which can be populated with values

Source code in OmniGibson/omnigibson/macros.py
def create_module_macros(module_path):
    """
    Creates a dictionary that can be populated with module macros based on the module's @module_path

    Args:
        module_path (str): Relative path from the package root directory pointing to the module. This will be parsed
            to generate the appropriate sub-macros dictionary, e.g., for module "dirty" in
            omnigibson/object_states_dirty.py, this would generate a dictionary existing at macros.object_states.dirty

    Returns:
        MacroDict: addict/macro dictionary which can be populated with values
    """
    # Sanity check module path, make sure omnigibson/ is in the path
    module_path = pathlib.Path(module_path)
    omnigibson_path = pathlib.Path(__file__).parent

    # Trim the .py, and anything before and including omnigibson/, and split into its appropriate parts
    try:
        subsections = module_path.with_suffix("").relative_to(omnigibson_path).parts
    except ValueError:
        raise ValueError(
            "module_path is expected to be a filepath including the omnigibson root directory, got: {module_path}!"
        )

    # Create and return the generated sub-dictionary
    def _recursively_get_or_create_dict(dic, keys):
        # If no entry is in @keys, it returns @dic
        # Otherwise, checks whether the dictionary contains the first entry in @keys, if so, it grabs the
        # corresponding nested dictionary, otherwise, generates a new MacroDict() as the value
        # It then recurisvely calls this function with the new dic and the remaining keys
        if len(keys) == 0:
            return dic
        else:
            key = keys[0]
            if key not in dic:
                dic[key] = MacroDict()
            return _recursively_get_or_create_dict(dic=dic[key], keys=keys[1:])

    return _recursively_get_or_create_dict(dic=macros, keys=subsections)