PyMJCF 使用教程

简介: PyMJCF 使用教程

系列文章目录

 


前言

重要:如果您在使用 PyMJCF 时发现自己卡住了,请查看本页上的各个重要方框和底部的常见问题部分,看看其中是否有相关内容。

       该库为 MuJoCo 基于 XML 的 MJCF 物理建模语言提供了一个 Python 对象模型。该库的目标是让用户能够在 Python 中轻松地与 MJCF 模型交互并对其进行修改,就像 JavaScript DOM 对 HTML 所做的那样。

       该库的一个主要特点是能够轻松地将多个独立的 MJCF 模型组成一个更大的模型。不同模型或同一模型的多个实例中的重复名称会自动进行消歧处理。

       下面的代码段提供了该库典型用例的一个快速示例。在这里,UpperBody 类可以简单地实例化 Arm 的两个副本,从而减少代码重复。每个 Arm 的身体、关节或 geoms 的名称都自动以其父名称为前缀,因此不会发生名称碰撞。


 

一、基本操作

1.1 创建 MJCF 模型

       在 PyMJCF 中,模型的基本构件是 mjcf.Element。它对应于生成的 XML 中的一个元素。然而,用户代码不能直接实例化一个通用的 mjcf.Element 对象。

       一个有效的模型总是由一个根 <mujoco> 元素组成。这在 PyMJCF 中被表示为特殊的 mjcf.RootElement 类型,它可以在用户代码中被实例化以创建一个空模型。

from dm_control import mjcf
mjcf_model = mjcf.RootElement()
print(mjcf_model)  # MJCF Element: <mujoco/>

1.2 添加新元素

       新元素的属性可以作为 kwargs 传递:

my_box = mjcf_model.worldbody.add('geom', name='my_box',
                                  type='box', pos=[0, .1, 0])
print(my_box)  # MJCF Element: <geom name="my_box" type="box" pos="0. 0.1 0."/>

1.3 解析现有的 XML 文档

       另外,如果已有一个 XML 文件,PyMJCF 也可以对其进行解析,以创建一个 Python 对象:

from dm_control import mjcf
# Parse from path
mjcf_model = mjcf.from_path(filename)
# Parse from file
with open(filename) as f:
  mjcf_model = mjcf.from_file(f)
# Parse from string
with open(filename) as f:
  xml_string = f.read()
mjcf_model = mjcf.from_xml_string(xml_string)
print(type(mjcf_model))  # <type 'mjcf.RootElement'>

1.4 遍历模型

       请看下面的 MJCF 模型:

<mujoco model="test">
  <default>
    <default class="brick">
      <geom rgba="1 0 0 1"/>
    </default>
  </default>
  <worldbody>
    <body name="foo">
      <freejoint/>
      <inertial pos="0 0 0" mass="1"/>
      <body name="bar">
        <joint name="my_hinge" type="hinge"/>
        <geom name="my_geom" pos="0 1 2" class="brick"/>
      </body>
    </body>
  </worldbody>
</mujoco>

       Element 对象的子元素和 XML 属性都作为 Python 属性公开。这些属性的名称都与它们的 XML 对应属性相同,只有一个例外:class XML 属性被命名为 dclass,以避免与 Python class 关键字冲突:

my_geom = mjcf_model.worldbody.body['foo'].body['bar'].geom['my_geom']
print(isinstance(mjcf_model, mjcf.Element)) # True
print(my_geom.name)    # 'my_geom'
print(my_geom.pos)     # np.array([0., 1., 2.], dtype=float)
print(my_geom.class)   # SyntaxError
print(my_geom.dclass)  # 'brick'

       请注意,对象模型中的属性值不受默认值的影响:

print(mjcf_model.default.default['brick'].geom.rgba)  # [1, 0, 0, 1]
print(my_geom.rgba)  # None

1.5 无需遍历即可查找元素

       我们还可以直接查找元素,而无需遍历对象层次结构:

found_geom = mjcf_model.find('geom', 'my_geom')
print(found_geom == my_geom)  # True

       查找给定类型的所有元素

