Skip to main content

用于 SDFormat 文件的 Python 解析器。

项目描述

PySDF (python-sdformat)

PySDF 是一组用于SDFormat XML的 python 绑定。这个想法是提供一种使用 SDF 的方法,感觉与 XML 一样快(或快于),但增加了语法突出显示、自动完成、验证和某种程度的验证的便利性。

当前绑定从任何版本读取 SDF,并将其解析为通用表示。默认情况下不验证或验证修改,从而提供更大的灵活性,但将来会添加可选验证和验证的方法。

安装

pip install python-sdformat

用法

绑定的元素按照官方 SDF 规范进行分组,但有一些例外。这意味着您可以通过遵循规范的嵌套找到您要查找的项目,例如,该 /sensor/imu/angular_velocity/x元素具有相应的类 pysdf.Sensor.Imu.AngularVelocity.X,并且可以 - 在 SDF 内 - 以 parsed_sensor.imu.angular_velocity.x. 前面提到的例外情况是:

  • snake_case用于变量名和CamelCase类名的绑定
  • 如果一个元素可能在其父元素中出现多次,则其对应的属性将是一个元组,其名称将是复数,例如world.models[idx]
  • 元素Pose, Frame,PluginInclude经常出现在多个元素中并提升到顶层,即,您将使用pysdf.Posenot pysdf.Model.Pose

例子

基本阅读和写作

from pysdf import SDF

sample_sdf = """<?xml version="1.0" ?>
<sdf version="1.6">
    <model name="empty_axis">
        <link name="link1" />
        <link name="link2" />
        <joint name="joint" type="fixed">
        <parent>link1</parent>
        <child>link2</child>
        <axis/>
        </joint>
    </model>
</sdf>
"""

# string round-trip
parsed = SDF.from_xml(sample_sdf)
sdf_string = parsed.to_xml()

# file round-trip
parsed.to_file("sample.sdf")
parsed = SDF.from_file("sample.sdf")

# prettify/reformat SDF to have nice indentation
parsed = SDF.from_file("sample.sdf", remove_blank_text=True)
parsed.to_file("sample.sdf", pretty_print=True)

手动构建 SDF

from pysdf import SDF, Link, Joint, Model

reference_sdf = """
<sdf version="1.6">
    <model name="empty_axis">
        <link name="link1" />
        <link name="link2" />
        <joint name="joint" type="fixed">
            <parent>link1</parent>
            <child>link2</child>
        </joint>
    </model>
</sdf>
"""

element = SDF(
    Model(
        Link(name="link1"),
        Link(name="link2"),
        Joint(
            Joint.Parent(text="link1"),
            Joint.Child(text="link2"),
            # attributes are set at the end
            # because python only accepts kwargs at the end.
            name="joint",
            type="fixed",
        ),
        name="empty_axis",
    ),
    version="1.6",
)

element.to_file("sample.sdf", pretty_print=True)

基本修改

from pysdf import Link, State, Model, Link

# Complex elements (with own children) are added on first read
element = Link()
element.to_xml()  # "<link/>"
element.inertial
element.to_xml()  # "<link><inertial/></link>"

# Simple elements (basic types) are added on first write (__set__):
element.inertial.mass
element.to_xml()  # "<link><inertial/></link>"
element.inertial.mass = 5.0
element.to_xml() 
# "<link><inertial><mass>5.0</mass></inertial></link>"

# default values are populated where applicable
assert element.inertial.inertia.ixx == 1.0

# Where possible types are converted automatically
element = State.Model()
element.scale  # (1.0, 1.0, 1.0), tuple
element.scale = "1 2 3"
assert element.scale == (1.0, 2.0, 3.0)
element.scale = [5, 5, 5]
assert element.scale == (5.0, 5.0, 5.0)

# Inserting children works from sequences of kwargs
element = Model()
element.add(Link(name="test"))
element.add(Link(name="test2"), Link(name="test3"))
element.to_xml() 
# '<model><link name="test"/><link name="test2"/><link name="test3"/><pose/></model>'

完整修改示例

from pysdf import SDF, Link

sample_sdf = """<?xml version="1.0" ?>
<sdf version="1.6">
    <model name="empty_axis">
        <link name="link1" />
        <link name="link2" />
        <joint name="joint" type="fixed">
            <parent>link1</parent>
            <child>link2</child>
        </joint>
    </model>
</sdf>
"""

parsed = SDF.from_xml(sample_sdf, remove_blank_text=True)
model = parsed.model

model.name = "modified_model"
model.links[1].add(Link.ParticleEmitter(
    Link.ParticleEmitter.Emitting(text="true"),
    name="my_emitter",
    type="box"
))

