Source code for embodichain.lab.sim.objects.backends.articulation_state
# ----------------------------------------------------------------------------
# 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.
# ----------------------------------------------------------------------------
"""Backend-specific articulation state-layout adapters."""
from __future__ import annotations
import numpy as np
import torch
__all__ = [
"get_state_joint_names",
"map_source_qpos_to_state_order",
"read_state_mimic_info",
]
[docs]
def get_state_joint_names(entity: object) -> list[str]:
"""Return active joint names in the backing state-buffer order."""
try:
layout = entity.joint_dof_layout
except (AttributeError, RuntimeError):
return entity.get_actived_joint_names()
return [joint.name for joint in layout]
[docs]
def map_source_qpos_to_state_order(
entity: object,
qpos: torch.Tensor,
*,
is_spawn_bound: bool,
) -> torch.Tensor:
"""Map source-ordered initial qpos values into runtime state order."""
if not is_spawn_bound:
return qpos
source_joint_names = entity.get_actived_joint_names()
state_joint_names = get_state_joint_names(entity)
if source_joint_names == state_joint_names:
return qpos
source_indices = {name: index for index, name in enumerate(source_joint_names)}
try:
state_order = [source_indices[name] for name in state_joint_names]
except KeyError as error:
raise RuntimeError(
"Spawn articulation state layout contains a joint absent from "
"the source articulation layout."
) from error
return qpos[..., state_order]
[docs]
def read_state_mimic_info(
entity: object,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
"""Read mimic metadata and map source joint ids into state-buffer ids."""
source_info = entity.get_mimic_info()
source_mimic_ids = np.asarray(source_info.mimic_id, dtype=np.int32).reshape(-1)
source_parent_ids = np.asarray(
source_info.mimic_parent,
dtype=np.int32,
).reshape(-1)
multipliers = np.asarray(
source_info.mimic_multiplier,
dtype=np.float32,
).reshape(-1)
offsets = np.asarray(source_info.mimic_offset, dtype=np.float32).reshape(-1)
relation_count = len(source_mimic_ids)
if not all(
len(values) == relation_count
for values in (source_parent_ids, multipliers, offsets)
):
raise RuntimeError("Articulation mimic metadata has inconsistent lengths.")
if relation_count == 0:
return source_mimic_ids, source_parent_ids, multipliers, offsets
source_joint_names = entity.get_actived_joint_names()
try:
state_joint_ids = {
joint.name: int(joint.dof_start) for joint in entity.joint_dof_layout
}
except (AttributeError, RuntimeError):
state_joint_ids = {name: index for index, name in enumerate(source_joint_names)}
try:
mimic_ids = np.asarray(
[
state_joint_ids[source_joint_names[int(source_id)]]
for source_id in source_mimic_ids
],
dtype=np.int32,
)
parent_ids = np.asarray(
[
state_joint_ids[source_joint_names[int(source_id)]]
for source_id in source_parent_ids
],
dtype=np.int32,
)
except (IndexError, KeyError) as error:
raise RuntimeError(
"Articulation mimic metadata references a joint absent from "
"the backing state layout."
) from error
return mimic_ids, parent_ids, multipliers, offsets