# Note that <freejoint> is also considered a joint
joints = mjcf_model.find_all('joint')
print(len(joints))  # 2
print(joints[0] == mjcf_model.worldbody.body['foo'].freejoint)  # True
print(joints[1] == mjcf_model.worldbody.body['foo'].body['bar'].joint[0])  # True

       请注意,find_all 返回元素的顺序与它们在模型中声明的顺序相同。

1.6 修改 XML 属性

       属性可以修改、添加或删除:

my_geom.pos = [1, 2, 3]
print(my_geom.pos)   # np.array([1., 2., 3.], dtype=float)
my_geom.quat = [0, 1, 0, 0]
print(my_geom.quat)  # np.array([0., 1., 0., 0.], dtype=float)
del my_geom.quat
print(my_geom.quat)   # None

       违反模式会导致错误:

print(my_geom.poss)  # raise AttributeError (no child or attribute called poss)
my_geom.pos = 'invalid'  # raise ValueError (assigning string to array)
my_geom.pos = [1, 2, 3, 4, 5, 6]  # raise ValueError (array length is too long)
# raise ValueError (mass is a required attribute of <inertial>)
del mjcf_model.find('body', 'foo').inertial.mass

1.7 标识符的唯一性

       PyMJCF 强化了模型中 "标识符 "属性的唯一性。标识符由 <default> 的类属性和所有名称属性组成。它们的唯一性只在特定命名空间内强制执行。例如,<body> 可以与 <geom> 具有相同的名称,而 <position> 和 <velocity> 执行器则不能具有相同的名称。

mjcf_model.worldbody.add('geom', name='my_geom')
foo = mjcf_model.worldbody.find('body', 'foo')
foo.add('my_geom')  # Error, duplicated geom name
foo.add('foo')  # OK, a geom can have the same name as a body
mjcf_model.find('geom', 'foo').name = 'my_geom'  # Error, duplicated geom name

1.8 引用属性

       有些属性是对其他元素的引用。例如,执行器的关节属性指向模型中的 <joint> 元素。

mjcf.Element 可以直接分配给这些引用属性:

my_hinge = mjcf_model.find('joint', 'my_hinge')
my_actuator = mjcf_model.actuator.add('velocity', joint=my_hinge)

       这是分配引用属性的推荐方式,因为它能保证在被引用元素重命名时引用不会失效。另外,字符串也可以分配给引用属性。在这种情况下,PyMJCF 不会尝试验证命名的元素是否确实存在于模型中。

重要:如果被引用的元素与引用属性在不同的模型中(例如在附加模型中),则必须通过直接将 mjcf.Element 对象而不是字符串赋值给属性来创建引用。分配给引用属性的字符串不能包含"/",因为 PyMJCF 会在附加时自动对它们进行作用域划分。

二、附加模型

       在本节中,我们将 mjcf.RootElement 简单地称为 "模型"。模型可以附加到其他模型上,以创建合成场景。

arena = mjcf.RootElement()
arena.worldbody.add('geom', name='ground', type='plane', size=[10, 10, 1])
robot = mjcf.from_xml_file('robot.xml')
arena.attach(robot)

我们把 arena 称为父模型,把机器人称为子模型(或附着模型)。

2.1 附加 frames

       当一个模型被附加到一个场地时,会在父模型中创建一个空体。这个空体称为附着 frame。

       附着 frame 是作为包含附着点的主体的子体创建的,其位置和方向与站点相同。生成 XML 时,附着 frame 的内容会与所附模型的 <worldbody> 内容一致。在生成的 XML 中,附着 frame 的名称是子代的完全/限定/前缀/。尾部的斜线确保附着 frame 的名称不会与用户定义的主体相冲突。

       更具体地说,如果我们有以下父模型和子模型:

<mujoco model="parent">
  <worldbody>
    <body>
      <geom name="foo" type="box" pos="-0.2 0 0.3" size="0.5 0.3 0.1"/>
      <site name="attachment_site" pos="1. 2. 3." quat="1. 0. 0. 1."/>
    </body>
  </worldbody>
</mujoco>
<mujoco model="child">
  <worldbody>
    <geom name="bar" type="box" pos="0.5 0.25 1." size="0.1 0.2 0.3"/>
  </worldbody>