parsed.to_file("sample.sdf", pretty_print=True)

迭代和过滤

您可以调用element.iter()递归迭代子树的所有子元素(广度优先)。iter()还接受一个filterkwarg,它允许您仅返回具有从调用者到孩子的所需路径的孩子。过滤器匹配路径的尾部,路径元素由/字符分隔,任何 SDF 标记都是有效的路径元素。这允许对特定子项进行轻松选择和批量编辑,例如,用于filter="pose"选择 SDF 中的filter="model/pose"所有姿势元素或选择作为模型的直接子项的所有姿势元素(模型的姿势)。

长示例 SDF 的应用程序,但我认为展示更真实的东西会很好。

from pysdf import SDF
import numpy as np

# taken from: 
# https://github.com/ignitionrobotics/sdformat/blob/sdf12/test/sdf/joint_nested_parent_child.sdf
large_example = """
<sdf version="1.8">
  <model name="joint_nested_parent_child">
    <model name="M1">
      <link name="L1">
        <pose>0 0 1 0 0 0</pose>
      </link>
      <link name="L2">
        <pose>1 1 0 0 0 0</pose>
      </link>
      <frame name="F1">
        <pose>1 0 0 0 0 0</pose>
      </frame>
      <model name="M2">
        <pose>0 0 1 1.570796326790 0 0</pose>
        <link name="L1"/>
      </model>
    </model>

    <link name="L1">
      <pose>0 0 10 0 1.57079632679 0</pose>
    </link>

    <frame name="F1" attached_to="M1::L1">
      <pose>1 0 0 0 0 0</pose>
    </frame>

    <!-- Joint with a parent link in a nested model -->
    <joint name="J1" type="fixed">
      <pose>1 0 0 0 0 0</pose>
      <parent>M1::L1</parent>
      <child>L1</child>
    </joint>
    <!-- Joint with a sibling parent frame which is attached to a link in a nested model -->
    <joint name="J2" type="fixed">
      <pose>0 1 0 0 0 0</pose>
      <parent>F1</parent>
      <child>L1</child>
    </joint>
    <!-- Joint with a child link in a nested model -->
    <joint name="J3" type="fixed">
      <pose>0 0 1 0 0 0</pose>
      <parent>L1</parent>
      <child>M1::L2</child>
    </joint>

    <!-- Joint with a child frame in a nested model -->
    <joint name="J4" type="fixed">
      <pose>0 0 1 0 0 0</pose>
      <parent>L1</parent>
      <child>M1::F1</child>
    </joint>

    <!-- Joint with a child model in a nested model -->
    <joint name="J5" type="fixed">
      <pose>0 0 1 0 0 0</pose>
      <parent>L1</parent>
      <child>M1::M2</child>
    </joint>

    <!-- Joint with a nested model frame as a parent  -->
    <joint name="J6" type="fixed">
      <pose>0 0 1 0 0 0</pose>
      <parent>M1::__model__</parent>
      <child>L1</child>
    </joint>

    <!-- Joint with a nested model frame as a child  -->
    <joint name="J7" type="fixed">
      <pose>0 0 1 0 0 0</pose>
      <parent>L1</parent>
      <child>M1::__model__</child>
    </joint>
  </model>
</sdf>
"""

# remove_blank_text strips whitespace to allow neat formatting
# later on
element = SDF.from_xml(large_example, remove_blank_text=True)
element.version = "1.9"  # v1.9 supports pose/@degrees

# convert all poses to degrees
for pose in element.iter("pose"):
    pose.degrees = True
    pose_ndarray = np.fromstring(pose.text, count=6, sep=" ")
    rotation_rad = pose_ndarray[3:]
    rotation_deg = rotation_rad / (2*np.pi) * 360
    pose_ndarray[3:] = rotation_deg
    pose.text = " ".join(map(str, pose_ndarray))

# turn on self-collision for all links in the nested model
for link in element.iter("model/model/link"):
    link.self_collide = True

# turn self-collision off (using a different syntax)
for link in element.model.iter("link"):
    link.self_collide = False

# offset all links by some vector
for pose in element.iter("link/pose"):
    pose_ndarray = np.fromstring(pose.text, count=6, sep=" ")
    pose_ndarray[:3] += (0, 0, 1)
    pose.text = " ".join(map(str, pose_ndarray))

element.to_file("sample.sdf", pretty_print=True)

项目详情


下载文件

下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。

源分布

python-sdformat-0.4.0.tar.gz (20.3 kB 查看哈希

已上传 source

内置分布

python_sdformat-0.4.0-py3-none-any.whl (21.2 kB 查看哈希

已上传 py3