새소식

ROS & Robotics

MuJoCo-python 환경에서 body 에 외력 주기 (xfrc_applied)

  • -

요새 계속 MuJoCo 관련 프로젝트를 진행하고 있는데, mujoco 의 python wrapper 인 mujoco-py 는 한국어 자료도 거의 없고, 참고할 만한 자료가 거의 공식 도큐먼트 밖에 없는 것 같았습니다. 프로젝트를 진행하면서 mujoco-py 를 사용하는 방법에 대해서 포스팅을 해 보려고 합니다.

 

mujoco 에서는 xfrc_applied 라는 외력을 사용할 수 있는데, 이를 이용하면 body 에 힘과 토크를 적용할 수 있습니다. 

 

https://www.youtube.com/watch?v=N2E-qnsyn1I

 

다음과 같이 body 에 외력을 줄 수 있습니다. 

 

  • Ubuntu 20.04
  • Mujoco-py

 

xfrc_applied 의 [0:3] 요소을, [3:6] 까지의 요소토크를 의미합니다. 다음 코드는 동영상처럼 원을 따라서 외력을 가하는 코드입니다. 

외력 자체를 적용하는 것은 

self.sim.data.xfrc_applied[body_index] = self.force

 

딱 이 한 줄로 할 수 있지만, 외력을 시각화하는 코드가 추가로 포함되어 있습니다. 

Mujoco-py 는 화살표를 지원하지 않아, 실린더에 끝 점에 구 모양을 붙여서 화살표와 같은 기능을 하도록 했습니다. 

 

  • xfrc_applied.py
# 단순한 사각형에 외력을 주고 이를 시각화하는 테스트 코드 import mujoco_py import numpy as np import time import math class Robot: def __init__(self, mjcf_path, body_name): self.model = mujoco_py.load_model_from_path(mjcf_path) self.sim = mujoco_py.MjSim(self.model) self.viewer = mujoco_py.MjViewer(self.sim) self.body_name = body_name self.last_update_time = time.time() self.body_index = self.sim.model.body_name2id(body_name) self.force = np.zeros(6) # 초기 외력 값 self.random_force = np.zeros(6) def rotation_quaternion(self, from_vec, to_vec): v = np.cross(from_vec, to_vec) w = np.sqrt((np.linalg.norm(from_vec) ** 2) * (np.linalg.norm(to_vec) ** 2)) + np.dot(from_vec, to_vec) return np.concatenate(([w], v)) def apply_external_force(self): # body name 가져오기 body_index = self.sim.model.body_name2id(self.body_name) # 외력 가하기 self.sim.data.xfrc_applied[body_index] = self.force force_vector = self.force[:3] # print("force_vector :", force_vector) # for force vector visualization start_point = self.sim.data.body_xpos[body_index] distance = 0.2 end_point = start_point + force_vector / (np.linalg.norm(force_vector) + 1.0E-6) * distance arrow_id = self.sim.model.geom_name2id("force_visualization") arrow_tip_id = self.sim.model.geom_name2id("force_arrow_tip") self.sim.model.geom_pos[arrow_id] = end_point default_direction = np.array([0, 0, 1]) quat = self.rotation_quaternion(default_direction, force_vector / np.linalg.norm(force_vector) + 1.0E-6) print("start point is ",start_point) print("end point is ",end_point) self.sim.model.geom_pos[arrow_id] = start_point self.sim.model.geom_quat[arrow_id] = quat self.sim.model.geom_pos[arrow_tip_id] = end_point def update_force_random(self): self.random_force = np.random.uniform(-0.001, 0.001, 2) # 크기 조정 self.force = np.concatenate([self.random_force, [0], np.zeros(3)]) def update_force_circle(self, time): radius = 0.01 omega = 5 x_force = radius * math.cos(omega * time) y_force = radius * math.sin(omega * time) self.force = np.array([x_force, y_force, 0, 0, 0, 0]) def run_simulation(self): while True: current_time = time.time() # if current_time - self.last_update_time > 0.01: self.update_force_circle(current_time - self.last_update_time) # self.last_update_time = current_time self.apply_external_force() self.sim.step() self.viewer.render() time.sleep(0.01) mjcf_path = "model.xml" body_name = "box" robot = Robot(mjcf_path, body_name) robot.run_simulation()

 

update_force 함수를 고쳐서 원하는 궤적으로 외력이 작용하도록 할 수 있습니다. 

 

  • model.xml
<?xml version="1.0" ?> <mujoco> <worldbody> <geom name="force_visualization" type="cylinder" rgba="1 0 0 1" size="0.01 0.1" quat="0.707 0 0.707 0"/> <geom name="force_arrow_tip" pos="0.5 0.5 1" type="sphere" size="0.05" rgba="1 0 0 1"/> <body name="box" pos="0 0 0.2"> <geom size="0.15 0.15 0.15" rgba="1 1 1 0.7" type="box"/> <joint axis="1 0 0" name="box:x" type="slide"/> <joint axis="0 1 0" name="box:y" type="slide"/> </body> <body name="floor" pos="0 0 0.025"> <geom size="10 10 0.02" rgba="0 1 0 1" type="box"/> </body> </worldbody> </mujoco>

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.