</mujoco>

       那么最终生成的 XML 将是

<!-- PyMJCF-generated XML, contains implementation details -->
<mujoco model="parent">
  <worldbody>
    <body>
      <geom name="foo" type="box" pos="-0.2 0 0.3" size="0.5 0.3 0.1"/>
      <site name="attachment_site" pos="1. 2. 3." quat="1. 0. 0. 1."/>
      <body name="child/" pos="1. 2. 3." quat="1. 0. 0. 1.">
        <geom name="child/my_box" type="box" pos="0.5 0.25 1." size="0.1 0.2 0.3"/>
      </body>
    </body>
  </worldbody>
</mujoco>

       重要:附着 frame 是以对用户透明的方式创建的。特别是,PyMJCF 不会将其作为常规正文处理。生成的 XML 中的名称应视为实现细节,不应依赖。

       尽管如此,有时还是有必要访问附着 frame ,例如在父模型和子模型之间添加一个连接点。最简单的方法是持有对 attach 调用所返回对象的引用:

attachment_frame = parent_model.attach('child')
attachment_frame.add('freejoint')

       另外,如果模型已经被附加,则可以使用附着框架命名空间中的 find 函数来检索附着 frame 。mjcf.traversal_utils 中的 get_attachment_frame 方便函数可以查找子模型的附着 frame ,而无需访问父模型。

frame_1 = parent_model.find('attachment_frame', 'child')
# Convenience function: get the attachment frame directly from a child model
frame_2 = mjcf.traversal_utils.get_attachment_frame(child_model)
print(frame_1 == frame_2)  # True

重要: 为鼓励良好的建模实践,附着 frame 的直接子元素只能是 <joint> 和 <inertial>。其他类型的元素应添加到附加模型的 <worldbody> 中。

2.2 元素所有权

重要:当遍历父模型时,子模型的元素不会出现。

2.3 默认类

       PyMJCF 确保父模型的默认类永远不会影响它的任何子模型。这最大限度地减少了两个模型发生微妙的 "不兼容 "的可能性,因为无论模型附属于哪个模型,其行为方式总是相同的。

       PyMJCF 在实践中实现这一点的方法是将模型的全局 <default> 上下文中的所有内容移到一个名为 / 的默认类中。换句话说,PyMJCF 生成的模型永远不会在全局默认上下文中出现任何内容。相反,生成的模型总是看起来像这样:

<!-- PyMJCF-generated XML, contains implementation details -->
<mujoco>
  <default>
    <default class="/">
      <!-- "global defaults" go here -->
      <geom rgba="1. 0. 0. 1."/>
    </default>
  </default>
</mujoco>

       重要:这种转换对用户是透明的。在 Python 中,上述 geom rgba 设置被当作全局默认值访问,即 mjcf_model.default.geom.rgba。一般来说,用户不必担心 PyMJCF 对默认值的内部处理。

       当模型被附加时,它的 / 默认类会变成 fully/qualified/prefix/。尾部的斜线确保了这种转换不会与用户命名的默认类发生冲突。更具体地说,如果我们有以下父模型和子模型:

<mujoco model="parent">
  <default>
    <geom rgba="1. 0. 0. 1."/>
    <default class="green">
      <geom rgba="0. 1. 0. 1."/>
    </default>
  </default>
</mujoco>
<mujoco model="child">
  <default>
    <joint range="0. 1."/>
    <default class="stiff">
      <joint stiffness="0.1"/>
    </default>
  </default>
</mujoco>

       那么最终生成的 XML 将是

<!-- PyMJCF-generated XML, contains implementation details -->
<mujoco model="parent">
  <default>
    <default class="/">
      <geom rgba="1. 0. 0. 1."/>
      <default class="green">
        <geom rgba="0. 1. 0. 1."/>
      </default>
    </default>
    <default class="child/">
      <joint range="0. 1."/>
      <default class="child/stiff">
        <joint stiffness="0.1"/>
      </default>
    </default>
  </default>
</mujoco>

2.4 全局选项

       如果任何全局选项不同,则无法将一个模型附加到另一个模型。全局选项由 <compiler>, <option>, <size> 和 <visual> 属性组成。与处理默认类一样,这是为了确保两个模型不会出现微妙的 "不兼容"。例如

