Source code for embodichain.lab.sim.sensors.attachment

# ----------------------------------------------------------------------------
# Copyright (c) 2021-2026 DexForce Technology Co., Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------

"""Resolve camera attachment targets from an explicit scene asset mapping."""

from __future__ import annotations

from collections.abc import Mapping
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from dexsim.engine import Node

    from embodichain.lab.sim.objects import Articulation

__all__ = ["resolve_parent_nodes"]


[docs] def resolve_parent_nodes( parent: str, assets: Mapping[str, Articulation], num_envs: int ) -> list[Node]: """Resolve a canonical Robot or Articulation link in each environment. Args: parent: A canonical link name, or ``"<asset_uid>/<link_name>"`` to disambiguate links shared by multiple assets. assets: Scene asset UIDs mapped to Articulation or Robot instances. num_envs: Expected number of camera and asset instances. Returns: Parent render nodes ordered by environment index. Raises: ValueError: If the parent link is missing or ambiguous. RuntimeError: If the asset count differs from ``num_envs``, or an environment is missing the link or its render node. """ asset_uid: str | None = None link_name = parent if "/" in parent: candidate_uid, candidate_link = parent.split("/", maxsplit=1) if candidate_uid in assets: asset_uid, link_name = candidate_uid, candidate_link matches: list[tuple[str, list[Node]]] = [] for uid, asset in assets.items(): if asset_uid is not None and uid != asset_uid: continue if link_name not in asset.link_names: continue if asset.num_instances != num_envs: raise RuntimeError( f"Camera parent asset {uid!r} has {asset.num_instances} instances " f"for {num_envs} arenas." ) matches.append((uid, asset.get_link_render_nodes(link_name))) if len(matches) == 1: return matches[0][1] if len(matches) > 1: owners = ", ".join(uid for uid, _ in matches) raise ValueError( f"Camera parent link {link_name!r} is ambiguous across assets " f"[{owners}]; use '<asset_uid>/{link_name}'." ) scope = f" on asset {asset_uid!r}" if asset_uid is not None else "" raise ValueError( f"Camera parent link {link_name!r} was not found{scope} in any " "registered Robot or Articulation." )