PyBulet 조사2: 여러 구성 요소로 구성된 객체

시작


이것은 며칠 전에 쓴 PyBulet의 조사 보도의 후속 내용이다.
https://zenn.dev/ymd_h/articles/14397e6ae7ea3d
지난번PyBullet에 대한 기본 정보와 단순한 대상의 구축을 조사해 기재한 결과, 이번에는 여러 개의 구성 요소로 구성된 더욱 복잡한 모델의 구축을 조사했다.(지난번 보도를 읽지 않은 독자는 저쪽에서 한 번 보세요.)
꼼꼼한 내용 없이 기존 URDF 포맷 파일에 저장된 모델을 사용하고, 강화 학습을 빠르게 하려면 페이스북 리서치제pybulletX를 활용하는 것이 좋다.
소식을 소개하다도 썼고 관심 있는 사람도 보세요.
https://zenn.dev/ymd_h/articles/e430f3459b7186

1. 여러 구성 요소 구조 이해


지난번과 마찬가지로 PyBullet의 API를 이용하여 모델을 구축하고자 하였으며, 다음URDF 형식에 설명한 블로그 글은 여러 구성 요소로 구성된 대상의 개념을 이해하기 위해 참고하였다.(개인적으로 XML을 쓰기 싫어서 프로그램으로 대응하고 싶어요.)
https://akifukka.hatenablog.com/entry/pybullet_urdf1
또 URDF 형식공식 자습서 페이지도 이미지를 잡는 데 도움이 된다.(특히 처음 그림은 이해하기 쉬우니 먼저 열어 보시면 좋겠습니다.)
http://wiki.ros.org/urdf/Tutorials/Create your own urdf file
링크(=link)로 불리는 부품은 조인트(=joint)를 통해 결합됩니다.링크 자체는 링크의 좌표계에서 마지막으로 설명한 충돌 판정용 객체 등을 구성하는 독립된 좌표계입니다.
스플라이스를 통해 연결된 링크는 모 피쳐 링크의 원점에서 시작하여 모 피쳐 링크의 원점을 이동, 회전 후에 배치합니다.
조인트는 가동 방법에 따라 다양한 종류가 있는데 하위 링크는 원점을 중심으로 이동한다.
PyBulet의 API에서 지정할 수 있는 것은 JOINT_REVOLUTE, JOINT_PRISMATIC, (축으로 이동), JOINT_FIXED(고정) 및 공식 문서뿐입니다.
Bulet3 자체도 다른 유형의 인터페이스를 지원하는데 설치도 내부에만 전달되는 C++ 함수처럼 보이는데 PyBullet부터 사용할 수 있는지 계속 조사가 필요하다.
또 존재설치 보기,JOINT_REVOLUTE,JOINT_PRISMATIC,JOINT_SPHERICAL,JOINT_PLANAR,JOINT_FIXED,JOINT_POINT2POINT,JOINT_GEAR 등의 상수가 있다.
Bulet3에서는 부모가 없는 주요 링크를'기초(=base)'라고 부르며 특수처리한다.링크 id로 관리하고 지정한 대상에 포함된 링크를 사용하며 기본적인 링크 id는 -1입니다.
공식 문서에 기재된 것, 다음 장에 기재된 구조 시-1가 아니라0....-1는 오류 없이 대상을 표시하지 않습니다...
함수를 통해 구축된 링크 인터페이스의 정보를 얻으면 친링크 id가 pybullet.getJointInfo로 변합니다..수수께끼야.

2. PyBulet의 구축