model_1 = mjcf.RootElement()
model_1.compiler.angle = 'radian'
model_2 = mjcf.RootElement()
model_2.compiler.angle = 'degree'
model_1.attach(model_2)  # Error!

       只有当两个模型明确赋予一个选项不同的值时,该选项才会被认为是冲突的。下面举例说明冲突选项可能产生的问题:

model_1 = mjcf.RootElement()
model_2 = mjcf.RootElement()
model_2.compiler.angle = 'degree'
model_1.attach(model_2)  # No error, but all angles in model_1 are now wrong!

在这里,model_1 假设 MuJoCo 的默认角度单位为弧度。由于没有显式地给 compiler.angle 赋值,PyMJCF 没有检测到与 model_2 中的 angle=degree 冲突。现在,model_1 中的所有角度都被错误地解释为度。

2.5 <worldbody> 以外的元素

       当连接模型时,所有非世界体元素的子元素(如致动器或肌腱)都会自动合并到适当的位置。已命名元素的前缀如前所述。

三、常见问题 {#common-gotchas}

3.1 使用 foo.dclass,而不是 foo.class

       class XML 属性与 PyMJCF 中的 dclass Python 属性相对应。这是因为 class 在 Python 中是一个保留关键字。不过,在 getattr 中使用 "class "也是可以的。

<geom name="my_geom" class="red"/>
print(my_geom.class)   # SyntaxError
print(my_geom.dclass)  # 'red'
print(getattr(my_geom, 'class'))  # 'red'

3.2 foo.type 和 foo.range 没问题

       type 和 range 属性在 Cider 中会触发语法高亮,但它们在 Python 中不是保留字。

my_geom.type = 'capsule'  # OK!
my_joint.range = [-1, 1]  # OK!

3.3 一个模型只能附加一次

一个模型不能附加两次。如果在模拟中需要同一模型的多个副本,可以制作深拷贝。最好是定义一个类来构造模型,然后根据需要多次调用构造函数。

目录
相关文章
|
JavaScript
NATAPP使用教程(内网穿透)
NATAPP使用教程(内网穿透)
785 0
|
域名解析 安全 应用服务中间件
手把手教你安装WordPress详细教程(图文)
如果还有不了解宝塔面板怎么使用的小伙伴,可以看下我总结的系列教程,保证从新手变老鸟:
1302 0
手把手教你安装WordPress详细教程(图文)
|
1月前
|
存储 Rust 算法
TinyMPC 使用教程(二)
TinyMPC 使用教程(二)
34 3
|
1月前
|
存储 机器人 测试技术
TinyMPC 使用教程(一)
TinyMPC 使用教程(一)
51 2
|
1月前
Crocoddyl 使用教程(二)
Crocoddyl 使用教程(二)
25 1
|
4月前
|
Linux 数据安全/隐私保护 Windows
[使用教程]xftp5中文版怎么使用?
[使用教程]xftp5中文版怎么使用?
|
12月前
|
域名解析 弹性计算 Linux
阿里云服务器怎么使用教程?图文操作教程
阿里云服务器怎么使用教程?图文操作教程,使用阿里云服务器快速搭建网站教程,先为云服务器安装宝塔面板,然后在宝塔面板上新建站点,阿里云服务器网以搭建WordPress网站博客为例,来详细说下从阿里云服务器CPU内存配置选择、Web环境、域名解析到网站上线全流程
763 0
|
监控 数据库
CANape的使用教程
CANape的使用教程
CANape的使用教程
|
搜索推荐
StartAllBack使用教程
StartAllBack, Win11开始菜单增强工具,为Windows11恢复经典样式的Windows7主题风格开始菜单和任务栏,功能包括:自定义开始菜单样式和操作,个性化任务栏及资源管理器等
StartAllBack使用教程
|
iOS开发 MacOS
XMind 2022 使用教程
XMind 2022的安装 XMind 2022的使用 XMind 最新版 思维导图的安装使用 思维导图 XMind 2022的下载 XMind 2022 XMind 2022 Win/Mac 强大的思维导图软件
XMind 2022 使用教程