#!/usr/bin/env python # Copyright (c) CTU - All Rights Reserved # Created on: 5/4/20 # Author: Vladimir Petrik <*****@*****.**> from pyphysx import * from pyphysx_utils.rate import Rate from pyphysx_render.pyrender import PyPhysxViewer scene = Scene() scene.add_actor( RigidStatic.create_plane(material=Material( static_friction=0.1, dynamic_friction=0.1, restitution=0.5))) actor = RigidDynamic() actor.attach_shape(Shape.create_box([0.2] * 3, Material(restitution=1.))) actor.set_global_pose([0.5, 0.5, 1.0]) actor.set_mass(1.) scene.add_actor(actor) render = PyPhysxViewer(video_filename='videos/01_free_fall.gif') render.add_physx_scene(scene) rate = Rate(240) while render.is_active: scene.simulate(rate.period()) render.update() rate.sleep()
class GripperCylinderEnv(Env): FINGER_OPEN_POS = 0.075 def __init__(self, horizon=100, render=False, realtime=False, reward_params=None, reset_state_sampler=None, state_trajectories: List[StateTrajectory] = None, video_filename=None, reset_on_singularity=True, reset_on_plane_hit=True, action_start_time=0., action_end_time=0., two_objects=False, dz=0.2, open_gripper_on_leave=True, close_gripper_on_leave=False, use_max_reward=False, add_obstacle=False) -> None: super().__init__() self.use_max_reward = use_max_reward self.close_gripper_on_leave = close_gripper_on_leave self.open_gripper_on_leave = open_gripper_on_leave assert not (self.close_gripper_on_leave and self.open_gripper_on_leave) self.action_end_time = action_end_time self.action_start_time = action_start_time self.two_objects = two_objects self.reset_on_singularity = reset_on_singularity self.reset_on_plane_hit = reset_on_plane_hit self.realtime = realtime self.reset_state_sampler = reset_state_sampler self.reward_params = reward_params or GripperCylinderEnv.get_default_reward_params( ) self.state_trajectories = state_trajectories or [] self.render = render self._horizon = horizon self.iter = 0 self.num_sub_steps = 10 self.control_frequency = Rate(12) """ Define action and observation space. """ self._action_space = FloatBox(low=-1., high=1., shape=7) self._observation_space = self.get_obs(get_space=True) """ Create scene. """ self.scene = Scene(scene_flags=[ SceneFlag.ENABLE_FRICTION_EVERY_ITERATION, SceneFlag.ENABLE_STABILIZATION ]) self.plane_mat = Material(static_friction=0, dynamic_friction=0) self.obj_mat = Material(static_friction=1., dynamic_friction=1., restitution=0) self.plane_actor = RigidStatic.create_plane(material=self.plane_mat) self.scene.add_actor(self.plane_actor) self.hand_actors = self.create_hand_actors(use_mesh_fingers=False, use_mesh_base=False) self.joints = self.create_hand_joints() self.obj = self.create_obj() self.obj_height = 0.1 self.obj_radius = 0.03 if self.two_objects: self.obj2 = self.create_obj() self.obj2_height = 0.1 self.obj2_radius = 0.03 if add_obstacle: actor = RigidStatic() actor.attach_shape( Shape.create_box([0.02, 0.02, 0.2], self.obj_mat)) actor.set_global_pose([0.075, 0.015, 0.1]) self.scene.add_actor(actor) if self.render: from pyphysx_render.pyrender import PyPhysxViewer, RoboticTrackball import pyrender render_scene = pyrender.Scene() render_scene.bg_color = np.array([0.75] * 3) cam = pyrender.PerspectiveCamera(yfov=np.deg2rad(60), aspectRatio=1.414, znear=0.005) cam_pose = np.eye(4) cam_pose[:3, 3] = RoboticTrackball.spherical_to_cartesian( 0.75, np.deg2rad(-90), np.deg2rad(60)) cam_pose[:3, :3] = RoboticTrackball.look_at_rotation( eye=cam_pose[:3, 3], target=np.zeros(3), up=[0, 0, 1]) nc = pyrender.Node(camera=cam, matrix=cam_pose) render_scene.add_node(nc) render_scene.main_camera_node = nc self.renderer = PyPhysxViewer(video_filename=video_filename, render_scene=render_scene, viewer_flags={ 'axes_scale': 0.2, 'plane_grid_spacing': 0.1, }) self.renderer.add_physx_scene(self.scene) # from pyphysx_render.renderer import PyPhysXParallelRenderer # self.renderer = PyPhysXParallelRenderer(render_window_kwargs=dict( # video_filename=video_filename, coordinates_scale=0.2, coordinate_lw=1., # cam_pos_distance=0.75, cam_pos_elevation=np.deg2rad(60), cam_pos_azimuth=np.deg2rad(-90.), # )) """ Compute trajectory caches. Stores quantities in MxN arrays [# of time steps, # of trajectories] """ timesteps = np.arange( 0., self.control_frequency.period() * (1 + self.horizon), self.control_frequency.period()) self.demo_opos = np.zeros( (len(timesteps), len(self.state_trajectories), 3)) self.demo_hpos = np.zeros( (len(timesteps), len(self.state_trajectories), 3)) self.demo_grip = np.zeros( (len(timesteps), len(self.state_trajectories))) self.demo_hrot = np.zeros( (len(timesteps), len(self.state_trajectories), 2)) if self.two_objects: self.demo_opos2 = np.zeros( (len(timesteps), len(self.state_trajectories), 3)) for i, st in enumerate(self.state_trajectories): half_height = 0.5 * st.get_property_at_time('o0szz', timesteps) self.demo_opos[:, i, 0] = st.get_property_at_time('o0posx', timesteps) self.demo_opos[:, i, 1] = st.get_property_at_time('o0posy', timesteps) self.demo_opos[:, i, 2] = np.maximum( st.get_property_at_time('o0posz', timesteps) - half_height, 0.) if self.two_objects: half_height2 = 0.5 * st.get_property_at_time( 'o1szz', timesteps) self.demo_opos2[:, i, 0] = st.get_property_at_time( 'o1posx', timesteps) self.demo_opos2[:, i, 1] = st.get_property_at_time( 'o1posy', timesteps) self.demo_opos2[:, i, 2] = np.maximum( st.get_property_at_time('o1posz', timesteps) - half_height2, 0.) self.demo_hpos[:, i, 0] = st.get_property_at_time('hposx', timesteps) self.demo_hpos[:, i, 1] = st.get_property_at_time('hposy', timesteps) self.demo_hpos[:, i, 2] = np.maximum( st.get_property_at_time('hposz', timesteps) - half_height, 0.) self.demo_grip[:, i] = (1 - st.get_property_at_time( 'touch', timesteps)) * self.FINGER_OPEN_POS self.demo_hrot[:, i, 0] = st.get_property_at_time('hazi', timesteps) self.demo_hrot[:, i, 1] = st.get_property_at_time('hele', timesteps) self.demo_hpos[int(action_end_time * 12):, :, :] = self.demo_opos[int(action_end_time * 12):, :, :].copy() if self.two_objects: self.demo_hpos[int(action_end_time * 12):, :, :] = self.demo_opos2[ int(action_end_time * 12):, :, :].copy() self.demo_hpos[int(action_end_time * 12):, :, 2] += dz self.demo_grip_weak_closed = np.zeros( (len(timesteps), len(self.state_trajectories)), dtype=np.bool) self.demo_grip_weak_closed[int(action_start_time * 12):int(action_end_time * 12 )] = True # those inside action self.demo_grip_weak_closed &= self.demo_grip < self.FINGER_OPEN_POS / 2 # that are also closed # self.demo_grip_strong_open = np.ones((len(timesteps), len(self.state_trajectories)), dtype=np.bool) # self.demo_grip_strong_open[int(action_start_time * 12):int(action_end_time * 12)] = False tmp_rot_vec = np.zeros( (len(timesteps), len(self.state_trajectories), 3)) for i, st in enumerate(self.state_trajectories): for j in range(3): tmp_rot_vec[:, i, j] = st.get_property_at_time( 'o0rot{}'.format(j), timesteps) self.demo_orot = npq.from_rotation_vector(tmp_rot_vec) if self.two_objects: tmp_rot_vec = np.zeros( (len(timesteps), len(self.state_trajectories), 3)) for i, st in enumerate(self.state_trajectories): for j in range(3): tmp_rot_vec[:, i, j] = st.get_property_at_time( 'o1rot{}'.format(j), timesteps) self.demo_orot2 = npq.from_rotation_vector(tmp_rot_vec) def get_obs(self, get_space=False): """ Get observation of state for RL. If get_space is true return space description. Space is defined as: 0: time [1] 1:7 hand pos + rotation vector [6] 7: gripper state [1] 8:14 obj pos + rotation vector [6], 14: obj height 15: obj radius In case of two objects: 16:22 obj pos + rotation vector [6], 22: obj height 23: obj radius """ if get_space: if self.two_objects: return FloatBox(low=[0.] + [-1.] * 6 + [0.] + [-1.] * 6 + [0.] * 2 + [-1.] * 6 + [0.] * 2, high=[1.] + [1.] * 6 + [self.FINGER_OPEN_POS] + [1.] * 6 + [0.2] * 2 + [1.] * 6 + [0.2] * 2) return FloatBox(low=[0.] + [-1.] * 6 + [0.] + [-1.] * 6 + [0.] * 2, high=[1.] + [1.] * 6 + [self.FINGER_OPEN_POS] + [1.] * 6 + [0.2] * 2) t = self.scene.simulation_time hand_pose = self.hand_actors[0].get_global_pose() grip = self.get_grip() obj_pose = self.obj.get_global_pose() if self.two_objects: obj2_pose = self.obj2.get_global_pose() return np.concatenate([ [t / 10.], hand_pose[0], npq.as_rotation_vector(hand_pose[1]), [grip], obj_pose[0], npq.as_rotation_vector(obj_pose[1]), [self.obj_height, self.obj_radius], obj2_pose[0], npq.as_rotation_vector(obj2_pose[1]), [self.obj2_height, self.obj2_radius], ]).astype(np.float32) return np.concatenate([ [t / 10.], hand_pose[0], npq.as_rotation_vector(hand_pose[1]), [grip], obj_pose[0], npq.as_rotation_vector(obj_pose[1]), [self.obj_height, self.obj_radius], ]).astype(np.float32) def step(self, action): self.iter += 1 """ Step in pyphysx. """ dt = self.control_frequency.period() / self.num_sub_steps dpose = (0.3 * np.tanh(action[:3]) * dt, quat_from_euler('xyz', np.pi * np.tanh(action[3:6]) * dt)) if self.open_gripper_on_leave and self.scene.simulation_time > self.action_end_time: self.set_grip(self.FINGER_OPEN_POS) elif self.close_gripper_on_leave and self.scene.simulation_time > self.action_end_time: self.set_grip(0.) else: grip = np.tanh(action[ 6]) * self.FINGER_OPEN_POS / 2 + self.FINGER_OPEN_POS / 2 # grip = 0.04 if grip < 0.05 else self.FINGER_OPEN_POS self.set_grip(grip) pose = self.hand_actors[0].get_global_pose() for _ in range(self.num_sub_steps): pose = multiply_transformations(dpose, pose) self.hand_actors[0].set_kinematic_target(pose) self.scene.simulate(dt) if self.render: if self.iter == 1: self.renderer.clear_physx_scenes() self.renderer.add_physx_scene(self.scene) self.renderer.update() # self.renderer.render_scene(self.scene, recompute_actors=self.iter == 1) if self.realtime: self.control_frequency.sleep() """ Compute rewards """ hp, hq = self.hand_actors[0].get_global_pose() op, oq = self.obj.get_global_pose() grip = self.get_grip() hrot = azimuth_elevation_from_quat(hq) hp[2] -= 0.5 * self.obj_height op[2] -= 0.5 * self.obj_height traj_rewards = np.zeros(self.demo_hpos.shape[1]) b, s = self.reward_params['demo_hand_pos']['b'], self.reward_params[ 'demo_hand_pos']['scale'] traj_rewards += s * np.exp(-0.5 * np.sum( (self.demo_hpos[self.iter] - hp)**2, axis=-1) * b) b, s = self.reward_params['demo_obj_pos']['b'], self.reward_params[ 'demo_obj_pos']['scale'] traj_rewards += s * np.exp(-0.5 * np.sum( (self.demo_opos[self.iter] - op)**2, axis=-1) * b) b, s = self.reward_params['demo_hand_azi_ele'][ 'b'], self.reward_params['demo_hand_azi_ele']['scale'] traj_rewards += s * np.exp(-0.5 * np.sum( (self.demo_hrot[self.iter] - hrot)**2, axis=-1) * b) b, s = self.reward_params['demo_obj_rot']['b'], self.reward_params[ 'demo_obj_rot']['scale'] traj_rewards += s * np.exp(-0.5 * npq.rotation_intrinsic_distance( self.demo_orot[self.iter], oq) * b) b, s = self.reward_params['demo_touch']['b'], self.reward_params[ 'demo_touch']['scale'] traj_rewards += s * self.demo_grip_weak_closed[self.iter] * np.exp( -0.5 * b * (grip - (self.obj_radius - 0.005))**2) if self.two_objects: op2, oq2 = self.obj2.get_global_pose() op2[2] -= 0.5 * self.obj2_height b, s = self.reward_params['demo_obj_pos']['b'], self.reward_params[ 'demo_obj_pos']['scale'] traj_rewards += s * np.exp(-0.5 * np.sum( (self.demo_opos2[self.iter] - op2)**2, axis=-1) * b) b, s = self.reward_params['demo_obj_rot']['b'], self.reward_params[ 'demo_obj_rot']['scale'] traj_rewards += s * np.exp(-0.5 * npq.rotation_intrinsic_distance( self.demo_orot2[self.iter], oq2) * b) b, s = self.reward_params['demo_touch']['b'], self.reward_params[ 'demo_touch']['scale'] traj_rewards += s * self.demo_grip_weak_closed[self.iter] * np.exp( -0.5 * b * (grip - (self.obj2_radius - 0.005))**2) if traj_rewards.size == 0: reward = 0. else: reward = np.max(traj_rewards) if self.use_max_reward else np.mean( traj_rewards) """ Resolve singularities and collisions that we don't want. """ if self.hand_plane_hit(): if self.reset_on_plane_hit: return EnvStep(self.get_obs(), 0., True, EnvInfo()) reward = 0. if np.abs(hrot[1]) > np.deg2rad( 85): # do not allow motion close to singularity if self.reset_on_singularity: return EnvStep(self.get_obs(), 0., True, EnvInfo()) reward = 0. return EnvStep(self.get_obs(), reward / self.horizon, self.iter == self.horizon, EnvInfo()) def reset(self): self.iter = 0 if self.reset_state_sampler is None: self.reset_hand_pose( pose=((0, 0, 0.05), quat_from_euler('yz', [-np.pi / 2, -np.pi / 2]))) self.obj.set_global_pose([0.0, 0.0, 0.05]) else: while True: if self.two_objects: hand_pose, grip, obj_pose, self.obj_height, self.obj_radius, obj2_pose, self.obj2_height, self.obj2_radius = self.reset_state_sampler( self) for s in self.obj2.get_atached_shapes(): self.obj2.detach_shape(s) self.obj2.attach_shape( self.create_cylinder(self.obj2_height, self.obj2_radius)) self.obj2.set_global_pose(obj2_pose) else: hand_pose, grip, obj_pose, self.obj_height, self.obj_radius = self.reset_state_sampler( self) [ self.obj.detach_shape(s) for s in self.obj.get_atached_shapes() ] self.obj.attach_shape( self.create_cylinder(self.obj_height, self.obj_radius)) self.reset_hand_pose(hand_pose, grip) self.obj.set_global_pose(obj_pose) if not self.hand_plane_hit() and not self.hand_obj_hit(): break # else: # print('reset collision detected: hand/plane: {}, hand/obj: {}'.format(self.hand_plane_hit(), # self.hand_obj_hit())) # print('Sample:', hand_pose, grip, obj_pose, self.obj_height, self.obj_radius) self.plane_mat.set_static_friction(np.random.uniform(0., 0.05)) self.plane_mat.set_dynamic_friction(np.random.uniform(0., 0.05)) self.scene.simulation_time = 0. return self.get_obs() def hand_plane_hit(self): for a in self.hand_actors: if a.overlaps(self.plane_actor): return True return False def hand_obj_hit(self): for a in self.hand_actors: if a.overlaps(self.obj): return True return False def obj2_plane_hit(self): if not self.two_objects: return False return self.obj2.overlaps(self.plane_actor) @staticmethod def randomized_reset_state_sampler(hazi_min=-np.deg2rad(360), hazi_max=np.deg2rad(360), hele_min=np.deg2rad(-80), hele_max=np.deg2rad(80), ohei_min=0.035, ohei_max=0.105, orad_min=0.025, orad_max=0.055, shared_sampler_dict=None): def sample(env: GripperCylinderEnv): hpos_std = shared_sampler_dict['hpos_std'] sa = shared_sampler_dict['angle_bound_scale'] q = quat_from_azimuth_elevation( np.random.uniform(hazi_min * sa, hazi_max * sa) + np.pi / 2, np.random.uniform(hele_min * sa, hele_max * sa)) ohei = np.random.uniform(ohei_min, ohei_max) orad = np.random.uniform(orad_min, orad_max) tip_pos = np.zeros(3) tip_pos[:2] = np.random.normal(0., hpos_std, size=2) tip_pos[2] = ohei / 2. + np.random.normal(0., hpos_std, size=1) obj_pos = np.array([0., 0., ohei / 2.]) obj_pos[:2] += np.random.normal(0., 1e-3, size=2) if not env.two_objects: return (tip_pos, q), GripperCylinderEnv.FINGER_OPEN_POS, ( obj_pos, npq.one), ohei, orad tind = np.random.randint(0, env.demo_opos2.shape[1]) tip_pos += env.demo_opos2[0, tind, :] o2pose = multiply_transformations( (tip_pos, q), (np.zeros(3), quat_from_euler('y', np.pi / 2))) o2h = np.random.uniform(ohei_min, ohei_max) o2rad = np.random.uniform(orad_min, orad_max) return (tip_pos, q), o2rad, (obj_pos, npq.one), ohei, orad, o2pose, o2h, o2rad return sample @staticmethod def get_default_reward_params(fixed_obj_pose_scale=0., hand_obj_pos_dist_scale=0., demo_hand_pos=0., demo_obj_pos=0., demo_hand_azi_ele=0., demo_touch=0., demo_obj_rot=0.): return { 'fixed_obj_pose': { 'b': 100., 'scale': fixed_obj_pose_scale, }, 'hand_obj_pos_dist': { 'b': 100., 'scale': hand_obj_pos_dist_scale, }, 'demo_hand_pos': { 'b': 100., 'scale': demo_hand_pos, }, 'demo_obj_pos': { 'b': 100., 'scale': demo_obj_pos, }, 'demo_obj_rot': { 'b': 10., 'scale': demo_obj_rot, }, 'demo_hand_azi_ele': { 'b': 10., 'scale': demo_hand_azi_ele, }, 'demo_touch': { 'b': 10000., 'scale': demo_touch, }, } @property def horizon(self): return self._horizon @staticmethod def df_from_observations(observations): assert len(observations.shape) == 2 assert observations.shape[1] == 16 or observations.shape[1] == 24 t = observations[:, 0] hpos, hrot = observations[:, 1:4], observations[:, 4:7] grip = observations[:, 7] opos, orot = observations[:, 8:11], observations[:, 11:14] ohei, orad = observations[:, 14], observations[:, 15] helaz = npq.as_spherical_coords( npq.from_rotation_vector(hrot)) - (np.pi / 2, 0.) df = pd.DataFrame() df.insert(0, 'hposx', hpos[:, 0]) df.insert(0, 'hposy', hpos[:, 1]) df.insert(0, 'hposz', hpos[:, 2]) df.insert(0, 'hazi', helaz[:, 1]) df.insert(0, 'hele', helaz[:, 0]) df.insert(0, 'touch', grip) df.insert(0, 'o0posx', opos[:, 0]) df.insert(0, 'o0posy', opos[:, 1]) df.insert(0, 'o0posz', opos[:, 2]) df.insert(0, 'o0rot0', orot[:, 0]) df.insert(0, 'o0rot1', orot[:, 1]) df.insert(0, 'o0rot2', orot[:, 2]) df.insert(0, 'ohei', ohei) df.insert(0, 'orad', orad) if observations.shape[1] > 16: o1pos, o1rot = observations[:, 16:19], observations[:, 19:22] o1hei, o1rad = observations[:, 22], observations[:, 23] df.insert(0, 'o1posx', o1pos[:, 0]) df.insert(0, 'o1posy', o1pos[:, 1]) df.insert(0, 'o1posz', o1pos[:, 2]) df.insert(0, 'o1rot0', o1rot[:, 0]) df.insert(0, 'o1rot1', o1rot[:, 1]) df.insert(0, 'o1rot2', o1rot[:, 2]) df.insert(0, 'o1hei', o1hei) df.insert(0, 'o1rad', o1rad) return df def create_cylinder(self, height=0.1, radius=0.03): cylinder = trimesh.primitives.Cylinder(height=height, radius=radius, sections=32) shape = Shape.create_convex_mesh_from_points(points=cylinder.vertices, material=self.obj_mat) return shape def create_obj(self): actor = RigidDynamic() actor.attach_shape(self.create_cylinder()) actor.set_mass(1.) actor.set_global_pose([0, 0, 0.05]) actor.set_angular_damping(0.05) actor.set_linear_damping(0.05) actor.set_max_linear_velocity(10.) actor.set_max_angular_velocity(2 * np.pi) self.scene.add_actor(actor) return actor def reset_hand_pose(self, pose, grip=None): if grip is None: grip = GripperCylinderEnv.FINGER_OPEN_POS self.hand_actors[0].set_global_pose(pose) self.hand_actors[1].set_global_pose( multiply_transformations(pose, [0, grip, 0.0584])) self.hand_actors[2].set_global_pose( multiply_transformations(pose, [0, -grip, 0.0584])) self.set_grip(grip) def get_grip(self): return self.joints[0].get_relative_transform()[0][1] # return self.joints[0].get_drive_position()[0][1] def set_grip(self, v): v = np.clip(v, 0., self.FINGER_OPEN_POS) self.joints[0].set_drive_position([0, v, 0]) self.joints[1].set_drive_position([0, -v, 0]) def create_hand_joints(self): j1 = D6Joint(self.hand_actors[0], self.hand_actors[1], local_pose0=[0, 0, 0.0584]) j2 = D6Joint(self.hand_actors[0], self.hand_actors[2], local_pose0=[0, 0, 0.0584]) for j in [j1, j2]: j.set_motion(D6Axis.Y, D6Motion.LIMITED) j.set_drive(D6Drive.Y, stiffness=1000., damping=200., force_limit=20.) j1.set_linear_limit(D6Axis.Y, 0., GripperCylinderEnv.FINGER_OPEN_POS) j2.set_linear_limit(D6Axis.Y, -GripperCylinderEnv.FINGER_OPEN_POS, 0.) return [j1, j2] def create_hand_actors(self, use_mesh_base=True, use_mesh_fingers=True): panda_dir = os.path.dirname( os.path.abspath(__file__)) + '/franka_panda/meshes/collision' hand_mat = Material(static_friction=1., dynamic_friction=1., restitution=0) trimesh_kwargs = dict(split_object=False, group_material=False) convex_kwargs = dict(material=hand_mat, quantized_count=64, vertex_limit=64) if use_mesh_base: mesh_hand: trimesh.Trimesh = trimesh.load( '{}/hand.obj'.format(panda_dir), **trimesh_kwargs) hand_shape = Shape.create_convex_mesh_from_points( points=mesh_hand.vertices, **convex_kwargs) else: minp, maxp = np.array([-0.0316359, -0.10399, -0.0259248]), np.array( [0.0316158, 0.100426, 0.0659622]) hand_shape = Shape.create_box(size=maxp - minp, material=hand_mat) hand_shape.set_local_pose((minp + maxp) / 2) if use_mesh_fingers: mesh_finger: trimesh.Trimesh = trimesh.load( '{}/finger.obj'.format(panda_dir), **trimesh_kwargs) finger1_shape = Shape.create_convex_mesh_from_points( points=mesh_finger.vertices, **convex_kwargs) finger2_shape = Shape.create_convex_mesh_from_points( points=mesh_finger.vertices, **convex_kwargs) finger2_shape.set_local_pose( (np.zeros(3), quat_from_euler('z', np.pi))) else: finger1_shape = Shape.create_box(size=[0.02, 0.02, 0.12], material=hand_mat) finger2_shape = Shape.create_box(size=[0.02, 0.02, 0.12], material=hand_mat) finger1_shape.set_local_pose([0., 0.01, 0.07]) finger2_shape.set_local_pose([0., -0.01, 0.07]) for s in [hand_shape, finger1_shape, finger2_shape]: s.set_local_pose( multiply_transformations((0, 0, -0.15), s.get_local_pose())) actors = [RigidDynamic() for _ in range(3)] actors[0].attach_shape(hand_shape) actors[1].attach_shape(finger1_shape) actors[2].attach_shape(finger2_shape) actors[0].set_mass(1000 + 0.81) actors[1].set_mass(0.1 + 5.) actors[2].set_mass(0.1 + 5.) actors[0].set_rigid_body_flag(RigidBodyFlag.KINEMATIC, True) for a in actors: a.disable_gravity() a.set_angular_damping(0.5) a.set_linear_damping(0.5) self.scene.add_actor(a) return actors