여러 개의 (하위) 링크를 포함하는 대상을 배치하려면 간단한 대상을 설정하는 것과 같은 -1 함수를 사용하십시오.
이때pybullet.createMultiBody·(질량)·linkMasses(충돌 판정용)·linkCollisionShapeIndices·(외관)·linkVisualShapeIndices(부체인에서 부체인의 원점 위치)·linkPositions(부체인에서 부체인의 방향·회전)·linkOrientationslinkInertialFramePositionslinkInertialFrameOrientationslinkParentIndices・(이음매의 종류)・linkJointTypes(이음매의 축)같은 길이의 목록에서 명확하게 지정해야 한다.
많이 힘들었어요. 지정하지 않으면linkJointAxis오류 발생.(오류 메시지에 무엇이 부족한지 알 수 없고 원본 코드에 오류가 발생한 조건을 확인했습니다.)
obj_id = pybullet.createMultiBody(mass, box_cid, -1,
                                  basePosition=[0, 0, 0.1],
                                  linkMasses=[mass, mass],
                                  linkCollisionShapeIndices=[-1, cylinder_cid],
                                  linkVisualShapeIndices=[-1, -1], 
                                  linkPositions=[[0, 0, 0],[0,0.5,0.5]],
                                  linkOrientations=[[0,0,0,1], [1,0,0,math.cos((3/8)*math.pi)]],
                                  linkInertialFramePositions=[[0,0,0],[0,0,0]],
                                  linkInertialFrameOrientations=[[0,0,0,1],[0,0,0,1]],
                                  linkParentIndices=[0, 1],
                                  linkJointTypes=[pybullet.JOINT_REVOLUTE, pybullet.JOINT_FIXED],
                                  linkJointAxis=[[1,0,0],[1,0,0]],
                                  physicsClientId=engine_id)
"All link arrays need to be same size." 함수를 통해 연축기의 가동 범위를 지정할 수 있다.
실복을 읽다만 해당, pybullet.changeDynamics일 경우 가동 범위가 업데이트됩니다.
pybullet.changeDynamics(obj_id,
                        1, # link id
                        jointLowerLimit=-1,
                        jointUpperLimit=1,
                        physicsClientId=engine_id)
단, PyBullet의 조작을 통해 가동 범위를 제한해도 접속 정보를 업데이트하지 않음 함수 때문에 jointLowerLimit <= jointUpperLimit 함수로 업데이트를 확인할 수 없습니다.

3. 실제로 해보기


