Source code for mani_skill.utils.building.articulation_builder

from __future__ import annotations

from typing import TYPE_CHECKING, Optional, Sequence, Union

import numpy as np
import sapien
import sapien.physx as physx
import torch
from sapien.wrapper.articulation_builder import (
    ArticulationBuilder as SapienArticulationBuilder,
)
from sapien.wrapper.articulation_builder import LinkBuilder

from mani_skill import logger
from mani_skill.utils import common, sapien_utils
from mani_skill.utils.structs import Articulation, Pose
from mani_skill.utils.structs.pose import to_sapien_pose

if TYPE_CHECKING:
    from mani_skill.envs.scene import ManiSkillScene


[docs]class ArticulationBuilder(SapienArticulationBuilder):
[docs] scene: ManiSkillScene
[docs] disable_self_collisions: bool = False
def __init__(self): super().__init__()
[docs] self.name = None
[docs] self.scene_idxs = None
[docs] self.initial_pose = None
[docs] def set_name(self, name: str): self.name = name return self
[docs] def set_scene_idxs( self, scene_idxs: Optional[ Union[list[int], Sequence[int], torch.Tensor, np.ndarray] ] = None, ): """ Set a list of scene indices to build this object in. Cannot be used in conjunction with scene mask """ self.scene_idxs = scene_idxs return self
[docs] def build_entities(self, *args, **kwargs): raise NotImplementedError( "_build_entities is a private function in ManiSkill. Use build() to properly build articulation" )
[docs] def _build_entities( self, fix_root_link=None, name_prefix="", initial_pose=sapien.Pose() ): entities = [] links = [] for b in self.link_builders: b._check() b.physx_body_type = "link" entity = sapien.Entity() link_component = b.build_physx_component( links[b.parent.index] if b.parent else None ) entity.add_component(link_component) if self.scene.can_render(): if b.visual_records: entity.add_component(b.build_render_component()) entity.name = b.name link_component.name = f"{name_prefix}{b.name}" link_component.joint.name = f"{name_prefix}{b.joint_record.name}" link_component.joint.type = b.joint_record.joint_type link_component.joint.pose_in_child = b.joint_record.pose_in_child link_component.joint.pose_in_parent = b.joint_record.pose_in_parent if link_component.joint.type in [ "revolute", "prismatic", "revolute_unwrapped", ]: link_component.joint.limit = np.array(b.joint_record.limits).flatten() link_component.joint.set_drive_property(0, b.joint_record.damping) if link_component.joint.type == "continuous": link_component.joint.limit = [-np.inf, np.inf] link_component.joint.set_drive_property(0, b.joint_record.damping) links.append(link_component) entities.append(entity) if fix_root_link is not None: entities[0].components[0].joint.type = ( "fixed" if fix_root_link else "undefined" ) entities[0].pose = initial_pose return entities
[docs] def build( self, name=None, fix_root_link=None, build_mimic_joints=True ) -> Articulation: assert self.scene is not None if name is not None: self.set_name(name) assert ( self.name is not None and self.name != "" and self.name not in self.scene.articulations ), "built actors in ManiSkill must have unique names and cannot be None or empty strings" if self.scene_idxs is not None: pass else: self.scene_idxs = torch.arange((self.scene.num_envs), dtype=int) num_arts = len(self.scene_idxs) if self.initial_pose is None: logger.warn( f"No initial pose set for articulation builder of {self.name}, setting to default pose q=[1,0,0,0], p=[0,0,0]. There may be simulation issues/bugs if this articulation at it's initial pose collides with other objects at their initial poses." ) self.initial_pose = sapien.Pose() self.initial_pose = Pose.create(self.initial_pose) initial_pose_b = self.initial_pose.raw_pose.shape[0] assert initial_pose_b == 1 or initial_pose_b == num_arts initial_pose_np = common.to_numpy(self.initial_pose.raw_pose) articulations = [] for i, scene_idx in enumerate(self.scene_idxs): if self.scene.parallel_in_single_scene: sub_scene = self.scene.sub_scenes[0] else: sub_scene = self.scene.sub_scenes[scene_idx] if initial_pose_b == 1: articulation_pose = to_sapien_pose(initial_pose_np) else: articulation_pose = to_sapien_pose(initial_pose_np[i]) links: list[sapien.Entity] = self._build_entities( name_prefix=f"scene-{scene_idx}-{self.name}_", initial_pose=articulation_pose, ) if fix_root_link is not None: links[0].components[0].joint.type = ( "fixed" if fix_root_link else "undefined" ) articulation: physx.PhysxArticulation = links[0].components[0].articulation if build_mimic_joints: for mimic in self.mimic_joint_records: joint = articulation.find_joint_by_name( f"scene-{scene_idx}-{self.name}_{mimic.joint}" ) mimic_joint = articulation.find_joint_by_name( f"scene-{scene_idx}-{self.name}_{mimic.mimic}" ) multiplier = mimic.multiplier offset = mimic.offset # joint mimics parent if joint.parent_link == mimic_joint.child_link: if joint.parent_link.parent is None: logger.warn( f"Skipping adding fixed tendon for {joint.name}" ) # tendon must be attached to grandparent continue root = joint.parent_link.parent parent = joint.parent_link child = joint.child_link articulation.create_fixed_tendon( [root, parent, child], [0, -multiplier, 1], [0, -1 / multiplier, 1], rest_length=offset, stiffness=1e5, ) # 2 children mimic each other if joint.parent_link == mimic_joint.parent_link: assert joint.parent_link is not None root = joint.parent_link articulation.create_fixed_tendon( [root, joint.child_link, mimic_joint.child_link], [0, -multiplier, 1], [0, -1 / multiplier, 1], rest_length=offset, stiffness=1e5, ) articulation.pose = articulation_pose for l in links: sub_scene.add_entity(l) articulation.name = f"scene-{scene_idx}_{self.name}" articulations.append(articulation) articulation: Articulation = Articulation.create_from_physx_articulations( articulations, self.scene, self.scene_idxs ) articulation.initial_pose = self.initial_pose self.scene.articulations[self.name] = articulation self.scene.add_to_state_dict_registry(articulation) return articulation