박자기처럼 상자 주위를 둥글게 움직일 수 있는 원통 대상을 만들어 봤다.
  • 전반전에 실린더가 중력으로 떨어져 지면에 부딪혔다.
  • 후반부에 비틀림을 가하여 실린더를 들어 중력에 대항한다.

  • 위의 GIF 아날로그 코드 on Google Colab
    import pybullet
    import matplotlib.pyplot as plt
    from PIL import Image
    from IPython import display as dd
    import base64
    import io
    import math
    
    # 物理エンジンの初期化
    engine_id = pybullet.connect(pybullet.DIRECT)
    
    # 重力
    pybullet.setGravity(0,0,-10, physicsClientId=engine_id)
    
    # 質量
    mass = 50.0
    fix_mass = 0
    
    # 床の設置
    plane_cid = pybullet.createCollisionShape(pybullet.GEOM_PLANE, meshScale=[0.05,0.05,0.05], physicsClientId=engine_id)
    plane_id = pybullet.createMultiBody(fix_mass, plane_cid, physicsClientId=engine_id)
    
    # 衝突判定用のオブジェクト(の雛形)
    cylinder_cid = pybullet.createCollisionShape(pybullet.GEOM_CYLINDER, radius=0.1, height=1, physicsClientId=engine_id)
    box_cid = pybullet.createCollisionShape(pybullet.GEOM_BOX, halfExtents=[0.1, 0.1, 0.1], physicsClientId=engine_id)
    
    # 外観オブジェクト(の雛形)
    r_vid = pybullet.createVisualShape(pybullet.GEOM_BOX, halfExtents=[0.1,0.1,0.1], rgbaColor=[1,0,0,1], physicsClientId=engine_id)
    
    # 複数コンポーネントオブジェクトの構築
    obj_id = pybullet.createMultiBody(10*mass, box_cid, r_vid,
                                      basePosition=[0, 0, 0.1],
                                      linkMasses=[fix_mass, mass],
                                      linkCollisionShapeIndices=[-1, cylinder_cid],
                                      linkVisualShapeIndices=[-1, -1], # 外観用オブジェクトを指定しない時は、 -1 を指定。
                                      linkPositions=[[0, 0, 0],[0,0.5,0.5]],
                                      linkOrientations=[[0,0,0,1], [1,0,0,math.cos((3/8)*math.pi)]],
                                      linkInertialFramePositions=[[0,0,0],[0,0,0]],
                                      linkInertialFrameOrientations=[[0,0,0,1],[0,0,0,1]],
                                      linkParentIndices=[0, 1],
                                      linkJointTypes=[pybullet.JOINT_REVOLUTE, pybullet.JOINT_FIXED],
                                      linkJointAxis=[[1,0,0],[1,0,0]],
                                      physicsClientId=engine_id)
    
    # シミュレーションとカメラ画像の保存
    gif = []
    for i in range(2000):
        if i % 10 == 0:
            gif.append(Image.fromarray(pybullet.getCameraImage(512, 384, physicsClientId=engine_id)[2]))
        pybullet.stepSimulation(physicsClientId=engine_id)
    
    for i in range(2000):
        pybullet.setJointMotorControl2(obj_id, 0, pybullet.TORQUE_CONTROL, force=600, physicsClientId=engine_id)
        if i % 10 == 0:
            gif.append(Image.fromarray(pybullet.getCameraImage(512, 384, physicsClientId=engine_id)[2]))
        pybullet.stepSimulation(physicsClientId=engine_id)
    
    # 画像をGIFとして書き出して表示
    f = io.BytesIO()
    gif[0].save(f, save_all=True, append_images=gif[1:],format="gif", loop=0)
    
    f.seek(0)
    b64 = base64.b64encode(f.read()).decode('ascii')
    f.close()
    
    display(dd.HTML(f'<img src="data:image/gif;base64,{b64}" />'))
    

    4. 힘든 곳


    하위 링크의 원점은 getJointInfo에 의해 지정되고 (충돌 판정) 대상은 하위 링크의 원점에 위치하며 (가능) 엇갈려 놓을 수 없습니다.
    따라서 위의 박자기(임시) 실린더처럼 객체의 중심 주위 이외에 이음매가 있는 경우 linkPositions 등을 이용하여 이음매용 링크를 만들고 이 링크와 연관된 형태로 JOINT_REVOLUTE 평행으로 이동하는 대상JOINT_FIXED을 구성해야 한다.
    이때 접두부의 품질0(고정)이든 적당한 품질이든 상관없다.총중량에 영향을 미치기 때문에 0두는 게 좋을 것 같지만 부작용이 없으면 계속 조사해야 한다.
    순조롭지 않을 때 매우 큰 힘(모멘트와 중력)을 가하면 이음매의 제한을 쉽게 구분할 수 있다는 설정 방법이 좋지 않은지, 아니면 힘의 가하는 방식이 좋지 않은지 느낄 수 있다.
    또한 setJointMotorControl2에서 지정한 토크는 1단계stepSimulation 1회)에 유효합니다.

    5. 끝말


    지난번에도 썼는데, PyBullet은 상당히 피곤해요.계속 조사하고 보도해.
    가동 범위의 제한이 조금 나왔지만 changeDynamics 함수, 마찰 등 다양한 지정상능을 조사했다.
    조작에 있어 깊이 파고들어setJointMotorControl2,setJointMotorControlArray 및 대상 상태를 얻는 방법이 필요하다.
    또 화면의 범위, 각도 조정 등을 조사하지 않으면 사용할 수 없다.
    (이번에 시뮬레이션한 각 대상의 크기가 작은 것은 카메라를 움직일 수 없기 때문이며, 더 크면 화면을 초과할 수 있기 때문이다.)

    좋은 웹페이지 즐겨찾기