mirror of
https://github.com/wassname/pyrobolearn.git
synced 2026-09-11 12:31:07 +08:00
refactor + update simulators and physics randomizers
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
This repository contains the code for the *PyRoboLearn* (PRL) framework: a Python framework for Robot Learning.
|
||||
This framework revolves mainly around 7 axes: simulators, worlds, robots, interfaces, learning tasks (= environment and policy), learning models, and learning algorithms.
|
||||
|
||||
This development of this framework is ongoing.
|
||||
|
||||
## Requirements
|
||||
|
||||
The framework has been tested with Python 2.7 and Ubuntu 16.04 and 18.04. We also tested parts of it with Python 3.5 on Ubuntu 16.04 and so far so good, but there might be some errors that escaped me.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
## Physics randomizer
|
||||
|
||||
This folder provides physics randomizers which randomizes the dynamical attributes / properties of an object.
|
||||
For instance, it can randomize the mass or inertial matrix of a link, the bounciness of an object, the gravity of
|
||||
the world, the contact friction coefficients of the floor and various links, and so on.
|
||||
|
||||
Note that physics randomizer instances have access to the simulator in order to modify the physical properties.
|
||||
Also, note that normally the physics randomizer is called at the beginning of an episode, and not at each time
|
||||
step. Changing the physical properties at each time step can lead to weird behaviors in the simulator.
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
# import physics
|
||||
from .physics_randomizer import *
|
||||
|
||||
# import world physics randomizer
|
||||
from .world_physics_randomizer import *
|
||||
|
||||
# import body physics randomizer
|
||||
from .body_physics_randomizer import *
|
||||
|
||||
# import link physics randomizer
|
||||
from .link_physics_randomizer import *
|
||||
|
||||
# import joint physics randomizer
|
||||
from .joint_physics_randomizer import *
|
||||
|
||||
# import robot physics randomizer
|
||||
from .robot_physics_randomizer import *
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python
|
||||
"""Define the `BodyPhysicsRandomizer` class which randomizes the physical attributes / properties of a body.
|
||||
|
||||
Dependencies:
|
||||
- `pyrobolearn.physics`
|
||||
- `pyrobolearn.robots`
|
||||
"""
|
||||
|
||||
from pyrobolearn.physics.physics_randomizer import PhysicsRandomizer
|
||||
# from pyrobolearn.robots.base import Object # TODO: change to Body or MultiBody
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class BodyPhysicsRandomizer(PhysicsRandomizer):
|
||||
r"""Body Physics Randomizer
|
||||
|
||||
The body physics randomizer can randomize the physical attributes of a body. It is an abstract class which is
|
||||
inherited notably by `LinkPhysicsRandomizer` and `JointPhysicsRandomizer`.
|
||||
"""
|
||||
|
||||
def __init__(self, body):
|
||||
"""
|
||||
Initialize the body physics randomizer.
|
||||
|
||||
Args:
|
||||
body (Body): multi-body object.
|
||||
"""
|
||||
self.body = body
|
||||
simulator = self.body.sim
|
||||
super(BodyPhysicsRandomizer, self).__init__(simulator)
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
##############
|
||||
|
||||
@property
|
||||
def body(self):
|
||||
"""Return the body / object instance."""
|
||||
return self._body
|
||||
|
||||
@body.setter
|
||||
def body(self, body):
|
||||
"""Set the body / object instance."""
|
||||
# TODO: uncomment the following lines
|
||||
# if not isinstance(body, Object):
|
||||
# raise TypeError("Expecting the given body to be an instance of `Object`, instead got: "
|
||||
# "{}".format(type(body)))
|
||||
self._body = body
|
||||
|
||||
@property
|
||||
def num_links(self):
|
||||
"""Return the number of links of the body."""
|
||||
return self.body.num_links
|
||||
|
||||
@property
|
||||
def num_joints(self):
|
||||
"""Return the number of joints of the body."""
|
||||
return self.body.num_joints
|
||||
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python
|
||||
"""Define the `JointPhysicsRandomizer` class which randomizes the physical attributes / properties of a joint or
|
||||
multiple joints of a specific body.
|
||||
|
||||
Dependencies:
|
||||
- `pyrobolearn.physics`
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
from pyrobolearn.physics.body_physics_randomizer import BodyPhysicsRandomizer
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class JointPhysicsRandomizer(BodyPhysicsRandomizer):
|
||||
r"""Joint Physics Randomizer
|
||||
|
||||
The joint physics randomizer can randomize the physical attributes of a joint. For instance, this can be the
|
||||
joint friction or damping coefficients. Other attributes can be the maximum force or velocity the joint(s) can
|
||||
achieve.
|
||||
"""
|
||||
|
||||
def __init__(self, body, joint_ids=None, joint_damping=None, **kwargs):
|
||||
"""
|
||||
Initialize the joint physics randomizer.
|
||||
|
||||
Args:
|
||||
body (Body): multi-body object.
|
||||
joint_ids (int, list of int, None): joint id(s).
|
||||
joint_damping (float, list of float, tuple of float, list of tuple of float, None): joint damping
|
||||
coefficient. If None, it will take the default joint damping value associated with the given
|
||||
`joint_ids` of the given `body`. If float, it will set that value to the specified joints and will
|
||||
always return this value when sampling. If list of float, it will set each value to each joint and will
|
||||
always return these values when sampling. If tuple of float, the first item is the lower bound of the
|
||||
joint damping and the second item is its upper bound. It will set these bounds for each joint. If list
|
||||
of tuples of joints, it will have a tuple of lower / upper bound for each joint.
|
||||
**kwargs (dict): range of possible physical properties. If given one value, that property won't be
|
||||
randomized. Each range is a tuple of two values `[lower_bound, upper_bound]`.
|
||||
"""
|
||||
super(JointPhysicsRandomizer, self).__init__(body)
|
||||
self.joints = joint_ids
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
##############
|
||||
|
||||
@property
|
||||
def joints(self):
|
||||
"""Return the list of joint ids."""
|
||||
return self._joints
|
||||
|
||||
@joints.setter
|
||||
def joints(self, joints):
|
||||
"""Set the joint id or the list of joint ids."""
|
||||
if joints is None:
|
||||
joints = self.body.joints
|
||||
elif isinstance(joints, int):
|
||||
joints = [joints]
|
||||
elif isinstance(joints, collections.Iterable):
|
||||
for idx, joint in enumerate(joints):
|
||||
if not isinstance(joint, int):
|
||||
raise TypeError("The {} element of the given list of joints is not an integer, instead got: "
|
||||
"{}".format(idx, type(joint)))
|
||||
else:
|
||||
raise TypeError("Expecting the given joints to be an integer or a list of integers, instead got: "
|
||||
"{}".format(type(joints)))
|
||||
self._joints = joints
|
||||
|
||||
@property
|
||||
def joint_dampings(self):
|
||||
"""Return the joint dampings associated with the joints."""
|
||||
return self.body.get_joint_dampings(self.joints)
|
||||
|
||||
@joint_dampings.setter
|
||||
def joint_dampings(self, values):
|
||||
"""Set the given joint damping values to each joint."""
|
||||
for joint, value in zip(self.joints, values):
|
||||
self.body.set_joint_damping(joint, value)
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
def names(self):
|
||||
"""Return an iterator over the property names."""
|
||||
for name in ['joint_damping']:
|
||||
yield name
|
||||
|
||||
def bounds(self):
|
||||
"""Return an iterator over the bounds for each property."""
|
||||
pass
|
||||
|
||||
def get_properties(self):
|
||||
"""
|
||||
Get the physics properties.
|
||||
|
||||
Returns:
|
||||
dict: current physic property values.
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_properties(self, properties):
|
||||
"""
|
||||
Set the given physic property values using the simulator.
|
||||
|
||||
Args:
|
||||
properties (dict): the physic property values to be set in the simulator.
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,134 @@
|
||||
#!/usr/bin/env python
|
||||
"""Define the `LinkPhysicsRandomizer` class which randomizes the physical attributes / properties of a link or
|
||||
multiple links of a specific body.
|
||||
|
||||
Dependencies:
|
||||
- `pyrobolearn.physics`
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
from pyrobolearn.physics.body_physics_randomizer import BodyPhysicsRandomizer
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class LinkPhysicsRandomizer(BodyPhysicsRandomizer):
|
||||
r"""Link Physics Randomizer
|
||||
|
||||
The link physics randomizer can randomize the physical attributes of a link.
|
||||
"""
|
||||
|
||||
def __init__(self, body, link_ids=None, masses=None, local_inertia_diagonals=None, local_inertia_positions=None,
|
||||
local_inertia_orientations=None, lateral_frictions=None, spinning_frictions=None,
|
||||
rolling_frictions=None, restitutions=None, linear_dampings=None, angular_dampings=None,
|
||||
contact_stiffnesses=None, contact_dampings=None, **kwargs):
|
||||
"""
|
||||
Initialize the link physics randomizer.
|
||||
|
||||
Args:
|
||||
body (Body): multi-body object.
|
||||
link_ids (int, list of int, None): link id(s).
|
||||
**kwargs (dict): range of possible physical properties. If given one value, that property won't be
|
||||
randomized. Each range is a tuple of two values `[lower_bound, upper_bound]`.
|
||||
"""
|
||||
super(LinkPhysicsRandomizer, self).__init__(body)
|
||||
self.links = link_ids
|
||||
|
||||
# set the bounds
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
##############
|
||||
|
||||
@property
|
||||
def links(self):
|
||||
"""Return the list of link ids."""
|
||||
return self._links
|
||||
|
||||
@links.setter
|
||||
def links(self, links):
|
||||
"""Set the link id or the list of link ids."""
|
||||
if isinstance(links, int):
|
||||
links = [links]
|
||||
elif isinstance(links, collections.Iterable):
|
||||
for idx, link in enumerate(links):
|
||||
if not isinstance(link, int):
|
||||
raise TypeError("The {} element of the given list of links is not an integer, instead got: "
|
||||
"{}".format(idx, type(link)))
|
||||
else:
|
||||
raise TypeError("Expecting the given links to be an integer or a list of integers, instead got: "
|
||||
"{}".format(type(links)))
|
||||
self._links = links
|
||||
|
||||
@property
|
||||
def masses(self):
|
||||
"""Return the mass of each specified link."""
|
||||
return self.body.get_masses(self.links)
|
||||
|
||||
@masses.setter
|
||||
def masses(self, values):
|
||||
"""Set the mass values."""
|
||||
self.body.set_masses(self.links, values)
|
||||
|
||||
@property
|
||||
def mass_bounds(self):
|
||||
"""Return the lower and upper bounds of each link mass."""
|
||||
return self._mass_bounds
|
||||
|
||||
@mass_bounds.setter
|
||||
def mass_bounds(self, bounds):
|
||||
"""Set the mass bound for each link."""
|
||||
if isinstance(bounds, (float, int)):
|
||||
bounds = [(bounds, bounds) for _ in self.links]
|
||||
elif isinstance(bounds, (list, tuple, np.ndarray)):
|
||||
pass
|
||||
self._mass_bounds = bounds
|
||||
|
||||
@property
|
||||
def dynamics(self):
|
||||
return None
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
def names(self):
|
||||
"""Return an iterator over the property names."""
|
||||
for name in ['mass']:
|
||||
yield name
|
||||
|
||||
def bounds(self):
|
||||
"""Return an iterator over the bounds for each property."""
|
||||
yield self.mass_bounds
|
||||
|
||||
def get_properties(self):
|
||||
"""
|
||||
Get the physics properties.
|
||||
|
||||
Returns:
|
||||
dict: current physic property values.
|
||||
"""
|
||||
properties = dict()
|
||||
# properties['mass'] =
|
||||
return properties
|
||||
|
||||
def set_properties(self, properties):
|
||||
"""
|
||||
Set the given physic property values using the simulator.
|
||||
|
||||
Args:
|
||||
properties (dict): the physic property values to be set in the simulator.
|
||||
"""
|
||||
if not isinstance(properties, dict):
|
||||
raise TypeError("Expecting the given 'properties' to be a dictionary, instead got: "
|
||||
"{}".format(type(properties)))
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python
|
||||
"""Define the `PhysicsRandomizer` class which randomizes the physical attributes / properties of an object.
|
||||
|
||||
This is the main abstract class from which all physics randomizers inherit from.
|
||||
|
||||
Dependencies:
|
||||
- `pyrobolearn.simulators`
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pyrobolearn.simulators import Simulator
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class PhysicsRandomizer(object):
|
||||
r"""Physics Randomizer
|
||||
|
||||
This the main abstract class from which all the physics randomizers inherit from. A physics randomizer randomize
|
||||
the physics properties of a certain object. This can be, for instance, the world, a robot, or a particular link
|
||||
of that robot.
|
||||
|
||||
It can change for example the dynamics of a particular object such as the mass, inertia, or others. It can also
|
||||
change their physical properties such as the friction, bounciness, etc.
|
||||
|
||||
Note that the physics randomizer instance has access to the simulator in order to modify the physical properties.
|
||||
Also, note that normally the physics randomizer is called at the beginning of an episode, and not at each time
|
||||
step. Changing the physical properties at each time step can lead to weird behaviors.
|
||||
|
||||
It is possible to not randomize some physical properties by specifying a specific value instead of a range (=tuple
|
||||
of 2 values; lower and upper bound).
|
||||
"""
|
||||
|
||||
def __init__(self, simulator):
|
||||
"""
|
||||
Initialize the physics randomizer.
|
||||
|
||||
Args:
|
||||
simulator (Simulator): simulator instance
|
||||
"""
|
||||
self.simulator = simulator
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
##############
|
||||
|
||||
@property
|
||||
def simulator(self):
|
||||
"""Return the simulator instance."""
|
||||
return self._simulator
|
||||
|
||||
@simulator.setter
|
||||
def simulator(self, simulator):
|
||||
"""Set the simulator instance."""
|
||||
# TODO: uncomment the following lines
|
||||
# if not isinstance(simulator, Simulator):
|
||||
# raise TypeError("Expecting the given simulator to be an instance of `Simulator`, instead got: "
|
||||
# "{}".format(type(simulator)))
|
||||
self._simulator = simulator
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
def properties(self):
|
||||
"""Return an iterator over the properties."""
|
||||
properties = self.get_properties()
|
||||
for p in properties.values():
|
||||
yield p
|
||||
|
||||
def named_properties(self):
|
||||
"""Return an iterator over the properties with their name and value"""
|
||||
properties = self.get_properties()
|
||||
for name, p in properties.items():
|
||||
yield name, p
|
||||
|
||||
def names(self):
|
||||
"""Return an iterator over the property names."""
|
||||
pass
|
||||
|
||||
def bounds(self):
|
||||
"""Return an iterator over the bounds for each property."""
|
||||
pass
|
||||
|
||||
def named_bounds(self):
|
||||
"""Return an iterator over the property bounds with their name and value."""
|
||||
for name, bound in zip(self.names(), self.bounds()):
|
||||
yield name, bound
|
||||
|
||||
def get_properties(self):
|
||||
"""
|
||||
Get the physics properties.
|
||||
|
||||
Returns:
|
||||
dict: current physic property values.
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_properties(self, properties):
|
||||
"""
|
||||
Set the given physic property values using the simulator.
|
||||
|
||||
Args:
|
||||
properties (dict): the physic property values to be set in the simulator.
|
||||
"""
|
||||
pass
|
||||
|
||||
def sample(self, seed=None):
|
||||
"""
|
||||
Sample a new set of physics properties and returns it. Note that it doesn't set them in the simulator.
|
||||
This sampling can be useful if the user wishes to check more carefully the sampled physic property values.
|
||||
Once satisfied, the user can set them by calling the `set_properties` method.
|
||||
|
||||
Note that it samples uniformly the physics properties between their specified lower and upper bounds.
|
||||
|
||||
Args:
|
||||
seed (int, None): random seed.
|
||||
|
||||
Returns:
|
||||
dict: sampled physic properties.
|
||||
"""
|
||||
# set random seed
|
||||
if seed is not None:
|
||||
np.random.seed(seed)
|
||||
|
||||
# sample each property
|
||||
properties = dict()
|
||||
for name, bound in zip(self.names(), self.bounds()):
|
||||
properties[name] = np.random.uniform(low=bound[0], high=bound[1])
|
||||
return properties
|
||||
|
||||
def randomize(self, seed=None):
|
||||
"""
|
||||
Randomize the physics properties and set them in the simulator.
|
||||
|
||||
Args:
|
||||
seed (int, None): random seed.
|
||||
"""
|
||||
sampled_properties = self.sample(seed)
|
||||
self.set_properties(sampled_properties)
|
||||
|
||||
def seed(self, seed=None):
|
||||
"""
|
||||
Set the given seed when sampling or randomizing the environment.
|
||||
|
||||
Args:
|
||||
seed (int): random seed.
|
||||
"""
|
||||
if seed is not None:
|
||||
np.random.seed(seed)
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python
|
||||
"""Define the `RobotPhysicsRandomizer` class which randomizes the physical attributes / properties of links and joints.
|
||||
|
||||
Dependencies:
|
||||
- `pyrobolearn.physics`
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
from pyrobolearn.physics.body_physics_randomizer import BodyPhysicsRandomizer
|
||||
from pyrobolearn.physics.link_physics_randomizer import LinkPhysicsRandomizer
|
||||
from pyrobolearn.physics.joint_physics_randomizer import JointPhysicsRandomizer
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class RobotPhysicsRandomizer(BodyPhysicsRandomizer):
|
||||
r"""Robot Physics Randomizer
|
||||
|
||||
The robot physics randomizer can randomize the physical attributes of a robot. It can notably change its mass,
|
||||
the contact frictions, the inertia of the links, the friction and damping coefficients of the joints, etc.
|
||||
"""
|
||||
|
||||
def __init__(self, body, links=None, joints=None, **kwargs):
|
||||
"""
|
||||
Initialize the robot physics randomizer.
|
||||
|
||||
Args:
|
||||
body (Body): multi-body object.
|
||||
links (int, list of int, LinkPhysicsRandomizer, list of LinkPhysicsRandomizer, None): link id(s) or link
|
||||
physics randomizer(s).
|
||||
joints (int, list of int, JointPhysicsRandomizer, list JointPhysicsRandomizer, None): joint id(s) or joint
|
||||
physics randomizer.
|
||||
**kwargs (dict): range of possible physical properties. If given one value, that property won't be
|
||||
randomized. Each range is a tuple of two values `[lower_bound, upper_bound]`.
|
||||
"""
|
||||
super(RobotPhysicsRandomizer, self).__init__(body)
|
||||
self.links = links
|
||||
self.joints = joints
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
##############
|
||||
|
||||
@property
|
||||
def links(self):
|
||||
"""Return the list of link physics randomizers."""
|
||||
return self._links
|
||||
|
||||
@links.setter
|
||||
def links(self, links):
|
||||
"""Set the link physics randomizer or the list of link physics randomizers."""
|
||||
if isinstance(links, int):
|
||||
links = [LinkPhysicsRandomizer(self.body, links)]
|
||||
elif isinstance(links, LinkPhysicsRandomizer):
|
||||
links = [links]
|
||||
elif isinstance(links, collections.Iterable):
|
||||
link_list = []
|
||||
for idx, link in enumerate(links):
|
||||
if isinstance(link, int):
|
||||
link = LinkPhysicsRandomizer(self.body, links)
|
||||
elif not isinstance(link, LinkPhysicsRandomizer):
|
||||
raise TypeError("The {} element of the given list of links is not an integer or a "
|
||||
"LinkPhysicsRandomizer, instead got: {}".format(idx, type(link)))
|
||||
link_list.append(link)
|
||||
links = link_list
|
||||
else:
|
||||
raise TypeError("Expecting the given links to be an integer / `LinkPhysicsRandomizer` or a list of "
|
||||
"integers / `LinkPhysicsRandomizer`, instead got: {}".format(type(links)))
|
||||
self._links = links
|
||||
|
||||
@property
|
||||
def joints(self):
|
||||
"""Return the list of joint physics randomizers."""
|
||||
return self._joints
|
||||
|
||||
@joints.setter
|
||||
def joints(self, joints):
|
||||
"""Set the joint physics randomizer or the list of joint physics randomizers."""
|
||||
if isinstance(joints, int):
|
||||
joints = [JointPhysicsRandomizer(self.body, joints)]
|
||||
elif isinstance(joints, JointPhysicsRandomizer):
|
||||
joints = [joints]
|
||||
elif isinstance(joints, collections.Iterable):
|
||||
joint_list = []
|
||||
for idx, joint in enumerate(joints):
|
||||
if isinstance(joint, int):
|
||||
joint = JointPhysicsRandomizer(self.body, joints)
|
||||
elif not isinstance(joint, JointPhysicsRandomizer):
|
||||
raise TypeError("The {} element of the given list of joints is not an integer or a "
|
||||
"JointPhysicsRandomizer, instead got: {}".format(idx, type(joint)))
|
||||
joint_list.append(joint)
|
||||
joints = joint_list
|
||||
else:
|
||||
raise TypeError("Expecting the given joints to be an integer / `JointPhysicsRandomizer` or a list of "
|
||||
"integers / `JointPhysicsRandomizer`, instead got: {}".format(type(joints)))
|
||||
self._joints = joints
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
def names(self):
|
||||
"""Return an iterator over the property names."""
|
||||
pass
|
||||
|
||||
def bounds(self):
|
||||
"""Return an iterator over the bounds for each property."""
|
||||
pass
|
||||
|
||||
def get_properties(self):
|
||||
"""
|
||||
Get the physics properties.
|
||||
|
||||
Returns:
|
||||
dict: current physic property values.
|
||||
"""
|
||||
pass
|
||||
|
||||
def set_properties(self, properties):
|
||||
"""
|
||||
Set the given physic property values using the simulator.
|
||||
|
||||
Args:
|
||||
properties (dict): the physic property values to be set in the simulator.
|
||||
"""
|
||||
pass
|
||||
@@ -0,0 +1,462 @@
|
||||
#!/usr/bin/env python
|
||||
"""Define the `WorldPhysicsRandomizer` class which randomizes the physical attributes / properties of the world.
|
||||
|
||||
Dependencies:
|
||||
- `pyrobolearn.physics`
|
||||
- `pyrobolearn.world`
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
|
||||
from pyrobolearn.worlds import World
|
||||
from pyrobolearn.physics.physics_randomizer import PhysicsRandomizer
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class WorldPhysicsRandomizer(PhysicsRandomizer):
|
||||
r"""World Physics Randomizer
|
||||
|
||||
The world physics randomizer can randomize the physical attributes of a world. It can notably change the gravity,
|
||||
the light intensity, the floor friction coefficients (including lateral, spinning, and rolling frictions), etc.
|
||||
"""
|
||||
|
||||
def __init__(self, world, gravity=None, lateral_friction=None, rolling_friction=None, spinning_friction=None,
|
||||
restitution=None, contact_damping=None, contact_stiffness=None, **kwargs):
|
||||
"""
|
||||
Initialize the world physics randomizer.
|
||||
|
||||
Args:
|
||||
world (World): world instance.
|
||||
gravity (None, np.float[3], tuple of np.float[3]): gravity bounds. If None, it will take
|
||||
the default value returned by the world, and it will not sample from it. If it is a float[3], it will
|
||||
set that value to the world and will always return this value when sampling from the physics randomizer.
|
||||
If it is a tuple, it has to be of length 2 where the first item is the lower bound and the second item
|
||||
is the upper bound.
|
||||
lateral_friction (None, float, tuple of float): lateral friction coefficient bounds. If None, it will take
|
||||
the default value returned by the world, and it will not sample from it. If it is a float, it will set
|
||||
that value to the world and will always return this value when sampling from the physics randomizer.
|
||||
If it is a tuple, it has to be of length 2 where the first item is the lower bound and the second item
|
||||
is the upper bound.
|
||||
rolling_friction (None, float, tuple of float): rolling friction coefficient bounds. (same types as
|
||||
described in `lateral_friction`)
|
||||
spinning_friction (None, float, tuple of float): spinning friction coefficient bounds. (same types as
|
||||
described in `lateral_friction`)
|
||||
restitution (None, float, tuple of float): restitution bounds. (same types as described in
|
||||
`lateral_friction`)
|
||||
contact_damping (None, float, tuple of float): contact damping bounds. (same types as described in
|
||||
`lateral_friction`)
|
||||
contact_stiffness (None, float, tuple of float): contact stiffness bounds. (same types as described in
|
||||
`lateral_friction`)
|
||||
**kwargs (dict): range of possible physical properties. If given one value, that property won't be
|
||||
randomized. Each range is a tuple of two values `[lower_bound, upper_bound]`.
|
||||
"""
|
||||
self.world = world
|
||||
simulator = self.world.simulator
|
||||
super(WorldPhysicsRandomizer, self).__init__(simulator)
|
||||
|
||||
# set the bounds
|
||||
self.gravity_bounds = gravity
|
||||
self.lateral_friction_bounds = lateral_friction
|
||||
self.rolling_friction_bounds = rolling_friction
|
||||
self.spinning_friction_bounds = spinning_friction
|
||||
self.restitution_bounds = restitution
|
||||
self.contact_damping_bounds = contact_damping
|
||||
self.contact_stiffness_bounds = contact_stiffness
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
##############
|
||||
|
||||
@property
|
||||
def world(self):
|
||||
"""Return the world instance."""
|
||||
return self._world
|
||||
|
||||
@world.setter
|
||||
def world(self, world):
|
||||
"""Set the world instance."""
|
||||
if not isinstance(world, World):
|
||||
raise TypeError("Expecting the given world to be an instance of `World`, instead got: "
|
||||
"{}".format(type(world)))
|
||||
self._world = world
|
||||
|
||||
@property
|
||||
def gravity(self):
|
||||
"""Return the gravity vector."""
|
||||
return self.world.gravity
|
||||
|
||||
@gravity.setter
|
||||
def gravity(self, gravity):
|
||||
"""
|
||||
Set the gravity vector.
|
||||
|
||||
Args:
|
||||
(np.float[3]): gravity vector [x,y,z].
|
||||
"""
|
||||
self.world.gravity = gravity
|
||||
|
||||
@property
|
||||
def gravity_bounds(self):
|
||||
"""Return the upper and lower bound for the gravity vector."""
|
||||
return self._gravity_bounds
|
||||
|
||||
@gravity_bounds.setter
|
||||
def gravity_bounds(self, bounds):
|
||||
"""Set the upper and lower bounds for the gravity vector."""
|
||||
if bounds is None:
|
||||
bounds = (self.gravity, self.gravity)
|
||||
elif isinstance(bounds, (list, tuple, np.ndarray)):
|
||||
if len(bounds) == 2:
|
||||
for bound in bounds:
|
||||
if not isinstance(bound, (list, tuple, np.ndarray)):
|
||||
raise TypeError("Expecting one of the bounds to be a 3D vector (list, tuple, or np.ndarray), "
|
||||
"instead got {}".format(type(bound)))
|
||||
if len(bound) != 3:
|
||||
raise ValueError("Expecting the gravity to be a 3D vector, instead received a {}D "
|
||||
"vector".format(len(bounds)))
|
||||
elif len(bounds) == 3:
|
||||
bounds = (bounds, bounds)
|
||||
else:
|
||||
raise ValueError("Expecting the gravity to be a 3D vector or a tuple of length 2 where the first item "
|
||||
"is the lower bound and the second item is the upper bound of 3D vectors, instead "
|
||||
"the given element has a length of {}".format(len(bounds)))
|
||||
else:
|
||||
raise TypeError("Expecting the gravity bounds to be a 3d vector, a tuple of 3d vectors, or None. Instead "
|
||||
"got {}".format(type(bounds)))
|
||||
self._gravity_bounds = bounds
|
||||
|
||||
@property
|
||||
def lateral_friction(self):
|
||||
"""Return the floor lateral friction coefficient."""
|
||||
return self.world.lateral_friction
|
||||
|
||||
@lateral_friction.setter
|
||||
def lateral_friction(self, coefficient):
|
||||
"""
|
||||
Set the floor lateral friction coefficient.
|
||||
|
||||
Args:
|
||||
coefficient (float): lateral friction coefficient.
|
||||
"""
|
||||
self.world.lateral_friction = coefficient
|
||||
|
||||
@property
|
||||
def lateral_friction_bounds(self):
|
||||
"""Return the upper and lower bound for the lateral friction coefficient."""
|
||||
return self._lateral_friction_bounds
|
||||
|
||||
@lateral_friction_bounds.setter
|
||||
def lateral_friction_bounds(self, bounds):
|
||||
"""Set the upper and lower bounds for the lateral friction coefficient."""
|
||||
if bounds is None:
|
||||
bounds = (self.lateral_friction, self.lateral_friction)
|
||||
elif isinstance(bounds, float):
|
||||
bounds = (bounds, bounds)
|
||||
elif isinstance(bounds, (tuple, list)):
|
||||
if len(bounds) == 2:
|
||||
for bound in bounds:
|
||||
if not isinstance(bound, float):
|
||||
raise TypeError("Expecting one of the bounds to be a float instead got {}".format(type(bound)))
|
||||
else:
|
||||
raise ValueError("Expecting the bounds to be a tuple of length 2 where the first item is the lower "
|
||||
"bound and the second item is the upper bound of the lateral friction, instead got "
|
||||
"a length of {}".format(len(bounds)))
|
||||
else:
|
||||
raise TypeError("Expecting the lateral friction bounds to be a float, or a tuple of float, or None. "
|
||||
"Instead got {}".format(type(bounds)))
|
||||
self._lateral_friction_bounds = bounds
|
||||
|
||||
@property
|
||||
def rolling_friction(self):
|
||||
"""Return the floor rolling friction coefficient."""
|
||||
return self.world.rolling_friction
|
||||
|
||||
@rolling_friction.setter
|
||||
def rolling_friction(self, coefficient):
|
||||
"""
|
||||
Set the floor rolling friction coefficient.
|
||||
|
||||
Args:
|
||||
coefficient (float): rolling friction coefficient.
|
||||
"""
|
||||
self.world.rolling_friction = coefficient
|
||||
|
||||
@property
|
||||
def rolling_friction_bounds(self):
|
||||
"""Return the upper and lower bound for the rolling friction coefficient."""
|
||||
return self._rolling_friction_bounds
|
||||
|
||||
@rolling_friction_bounds.setter
|
||||
def rolling_friction_bounds(self, bounds):
|
||||
"""Set the upper and lower bounds for the rolling friction coefficient."""
|
||||
if bounds is None:
|
||||
bounds = (self.rolling_friction, self.rolling_friction)
|
||||
elif isinstance(bounds, float):
|
||||
bounds = (bounds, bounds)
|
||||
elif isinstance(bounds, (tuple, list)):
|
||||
if len(bounds) == 2:
|
||||
for bound in bounds:
|
||||
if not isinstance(bound, float):
|
||||
raise TypeError("Expecting one of the bounds to be a float instead got {}".format(type(bound)))
|
||||
else:
|
||||
raise ValueError("Expecting the bounds to be a tuple of length 2 where the first item is the lower "
|
||||
"bound and the second item is the upper bound of the rolling friction, instead got "
|
||||
"a length of {}".format(len(bounds)))
|
||||
else:
|
||||
raise TypeError("Expecting the rolling friction bounds to be a float, or a tuple of float, or None. "
|
||||
"Instead got {}".format(type(bounds)))
|
||||
self._rolling_friction_bounds = bounds
|
||||
|
||||
@property
|
||||
def spinning_friction(self):
|
||||
"""Return the floor spinning friction coefficient."""
|
||||
return self.world.spinning_friction
|
||||
|
||||
@spinning_friction.setter
|
||||
def spinning_friction(self, coefficient):
|
||||
"""
|
||||
Set the floor spinning friction coefficient.
|
||||
|
||||
Args:
|
||||
coefficient (float): spinning friction coefficient.
|
||||
"""
|
||||
self.world.spinning_friction = coefficient
|
||||
|
||||
@property
|
||||
def spinning_friction_bounds(self):
|
||||
"""Return the upper and lower bound for the spinning friction coefficient."""
|
||||
return self._spinning_friction_bounds
|
||||
|
||||
@spinning_friction_bounds.setter
|
||||
def spinning_friction_bounds(self, bounds):
|
||||
"""Set the upper and lower bounds for the spinning friction coefficient."""
|
||||
if bounds is None:
|
||||
bounds = (self.spinning_friction, self.spinning_friction)
|
||||
elif isinstance(bounds, float):
|
||||
bounds = (bounds, bounds)
|
||||
elif isinstance(bounds, (tuple, list)):
|
||||
if len(bounds) == 2:
|
||||
for bound in bounds:
|
||||
if not isinstance(bound, float):
|
||||
raise TypeError("Expecting one of the bounds to be a float instead got {}".format(type(bound)))
|
||||
else:
|
||||
raise ValueError("Expecting the bounds to be a tuple of length 2 where the first item is the lower "
|
||||
"bound and the second item is the upper bound of the spinning friction, instead got "
|
||||
"a length of {}".format(len(bounds)))
|
||||
else:
|
||||
raise TypeError("Expecting the spinning friction bounds to be a float, or a tuple of float, or None. "
|
||||
"Instead got {}".format(type(bounds)))
|
||||
self._spinning_friction_bounds = bounds
|
||||
|
||||
@property
|
||||
def restitution(self):
|
||||
"""Return the floor restitution (bounciness) coefficient."""
|
||||
return self.world.restitution
|
||||
|
||||
@restitution.setter
|
||||
def restitution(self, coefficient):
|
||||
"""
|
||||
Set the floor restitution (bounciness) coefficient.
|
||||
|
||||
Args:
|
||||
coefficient (float): restitution coefficient.
|
||||
"""
|
||||
self.world.restitution = coefficient
|
||||
|
||||
@property
|
||||
def restitution_bounds(self):
|
||||
"""Return the upper and lower bound for the restitution coefficient."""
|
||||
return self._restitution_bounds
|
||||
|
||||
@restitution_bounds.setter
|
||||
def restitution_bounds(self, bounds):
|
||||
"""Set the upper and lower bounds for the restitution coefficient."""
|
||||
if bounds is None:
|
||||
bounds = (self.restitution, self.restitution)
|
||||
elif isinstance(bounds, float):
|
||||
bounds = (bounds, bounds)
|
||||
elif isinstance(bounds, (tuple, list)):
|
||||
if len(bounds) == 2:
|
||||
for bound in bounds:
|
||||
if not isinstance(bound, float):
|
||||
raise TypeError("Expecting one of the bounds to be a float instead got {}".format(type(bound)))
|
||||
else:
|
||||
raise ValueError("Expecting the bounds to be a tuple of length 2 where the first item is the lower "
|
||||
"bound and the second item is the upper bound of the restitution, instead got "
|
||||
"a length of {}".format(len(bounds)))
|
||||
else:
|
||||
raise TypeError("Expecting the restitution bounds to be a float, or a tuple of float, or None. "
|
||||
"Instead got {}".format(type(bounds)))
|
||||
self._restitution_bounds = bounds
|
||||
|
||||
@property
|
||||
def contact_damping(self):
|
||||
"""Return the floor contact damping."""
|
||||
return self.world.contact_damping
|
||||
|
||||
@contact_damping.setter
|
||||
def contact_damping(self, value):
|
||||
"""
|
||||
Set the floor contact damping.
|
||||
|
||||
Args:
|
||||
value (float): contact damping value.
|
||||
"""
|
||||
self.world.contact_damping = value
|
||||
|
||||
@property
|
||||
def contact_damping_bounds(self):
|
||||
"""Return the upper and lower bound for the contact damping value."""
|
||||
return self._contact_damping_bounds
|
||||
|
||||
@contact_damping_bounds.setter
|
||||
def contact_damping_bounds(self, bounds):
|
||||
"""Set the upper and lower bounds for the contact damping value."""
|
||||
if bounds is None:
|
||||
bounds = (self.contact_damping, self.contact_damping)
|
||||
elif isinstance(bounds, float):
|
||||
bounds = (bounds, bounds)
|
||||
elif isinstance(bounds, (tuple, list)):
|
||||
if len(bounds) == 2:
|
||||
for bound in bounds:
|
||||
if not isinstance(bound, float):
|
||||
raise TypeError("Expecting one of the bounds to be a float instead got {}".format(type(bound)))
|
||||
else:
|
||||
raise ValueError("Expecting the bounds to be a tuple of length 2 where the first item is the lower "
|
||||
"bound and the second item is the upper bound of the contact damping, instead got "
|
||||
"a length of {}".format(len(bounds)))
|
||||
else:
|
||||
raise TypeError("Expecting the contact damping bounds to be a float, or a tuple of float, or None. "
|
||||
"Instead got {}".format(type(bounds)))
|
||||
self._contact_damping_bounds = bounds
|
||||
|
||||
@property
|
||||
def contact_stiffness(self):
|
||||
"""Return the floor contact stiffness."""
|
||||
return self.world.contact_stiffness
|
||||
|
||||
@contact_stiffness.setter
|
||||
def contact_stiffness(self, value):
|
||||
"""
|
||||
Set the floor contact stiffness.
|
||||
|
||||
Args:
|
||||
value (float): contact stiffness value.
|
||||
"""
|
||||
self.world.contact_stiffness = value
|
||||
|
||||
@property
|
||||
def contact_stiffness_bounds(self):
|
||||
"""Return the upper and lower bound for the contact stiffness value."""
|
||||
return self._contact_stiffness_bounds
|
||||
|
||||
@contact_stiffness_bounds.setter
|
||||
def contact_stiffness_bounds(self, bounds):
|
||||
"""Set the upper and lower bounds for the contact stiffness value."""
|
||||
if bounds is None:
|
||||
bounds = (self.contact_stiffness, self.contact_stiffness)
|
||||
elif isinstance(bounds, float):
|
||||
bounds = (bounds, bounds)
|
||||
elif isinstance(bounds, (tuple, list)):
|
||||
if len(bounds) == 2:
|
||||
for bound in bounds:
|
||||
if not isinstance(bound, float):
|
||||
raise TypeError("Expecting one of the bounds to be a float instead got {}".format(type(bound)))
|
||||
else:
|
||||
raise ValueError("Expecting the bounds to be a tuple of length 2 where the first item is the lower "
|
||||
"bound and the second item is the upper bound of the contact stiffness, instead got "
|
||||
"a length of {}".format(len(bounds)))
|
||||
else:
|
||||
raise TypeError("Expecting the contact stiffness bounds to be a float, or a tuple of float, or None. "
|
||||
"Instead got {}".format(type(bounds)))
|
||||
self._contact_stiffness_bounds = bounds
|
||||
|
||||
@property
|
||||
def floor_dynamics(self):
|
||||
"""Return the floor dynamical parameters (friction, restitution, etc).
|
||||
|
||||
Returns:
|
||||
float: lateral friction coefficient
|
||||
float: rolling friction coefficient
|
||||
float: spinning friction coefficient
|
||||
float: restitution coefficient
|
||||
float: contact damping value
|
||||
float: contact stiffness value
|
||||
"""
|
||||
return self.world.floor_dynamics
|
||||
|
||||
@floor_dynamics.setter
|
||||
def floor_dynamics(self, dynamics):
|
||||
"""
|
||||
Set the floor dynamics.
|
||||
|
||||
Args:
|
||||
values (dict): floor dynamics.
|
||||
"""
|
||||
self.world.floor_dynamics = dynamics
|
||||
|
||||
###########
|
||||
# Methods #
|
||||
###########
|
||||
|
||||
def names(self):
|
||||
"""Return an iterator over the property names."""
|
||||
for name in ['gravity', 'lateral_friction', 'rolling_friction', 'spinning_friction', 'restitution',
|
||||
'contact_damping', 'contact_stiffness']:
|
||||
yield name
|
||||
|
||||
def bounds(self):
|
||||
"""Return an iterator over the bounds"""
|
||||
yield self.gravity_bounds
|
||||
yield self.lateral_friction_bounds
|
||||
yield self.rolling_friction_bounds
|
||||
yield self.spinning_friction_bounds
|
||||
yield self.restitution_bounds
|
||||
yield self.contact_damping_bounds
|
||||
yield self.contact_stiffness_bounds
|
||||
|
||||
def get_properties(self):
|
||||
"""
|
||||
Get the physics properties.
|
||||
|
||||
Returns:
|
||||
dict: current physic property values.
|
||||
"""
|
||||
properties = dict()
|
||||
properties['gravity'] = self.gravity
|
||||
floor_dynamics = self.floor_dynamics
|
||||
if floor_dynamics is not None: # there is a floor
|
||||
properties['lateral_friction'] = floor_dynamics[0]
|
||||
properties['rolling_friction'] = floor_dynamics[1]
|
||||
properties['spinning_friction'] = floor_dynamics[2]
|
||||
properties['restitution'] = floor_dynamics[3]
|
||||
properties['contact_damping'] = floor_dynamics[4]
|
||||
properties['contact_stiffness'] = floor_dynamics[5]
|
||||
return properties
|
||||
|
||||
def set_properties(self, properties):
|
||||
"""
|
||||
Set the given physic property values using the simulator.
|
||||
|
||||
Args:
|
||||
properties (dict): the physic property values to be set in the simulator.
|
||||
"""
|
||||
if not isinstance(properties, dict):
|
||||
raise TypeError("Expecting the given 'properties' to be a dictionary, instead got: "
|
||||
"{}".format(type(properties)))
|
||||
|
||||
# set gravity
|
||||
if 'gravity' in properties:
|
||||
self.gravity = properties['gravity']
|
||||
|
||||
# set floor dynamics
|
||||
self.floor_dynamics = properties
|
||||
@@ -5,7 +5,7 @@ import importlib
|
||||
import inspect
|
||||
|
||||
# General robot class
|
||||
from .base import Object, MovableObject, ControllableObject
|
||||
from .base import Body, MovableBody, ControllableBody
|
||||
from .actuators import *
|
||||
from .sensors import *
|
||||
from .robot import Robot
|
||||
|
||||
+129
-48
@@ -1,9 +1,17 @@
|
||||
#!/usr/bin/env python
|
||||
"""Define the various basic objects that are present in the simulator/world.
|
||||
"""Define the various basic bodies / objects that are present in the simulator / world.
|
||||
|
||||
Dependencies:
|
||||
- `pyrobolearn.simulators`
|
||||
- `pyrobolearn.utils`
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import quaternion
|
||||
# import quaternion
|
||||
|
||||
from pyrobolearn.simulators import Simulator
|
||||
from pyrobolearn.utils.orientation import get_rpy_from_quaternion, get_matrix_from_quaternion
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
@@ -15,99 +23,172 @@ __email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class Object(object):
|
||||
r"""Object
|
||||
class Body(object):
|
||||
r"""Physical (Multi-)Body
|
||||
|
||||
Define an object in the simulator/world.
|
||||
Define a physical body in the simulator/world.
|
||||
"""
|
||||
|
||||
def __init__(self, simulator, object_id):
|
||||
def __init__(self, simulator, body_id, name=None):
|
||||
"""
|
||||
Initialize the Body.
|
||||
|
||||
Args:
|
||||
simulator (Simulator): simulator instance.
|
||||
body_id (int): unique body id returned by the simulator.
|
||||
name (str): name of the body.
|
||||
"""
|
||||
self.simulator = simulator
|
||||
self.id = body_id
|
||||
self.name = name
|
||||
self.joints = None
|
||||
|
||||
##############
|
||||
# Properties #
|
||||
##############
|
||||
|
||||
@property
|
||||
def simulator(self):
|
||||
"""Return the simulator."""
|
||||
return self.sim
|
||||
|
||||
@simulator.setter
|
||||
def simulator(self, simulator):
|
||||
"""Set the simulator."""
|
||||
if not isinstance(simulator, Simulator):
|
||||
raise TypeError("Expecting the given simulator to be an instance of `Simulator`, instead got: "
|
||||
"{}".format(type(simulator)))
|
||||
self.sim = simulator
|
||||
self.id = object_id
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Return the id."""
|
||||
return self._id
|
||||
|
||||
@id.setter
|
||||
def id(self, id_):
|
||||
"""Set the unique body id."""
|
||||
if not isinstance(id_, int):
|
||||
raise TypeError("Expecting the given simulator to be an integer, instead got: {}".format(type(id_)))
|
||||
self._id = id_
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.sim.getBodyInfo(self.id)
|
||||
"""Return the name of the body (or the base if not given)."""
|
||||
if self._name is None:
|
||||
return self.sim.get_body_info(self.id)
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, name):
|
||||
"""Set the name of the body."""
|
||||
if not isinstance(name, str):
|
||||
raise TypeError("Expecting the given name to be a string, instead got: {}".format(type(name)))
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def pose(self):
|
||||
"""Return the body pose."""
|
||||
return self.sim.get_base_pose(self.id)
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
return np.array(self.sim.getBasePositionAndOrientation(self.id)[0])
|
||||
"""Return the body position."""
|
||||
return self.sim.get_base_position(self.id)
|
||||
|
||||
@property
|
||||
def quaternion(self):
|
||||
quat = self.sim.getBasePositionAndOrientation(self.id)[1]
|
||||
return quaternion.quaternion(quat[3], *quat[:3])
|
||||
def orientation(self):
|
||||
"""Return the body orientation as a quaternion [x,y,z,w]."""
|
||||
return self.sim.get_base_orientation(self.id)
|
||||
|
||||
# alias
|
||||
orientation = quaternion
|
||||
quaternion = orientation
|
||||
|
||||
@property
|
||||
def rpy(self):
|
||||
quat = self.sim.getBasePositionAndOrientation(self.id)[1]
|
||||
y, p, r = self.sim.getEulerFromQuaternion(quat)
|
||||
return np.array([r, p, y])
|
||||
"""Return the orientation as the Roll-Pitch-Yaw angles."""
|
||||
return get_rpy_from_quaternion(self.orientation)
|
||||
|
||||
@property
|
||||
def rotation(self):
|
||||
quat = self.sim.getBasePositionAndOrientation(self.id)[1]
|
||||
rot = self.sim.getMatrixFromQuaternion(quat)
|
||||
return np.array(rot).reshape(3, 3)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
pos, quat = self.sim.getBasePositionAndOrientation(self.id)
|
||||
rpy = self.sim.getEulerFromQuaternion(quat)[::-1]
|
||||
# return np.array(pos), quaternion.quaternion(quat[3], *quat[:3])
|
||||
return np.array(pos+rpy)
|
||||
def rotation_matrix(self):
|
||||
"""Return the orientation as a rotation matrix."""
|
||||
return get_matrix_from_quaternion(self.orientation)
|
||||
|
||||
@property
|
||||
def linear_velocity(self):
|
||||
return np.array(self.sim.getBaseVelocity(self.id)[0])
|
||||
"""Return the linear velocity of the body's base."""
|
||||
return self.sim.get_base_linear_velocity(self.id)
|
||||
|
||||
@property
|
||||
def angular_velocity(self):
|
||||
return np.array(self.sim.getBaseVelocity(self.id)[1])
|
||||
"""Return the angular velocity of the body's base."""
|
||||
return self.sim.get_base_angular_velocity(self.id)
|
||||
|
||||
@property
|
||||
def velocity(self):
|
||||
lin, ang = self.sim.getBaseVelocity(self.id)
|
||||
return np.array(lin+ang)
|
||||
"""Return the linear and angular velocity of the body."""
|
||||
return self.sim.get_base_velocity(self.id)
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
return self.sim.getVisualShapeData(self.id)[0][-1]
|
||||
|
||||
# alias
|
||||
rgba_color = color
|
||||
return self.sim.get_visual_shape_data(self.id)[0][-1]
|
||||
|
||||
@property
|
||||
def mass(self):
|
||||
links = [-1] + list(range(self.sim.getNumJoints(self.id)))
|
||||
return np.sum([self.sim.getDynamicsInfo(self.id, linkId)[0] for linkId in links])
|
||||
"""Return the total mass of the body."""
|
||||
return self.sim.get_mass(self.id)
|
||||
|
||||
@property
|
||||
def dimensions(self):
|
||||
return np.array(self.sim.getVisualShapeData(self.id)[0][3])
|
||||
"""Return the dimensions of the body. Warnings: this should not be trusted too much..."""
|
||||
return np.array(self.sim.get_visual_shape_data(self.id)[0][3])
|
||||
|
||||
@property
|
||||
def num_joints(self):
|
||||
"""Return the total number of joints."""
|
||||
return self.sim.num_joints(self.id)
|
||||
|
||||
@property
|
||||
def num_links(self):
|
||||
"""Return the total number of links. This is the same as the number of joints."""
|
||||
return self.sim.num_links(self.id)
|
||||
|
||||
@property
|
||||
def num_actuated_joints(self):
|
||||
"""Return the total number of actuated joints. This property should be overwritten in the child class."""
|
||||
return self.sim.num_actuated_joints(self.id)
|
||||
|
||||
@property
|
||||
def actuated_joints(self):
|
||||
"""Return the total number of actuated joints."""
|
||||
if self.joints is None:
|
||||
self.joints = self.sim.get_actuated_joint_ids(self.id)
|
||||
return self.joints
|
||||
|
||||
@property
|
||||
def center_of_mass(self):
|
||||
"""Return the center of mass."""
|
||||
return self.sim.get_center_of_mass(self.id)
|
||||
|
||||
|
||||
class MovableObject(Object):
|
||||
r"""Movable Object
|
||||
class MovableBody(Body):
|
||||
r"""Movable Body
|
||||
|
||||
Define a movable object in the world.
|
||||
"""
|
||||
|
||||
def __init__(self, simulator, object_id):
|
||||
super(MovableObject, self).__init__(simulator, object_id)
|
||||
def __init__(self, simulator, object_id, name=None):
|
||||
super(MovableBody, self).__init__(simulator, object_id, name=name)
|
||||
|
||||
def move(self, new_position=None, new_orientation=None):
|
||||
pass
|
||||
# def move(self, position=None, orientation=None):
|
||||
# pass
|
||||
|
||||
|
||||
class ControllableObject(MovableObject):
|
||||
r"""Controllable Object
|
||||
class ControllableBody(MovableBody):
|
||||
r"""Controllable Body
|
||||
|
||||
Define a controllable object in the world.
|
||||
"""
|
||||
|
||||
def __init__(self, simulator, object_id):
|
||||
super(ControllableObject, self).__init__(simulator, object_id)
|
||||
def __init__(self, simulator, object_id, name=None):
|
||||
super(ControllableBody, self).__init__(simulator, object_id, name=name)
|
||||
|
||||
@@ -9,7 +9,7 @@ import sympy
|
||||
import sympy.physics.mechanics as mechanics
|
||||
|
||||
from pyrobolearn.robots.robot import Robot
|
||||
from pyrobolearn.utils.orientation import getSymbolicMatrixFromAxisAngle
|
||||
from pyrobolearn.utils.orientation import get_symbolic_matrix_from_axis_angle
|
||||
|
||||
|
||||
class CartPole(Robot):
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
# basic simulator
|
||||
from .simulator import Simulator
|
||||
|
||||
# bullet simulator
|
||||
from .bullet import Bullet
|
||||
|
||||
# PyBullet simulator
|
||||
import pybullet
|
||||
import pybullet_data
|
||||
|
||||
@@ -21,12 +21,13 @@ References:
|
||||
import time
|
||||
import numpy as np
|
||||
import quaternion
|
||||
from pyrobolearn.utils.converter import NumpyListConverter, QuaternionListConverter
|
||||
|
||||
import pybullet
|
||||
import pybullet_data
|
||||
from pybullet_envs.bullet.bullet_client import BulletClient
|
||||
|
||||
from simulator import Simulator
|
||||
from pyrobolearn.utils.converter import NumpyListConverter, QuaternionListConverter
|
||||
from pyrobolearn.simulators.simulator import Simulator
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
@@ -74,9 +75,11 @@ class Bullet(Simulator):
|
||||
Erwin Coumans and Yunfei Bai, 2017/2018
|
||||
"""
|
||||
|
||||
def __init__(self, render=True): # , converter=None):
|
||||
def __init__(self, render=True, **kwargs): # , converter=None):
|
||||
super(Bullet, self).__init__()
|
||||
|
||||
# parse the kwargs
|
||||
|
||||
# Connect to pybullet
|
||||
if render:
|
||||
self.sim = BulletClient(connection_mode=pybullet.GUI)
|
||||
@@ -84,6 +87,9 @@ class Bullet(Simulator):
|
||||
self.sim = BulletClient(connection_mode=pybullet.DIRECT)
|
||||
self.id = self.sim._client
|
||||
|
||||
# add additional search path when loading URDFs, SDFs, MJCFs, etc.
|
||||
self.sim.setAdditionalSearchPath(pybullet_data.getDataPath())
|
||||
|
||||
# Converters
|
||||
# if converter is None:
|
||||
self.conv = NumpyListConverter()
|
||||
@@ -133,12 +139,22 @@ class Bullet(Simulator):
|
||||
"""
|
||||
self.sim.resetSimulation()
|
||||
|
||||
def close(self):
|
||||
"""Close the simulator."""
|
||||
try:
|
||||
self.sim.disconnect(physicsClientId=self.id)
|
||||
except pybullet.error:
|
||||
pass
|
||||
|
||||
def step(self, sleep_time=0.):
|
||||
"""Perform a step in the simulator.
|
||||
|
||||
"stepSimulation will perform all the actions in a single forward dynamics simulation step such as collision
|
||||
detection, constraint solving and integration. The default timestep is 1/240 second, it can be changed using
|
||||
the setTimeStep or setPhysicsEngineParameter API." [1]
|
||||
|
||||
Args:
|
||||
sleep_time (float): time to sleep after performing one step in the simulation.
|
||||
"""
|
||||
self.sim.stepSimulation()
|
||||
time.sleep(sleep_time)
|
||||
@@ -170,7 +186,7 @@ class Bullet(Simulator):
|
||||
"""
|
||||
self.sim.setTimeStep(timeStep=time_step)
|
||||
|
||||
def set_real_time(self, flag=True):
|
||||
def set_real_time(self, enable=True):
|
||||
"""Enable/disable real time in the simulator.
|
||||
|
||||
"By default, the physics server will not step the simulation, unless you explicitly send a 'stepSimulation'
|
||||
@@ -185,9 +201,9 @@ class Bullet(Simulator):
|
||||
allows the physicsserver thread to add additional calls to stepSimulation." [1]
|
||||
|
||||
Args:
|
||||
flag (bool): If True, it will enable the real-time simulation. If False, it will disable it.
|
||||
enable (bool): If True, it will enable the real-time simulation. If False, it will disable it.
|
||||
"""
|
||||
self.sim.setRealTimeSimulation(enableRealTimeSimulation=int(flag))
|
||||
self.sim.setRealTimeSimulation(enableRealTimeSimulation=int(enable))
|
||||
|
||||
def pause(self):
|
||||
"""Pause the simulator if in real-time."""
|
||||
@@ -201,18 +217,25 @@ class Bullet(Simulator):
|
||||
"""Get the physics engine parameters.
|
||||
|
||||
Returns:
|
||||
dict: dictionary containing the following tags with their corresponding values: ['gravityAccelerationX',
|
||||
'useRealTimeSimulation', 'gravityAccelerationZ', 'numSolverIterations', 'gravityAccelerationY',
|
||||
'numSubSteps', 'fixedTimeStep']
|
||||
dict: dictionary containing the following tags with their corresponding values: ['gravity',
|
||||
'num_solver_iterations', 'use_real_time_simulation', 'num_sub_steps', 'fixed_time_step']
|
||||
"""
|
||||
return self.sim.getPhysicsEngineParameters()
|
||||
d = self.sim.getPhysicsEngineParameters()
|
||||
properties = dict()
|
||||
properties['gravity'] = np.array([d['gravityAccelerationX'], d['gravityAccelerationY'],
|
||||
d['gravityAccelerationZ']])
|
||||
properties['num_solver_iterations'] = d['numSolverIterations']
|
||||
properties['use_real_time_simulation'] = d['useRealTimeSimulation']
|
||||
properties['num_sub_steps'] = d['numSubSteps']
|
||||
properties['fixed_time_step'] = d['fixedTimeStep']
|
||||
return properties
|
||||
|
||||
def set_physics_properties(self, time_step=None, num_solver_iterations=None, use_split_impulse=None,
|
||||
split_impulse_penetration_threshold=None, num_sub_steps=None,
|
||||
collision_filter_mode=None, contact_breaking_threshold=None, max_num_cmd_per_1ms=None,
|
||||
enable_file_caching=None, restitution_velocity_threshold=None, erp=None,
|
||||
contact_erp=None, friction_erp=None, enable_cone_friction=None,
|
||||
deterministic_overlapping_pairs=None, solver_residual_threshold=None):
|
||||
deterministic_overlapping_pairs=None, solver_residual_threshold=None, **kwargs):
|
||||
"""Set the physics engine parameters.
|
||||
|
||||
Args:
|
||||
@@ -415,7 +438,7 @@ class Bullet(Simulator):
|
||||
|
||||
Args:
|
||||
plugin_id (int): unique plugin id.
|
||||
args (list): list of argument values to be interpreted by the plugin. One can be a string, while the
|
||||
*args (list): list of argument values to be interpreted by the plugin. One can be a string, while the
|
||||
others must be integers or float.
|
||||
"""
|
||||
kwargs = {}
|
||||
@@ -462,9 +485,9 @@ class Bullet(Simulator):
|
||||
|
||||
Args:
|
||||
filename (str): a relative or absolute path to the URDF file on the file system of the physics server.
|
||||
position (vec3): create the base of the object at the specified position in world space coordinates [X,Y,Z]
|
||||
position (vec3): create the base of the object at the specified position in world space coordinates [x,y,z]
|
||||
orientation (quat): create the base of the object at the specified orientation as world space quaternion
|
||||
[X,Y,Z,W]
|
||||
[x,y,z,w]
|
||||
use_maximal_coordinates (int): Experimental. By default, the joints in the URDF file are created using the
|
||||
reduced coordinate method: the joints are simulated using the Featherstone Articulated Body algorithm
|
||||
(btMultiBody in Bullet 2.x). The useMaximalCoordinates option will create a 6 degree of freedom rigid
|
||||
@@ -529,7 +552,7 @@ class Bullet(Simulator):
|
||||
return self.sim.loadMJCF(filename, globalScaling=scaling)
|
||||
|
||||
def load_mesh(self, filename, position, orientation=(0, 0, 0, 1), mass=1., scale=(1., 1., 1.),
|
||||
color=None, flags=None):
|
||||
color=None, flags=None, *args, **kwargs):
|
||||
"""
|
||||
Load a mesh in the world (only available in the simulator).
|
||||
|
||||
@@ -541,7 +564,7 @@ class Bullet(Simulator):
|
||||
If np.quaternion then it uses the convention (w,x,y,z). If float[4], it uses the convention (x,y,z,w)
|
||||
mass (float): mass of the mesh (in kg). If mass = 0, it won't move even if there is a collision.
|
||||
scale (float[3]): scale the mesh in the (x,y,z) directions
|
||||
color (int[4]): color of the mesh (by default: white and opaque)
|
||||
color (int[4], None): color of the mesh (by default: white and opaque)
|
||||
flags (int, None): if flag = `sim.GEOM_FORCE_CONCAVE_TRIMESH` (=1), this will create a concave static
|
||||
triangle mesh. This should not be used with dynamic/moving objects, only for static (mass=0) terrain.
|
||||
|
||||
@@ -648,9 +671,10 @@ class Bullet(Simulator):
|
||||
|
||||
def create_constraint(self, parent_body_id, parent_link_id, child_body_id, child_link_id, joint_type,
|
||||
joint_axis, parent_frame_position, child_frame_position,
|
||||
parent_frame_orientation=(0., 0., 0., 1.), child_frame_orientation=(0., 0., 0., 1.)):
|
||||
parent_frame_orientation=(0., 0., 0., 1.), child_frame_orientation=(0., 0., 0., 1.),
|
||||
*args, **kwargs):
|
||||
"""
|
||||
Create a constaint.
|
||||
Create a constraint.
|
||||
|
||||
"URDF, SDF and MJCF specify articulated bodies as a tree-structures without loops. The 'createConstraint'
|
||||
allows you to connect specific links of bodies to close those loops. In addition, you can create arbitrary
|
||||
@@ -697,7 +721,8 @@ class Bullet(Simulator):
|
||||
self.sim.removeConstraint(constraint_id)
|
||||
|
||||
def change_constraint(self, constraint_id, child_joint_pivot=None, child_frame_orientation=None, max_force=None,
|
||||
gear_ratio=None, gear_auxiliary_link=None, relative_position_target=None, erp=None):
|
||||
gear_ratio=None, gear_auxiliary_link=None, relative_position_target=None, erp=None, *args,
|
||||
**kwargs):
|
||||
"""
|
||||
Change the parameters of an existing constraint.
|
||||
|
||||
@@ -804,7 +829,11 @@ class Bullet(Simulator):
|
||||
return np.sum(self.get_link_masses(body_id, [-1] + list(range(self.num_links(body_id)))))
|
||||
|
||||
def get_base_mass(self, body_id):
|
||||
"""Return the base mass of the robot."""
|
||||
"""Return the base mass of the robot.
|
||||
|
||||
Args:
|
||||
body_id (int): unique object id.
|
||||
"""
|
||||
return self.get_link_masses(body_id, -1)
|
||||
|
||||
def get_base_name(self, body_id):
|
||||
@@ -991,7 +1020,7 @@ class Bullet(Simulator):
|
||||
self.sim.resetBaseVelocity(body_id, angularVelocity=angular_velocity)
|
||||
|
||||
def apply_external_force(self, body_id, link_id=-1, force=(0., 0., 0.), position=(0., 0., 0.),
|
||||
flags=pybullet.LINK_FRAME):
|
||||
frame=pybullet.LINK_FRAME):
|
||||
"""
|
||||
Apply the specified external force on the specified position on the body / link.
|
||||
|
||||
@@ -1006,10 +1035,10 @@ class Bullet(Simulator):
|
||||
force (np.float[3]): external force to be applied.
|
||||
position (np.float[3]): position on the link where the force is applied. See `flags` for coordinate
|
||||
systems.
|
||||
flags (int): Specify the coordinate system of force/position: either `pybullet.WORLD_FRAME` (=2) for
|
||||
frame (int): Specify the coordinate system of force/position: either `pybullet.WORLD_FRAME` (=2) for
|
||||
Cartesian world coordinates or `pybullet.LINK_FRAME` (=1) for local link coordinates.
|
||||
"""
|
||||
self.sim.applyExternalForce(body_id, link_id, force, position, flags)
|
||||
self.sim.applyExternalForce(body_id, link_id, force, position, frame)
|
||||
|
||||
def apply_external_torque(self, body_id, link_id=-1, torque=(0., 0., 0.)):
|
||||
"""
|
||||
@@ -1091,7 +1120,11 @@ class Bullet(Simulator):
|
||||
[15] np.float[4]: joint orientation in parent frame
|
||||
[16] int: parent link index, -1 for base
|
||||
"""
|
||||
return self.sim.getJointInfo(body_id, joint_id)
|
||||
info = self.sim.getJointInfo(body_id, joint_id)
|
||||
info[13] = np.array(info[13])
|
||||
info[14] = np.array(info[14])
|
||||
info[15] = np.array(info[15])
|
||||
return info
|
||||
|
||||
def get_joint_state(self, body_id, joint_id):
|
||||
"""
|
||||
@@ -1227,7 +1260,8 @@ class Bullet(Simulator):
|
||||
joint_ids (list of int): list of joint id.
|
||||
control_mode (int): POSITION_CONTROL (=2) (which is in fact CONTROL_MODE_POSITION_VELOCITY_PD),
|
||||
VELOCITY_CONTROL (=0), TORQUE_CONTROL (=1) and PD_CONTROL (=3).
|
||||
positions (list of float): list of target joint positions (used in POSITION_CONTROL) the target value is target position of the joint.
|
||||
positions (list of float): list of target joint positions (used in POSITION_CONTROL) the target value is
|
||||
target position of the joint.
|
||||
velocities (list of float): list of target joint velocities (used in PD_CONTROL, VELOCITY_CONTROL and
|
||||
POSITION_CONTROL).
|
||||
forces (list of float): list of forces. In POSITION_CONTROL and VELOCITY_CONTROL, these are the maximum
|
||||
@@ -1663,7 +1697,7 @@ class Bullet(Simulator):
|
||||
velocities (None, float, np.float[N]): desired velocity, or list of desired velocities [rad/s]
|
||||
kps (None, float, np.float[N]): position gain(s)
|
||||
kds (None, float, np.float[N]): velocity gain(s)
|
||||
forces (float): maximum motor force(s)/torque(s) used to reach the target values.
|
||||
forces (None, float, np.float[N]): maximum motor force(s)/torque(s) used to reach the target values.
|
||||
"""
|
||||
if isinstance(joint_ids, int):
|
||||
self.set_joint_motor_control(body_id, joint_ids, control_mode=pybullet.POSITION_CONTROL, position=positions,
|
||||
@@ -1699,7 +1733,7 @@ class Bullet(Simulator):
|
||||
body_id (int): unique body id.
|
||||
joint_ids (int, list of int): joint id, or list of joint ids.
|
||||
velocities (float, np.float[N]): desired velocity, or list of desired velocities [rad/s]
|
||||
max_force (bool, float, float[N]): maximum motor forces/torques
|
||||
max_force (None, float, np.float[N]): maximum motor forces/torques
|
||||
"""
|
||||
if isinstance(joint_ids, int):
|
||||
if max_force is None:
|
||||
@@ -1820,7 +1854,7 @@ class Bullet(Simulator):
|
||||
Args:
|
||||
body_id (int): unique body id.
|
||||
joint_ids (int, list of int): joint id, or list of joint ids.
|
||||
torque (float, list of float): desired torque(s) to apply to the joint(s) [N].
|
||||
torques (float, list of float): desired torque(s) to apply to the joint(s) [N].
|
||||
"""
|
||||
if isinstance(joint_ids, int):
|
||||
self.sim.setJointMotorControl2(body_id, joint_ids, self.sim.TORQUE_CONTROL, force=torques)
|
||||
@@ -2077,7 +2111,7 @@ class Bullet(Simulator):
|
||||
* orthographic projection
|
||||
* perspective projection
|
||||
|
||||
For the perspective projection, see `computeProjectionMatrixFOV(self)
|
||||
For the perspective projection, see `computeProjectionMatrixFOV(self)`.
|
||||
|
||||
Args:
|
||||
left (float): left screen (canvas) coordinate
|
||||
@@ -2989,50 +3023,50 @@ class Bullet(Simulator):
|
||||
|
||||
def calculate_forward_dynamics(self, body_id, q, dq, torques):
|
||||
r"""
|
||||
Given the specified joint positions :math:`q` and velocities :math:`\dot{q}`, and joint torques :math:`\tau`,
|
||||
it computes the joint accelerations :math:`\ddot{q}`. That is, :math:`\ddot{q} = FD(model, q, \dot{q}, \tau)`.
|
||||
Given the specified joint positions :math:`q` and velocities :math:`\dot{q}`, and joint torques :math:`\tau`,
|
||||
it computes the joint accelerations :math:`\ddot{q}`. That is, :math:`\ddot{q} = FD(model, q, \dot{q}, \tau)`.
|
||||
|
||||
Specifically, it uses the rigid-body equation of motion in joint space given by (see [1]):
|
||||
Specifically, it uses the rigid-body equation of motion in joint space given by (see [1]):
|
||||
|
||||
.. math:: \ddot{q} = H(q)^{-1} (\tau - C(q,\dot{q}))
|
||||
.. math:: \ddot{q} = H(q)^{-1} (\tau - C(q,\dot{q}))
|
||||
|
||||
where :math:`\tau` is the vector of applied torques, :math:`H(q)` is the inertia matrix, and
|
||||
:math:`C(q,\dot{q}) \dot{q}` is the vector accounting for Coriolis, centrifugal forces, gravity, and any
|
||||
other forces acting on the system except the applied torques :math:`\tau`.
|
||||
where :math:`\tau` is the vector of applied torques, :math:`H(q)` is the inertia matrix, and
|
||||
:math:`C(q,\dot{q}) \dot{q}` is the vector accounting for Coriolis, centrifugal forces, gravity, and any
|
||||
other forces acting on the system except the applied torques :math:`\tau`.
|
||||
|
||||
Normally, a more popular form of this equation of motion (in joint space) is given by:
|
||||
Normally, a more popular form of this equation of motion (in joint space) is given by:
|
||||
|
||||
.. math:: H(q) \ddot{q} + S(q,\dot{q}) \dot{q} + g(q) = \tau + J^T(q) F
|
||||
.. math:: H(q) \ddot{q} + S(q,\dot{q}) \dot{q} + g(q) = \tau + J^T(q) F
|
||||
|
||||
which is the same as the first one with :math:`C = S\dot{q} + g(q) - J^T(q) F`. However, this last formulation
|
||||
is useful to understand what happens when we set some variables to 0.
|
||||
Assuming that there are no forces acting on the system, and giving desired joint torques of 0, this
|
||||
method will return :math:`\ddot{q} = - H(q)^{-1} (S(q,\dot{q}) \dot{q} + g(q))`. If in addition
|
||||
the joint velocities are also 0, it will return :math:`\ddot{q} = - H(q)^{-1} g(q)` which are
|
||||
the accelerations due to gravity.
|
||||
which is the same as the first one with :math:`C = S\dot{q} + g(q) - J^T(q) F`. However, this last formulation
|
||||
is useful to understand what happens when we set some variables to 0.
|
||||
Assuming that there are no forces acting on the system, and giving desired joint torques of 0, this
|
||||
method will return :math:`\ddot{q} = - H(q)^{-1} (S(q,\dot{q}) \dot{q} + g(q))`. If in addition
|
||||
the joint velocities are also 0, it will return :math:`\ddot{q} = - H(q)^{-1} g(q)` which are
|
||||
the accelerations due to gravity.
|
||||
|
||||
For inverse dynamics, which computes the joint torques given the joint positions, velocities, and
|
||||
accelerations (that is, :math:`\tau = ID(model, q, \dot{q}, \ddot{q})`, this can be computed using
|
||||
:math:`\tau = H(q)\ddot{q} + C(q,\dot{q})`. For more information about different
|
||||
control schemes (position, force, impedance control and others), or about the formulation of the equation
|
||||
of motion in task/operational space (instead of joint space), check the references [1-4].
|
||||
For inverse dynamics, which computes the joint torques given the joint positions, velocities, and
|
||||
accelerations (that is, :math:`\tau = ID(model, q, \dot{q}, \ddot{q})`, this can be computed using
|
||||
:math:`\tau = H(q)\ddot{q} + C(q,\dot{q})`. For more information about different
|
||||
control schemes (position, force, impedance control and others), or about the formulation of the equation
|
||||
of motion in task/operational space (instead of joint space), check the references [1-4].
|
||||
|
||||
Args:
|
||||
body_id (int): unique body id.
|
||||
q (np.float[N]): joint positions
|
||||
dq (np.float[N]): joint velocities
|
||||
torques (np.float[N]): desired joint torques
|
||||
Args:
|
||||
body_id (int): unique body id.
|
||||
q (np.float[N]): joint positions
|
||||
dq (np.float[N]): joint velocities
|
||||
torques (np.float[N]): desired joint torques
|
||||
|
||||
Returns:
|
||||
float[N]: joint accelerations computed using the rigid-body equation of motion
|
||||
Returns:
|
||||
float[N]: joint accelerations computed using the rigid-body equation of motion
|
||||
|
||||
References:
|
||||
[1] "Rigid Body Dynamics Algorithms", Featherstone, 2008, chap1.1
|
||||
[2] "Robotics: Modelling, Planning and Control", Siciliano et al., 2010
|
||||
[3] "Springer Handbook of Robotics", Siciliano et al., 2008
|
||||
[4] Lecture on "Impedance Control" by Prof. De Luca, Universita di Roma,
|
||||
http://www.diag.uniroma1.it/~deluca/rob2_en/15_ImpedanceControl.pdf
|
||||
"""
|
||||
References:
|
||||
[1] "Rigid Body Dynamics Algorithms", Featherstone, 2008, chap1.1
|
||||
[2] "Robotics: Modelling, Planning and Control", Siciliano et al., 2010
|
||||
[3] "Springer Handbook of Robotics", Siciliano et al., 2008
|
||||
[4] Lecture on "Impedance Control" by Prof. De Luca, Universita di Roma,
|
||||
http://www.diag.uniroma1.it/~deluca/rob2_en/15_ImpedanceControl.pdf
|
||||
"""
|
||||
# convert numpy arrays to lists
|
||||
if isinstance(q, np.ndarray):
|
||||
q = q.ravel().tolist()
|
||||
|
||||
+1975
-91
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ from .state import State
|
||||
from .basic_states import *
|
||||
|
||||
# import object states
|
||||
from .object_states import *
|
||||
from .body_states import *
|
||||
|
||||
# import time/count states
|
||||
from .time_states import *
|
||||
|
||||
@@ -7,8 +7,8 @@ This includes notably the joint positions, velocities, and force/torque states.
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from pyrobolearn.states.state import State
|
||||
from pyrobolearn.worlds.world import World
|
||||
from pyrobolearn.robots import Object
|
||||
from pyrobolearn.worlds import World
|
||||
from pyrobolearn.robots import Body
|
||||
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
@@ -21,15 +21,15 @@ __email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
|
||||
class ObjectState(State):
|
||||
"""Object state (abstract)
|
||||
class BodyState(State):
|
||||
"""Body state (abstract)
|
||||
"""
|
||||
__metaclass__ = ABCMeta
|
||||
|
||||
def __init__(self, obj, world=None):
|
||||
super(ObjectState, self).__init__()
|
||||
if not isinstance(obj, (Object, int)):
|
||||
raise TypeError("Expecting an instance of Object, or an identifier from the simulator/world.")
|
||||
super(BodyState, self).__init__()
|
||||
if not isinstance(obj, (Body, int)):
|
||||
raise TypeError("Expecting an instance of Body, or an identifier from the simulator/world.")
|
||||
if isinstance(obj, int):
|
||||
if not isinstance(world, World):
|
||||
# try to look for the world in global variables
|
||||
@@ -37,7 +37,7 @@ class ObjectState(State):
|
||||
world = globals()['world']
|
||||
else:
|
||||
raise ValueError("When giving the object identifier, the world need to be given as well.")
|
||||
obj = Object(world.getSimulator(), obj)
|
||||
obj = Body(world.simulator, obj)
|
||||
self.obj = obj
|
||||
|
||||
@abstractmethod
|
||||
@@ -45,7 +45,7 @@ class ObjectState(State):
|
||||
pass
|
||||
|
||||
|
||||
class PositionState(ObjectState):
|
||||
class PositionState(BodyState):
|
||||
"""Position of an object.
|
||||
"""
|
||||
def __init__(self, obj, world=None):
|
||||
@@ -56,7 +56,7 @@ class PositionState(ObjectState):
|
||||
self.data = self.obj.position
|
||||
|
||||
|
||||
class OrientationState(ObjectState):
|
||||
class OrientationState(BodyState):
|
||||
"""Orientation of an object.
|
||||
"""
|
||||
def __init__(self, obj, world=None):
|
||||
@@ -67,7 +67,7 @@ class OrientationState(ObjectState):
|
||||
self.data = self.obj.orientation
|
||||
|
||||
|
||||
class VelocityState(ObjectState):
|
||||
class VelocityState(BodyState):
|
||||
"""Velocity of an object.
|
||||
"""
|
||||
def __init__(self, obj, world=None):
|
||||
@@ -1,5 +1,11 @@
|
||||
# utils code to transform orientation expressed in different forms
|
||||
# This includes rotation matrices, euler angles (RPY), axis-angle, and quaternions
|
||||
#!/usr/bin/env python
|
||||
"""Provide utils code to transform orientation expressed in different forms
|
||||
|
||||
This includes rotation matrices, euler angles (RPY), axis-angle, and quaternions.
|
||||
|
||||
References:
|
||||
[1] "Robotics: Modelling, Planning and Control", Siciliano et al., 2010, chapter 2
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import quaternion
|
||||
@@ -8,8 +14,26 @@ from collections import Iterable
|
||||
|
||||
from pyrobolearn.utils.converter import QuaternionNumpyConverter
|
||||
|
||||
__author__ = "Brian Delhaisse"
|
||||
__copyright__ = "Copyright 2018, PyRoboLearn"
|
||||
__credits__ = ["Brian Delhaisse"]
|
||||
__license__ = "MIT"
|
||||
__version__ = "1.0.0"
|
||||
__maintainer__ = "Brian Delhaisse"
|
||||
__email__ = "briandelhaisse@gmail.com"
|
||||
__status__ = "Development"
|
||||
|
||||
def getMatrixFromAxisAngle(axis, angle):
|
||||
|
||||
def get_matrix_from_axis_angle(axis, angle):
|
||||
"""Return the rotation matrix from the specified axis and angle.
|
||||
|
||||
Args:
|
||||
axis (np.float[3], list of 3 float): 3d axis vector.
|
||||
angle (float): angle.
|
||||
|
||||
Returns:
|
||||
np.float[3,3]: rotation matrix.
|
||||
"""
|
||||
x, y, z = axis
|
||||
a = angle
|
||||
c, s = np.cos(a), np.sin(a)
|
||||
@@ -20,7 +44,16 @@ def getMatrixFromAxisAngle(axis, angle):
|
||||
return R
|
||||
|
||||
|
||||
def getSymbolicMatrixFromAxisAngle(axis, angle):
|
||||
def get_symbolic_matrix_from_axis_angle(axis, angle):
|
||||
"""Return the symbolic rotation matrix from the specified axis and angle.
|
||||
|
||||
Args:
|
||||
axis (np.float[3], list of 3 float, list of 3 sympy.Symbol): 3d axis vector.
|
||||
angle (float, sympy.Symbol): angle.
|
||||
|
||||
Returns:
|
||||
np.float[3,3]: rotation matrix.
|
||||
"""
|
||||
x, y, z = axis
|
||||
a = angle
|
||||
c, s = sympy.cos(a), sympy.sin(a)
|
||||
@@ -31,19 +64,49 @@ def getSymbolicMatrixFromAxisAngle(axis, angle):
|
||||
return R
|
||||
|
||||
|
||||
def getAxisAngleFromMatrix(R):
|
||||
def get_axis_angle_from_matrix(R):
|
||||
"""Return the associated axis and angle from the specified rotation matrix.
|
||||
|
||||
Args:
|
||||
R (np.float[3,3]): 3-by-3 rotation matrix.
|
||||
|
||||
Returns:
|
||||
float: angle.
|
||||
np.float[3]: 3d axis vector.
|
||||
"""
|
||||
angle = np.arccos((R[0, 0] + R[1, 1] + R[2, 2] - 1) / 2.)
|
||||
axis = 1. / (2. * np.sin(angle)) * np.array([R[2, 1] - R[1, 2], R[0, 2] - R[2, 0], R[1, 0] - R[0, 1]])
|
||||
return angle, axis
|
||||
|
||||
|
||||
def getSymbolicAxisAngleFromMatrix(R):
|
||||
def get_symbolic_axis_angle_from_matrix(R):
|
||||
"""Return the symbolic axis and angle from the specified rotation matrix.
|
||||
|
||||
Args:
|
||||
R (np.array of sympy.Symbol): 3-by-3 rotation matrix.
|
||||
|
||||
Returns:
|
||||
sympy.Symbol: angle.
|
||||
np.array of 3 sympy.Symbol: 3d axis vector.
|
||||
"""
|
||||
angle = sympy.acos((R[0, 0] + R[1, 1] + R[2, 2] - 1) / 2.)
|
||||
axis = 1. / (2. * sympy.sin(angle)) * np.array([R[2, 1] - R[1, 2], R[0, 2] - R[2, 0], R[1, 0] - R[0, 1]])
|
||||
return angle, axis
|
||||
|
||||
|
||||
def getQuaternionFromAxisAngle(axis, angle, convert_to_quat=False, convention='xyzw'):
|
||||
def get_quaternion_from_axis_angle(axis, angle, convert_to_quat=False, convention='xyzw'):
|
||||
"""Get the quaternion associated from the axis/angle representation.
|
||||
|
||||
Args:
|
||||
axis (np.float[3]): 3d axis vector.
|
||||
angle (float): angle.
|
||||
convert_to_quat (bool): If True, it will return an instance of `quaternion.quaternion`.
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.float[4], quaternion.quaternion: quaternion.
|
||||
"""
|
||||
w = np.cos(angle / 2.)
|
||||
x, y, z = np.sin(angle / 2.) * axis
|
||||
if convert_to_quat:
|
||||
@@ -57,7 +120,18 @@ def getQuaternionFromAxisAngle(axis, angle, convert_to_quat=False, convention='x
|
||||
raise NotImplementedError("Asking for a convention that has not been implemented")
|
||||
|
||||
|
||||
def getSymbolicQuaternionFromAxisAngle(axis, angle, convention='xyzw'):
|
||||
def get_symbolic_quaternion_from_axis_angle(axis, angle, convention='xyzw'):
|
||||
"""Get the symbolic quaternion associated from the axis/angle representation.
|
||||
|
||||
Args:
|
||||
axis (np.float[3], np.array of 3 sympy.Symbol): 3d axis vector.
|
||||
angle (float, sympy.Symbol): angle.
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.float[4]: symbolic quaternion.
|
||||
"""
|
||||
w = sympy.cos(angle / 2.)
|
||||
x, y, z = sympy.sin(angle / 2.) * axis
|
||||
if convention == 'xyzw':
|
||||
@@ -68,21 +142,55 @@ def getSymbolicQuaternionFromAxisAngle(axis, angle, convention='xyzw'):
|
||||
raise NotImplementedError("Asking for a convention that has not been implemented")
|
||||
|
||||
|
||||
def getRPYFromMatrix(R):
|
||||
r = np.arctan2(R[1, 0], R[0, 0])
|
||||
p = np.arctan2(-R[2, 0], np.sqrt(R[2, 1]**2 + R[2, 2]**2))
|
||||
y = np.arctan2(R[2, 1], R[2, 2])
|
||||
def get_rpy_from_matrix(R):
|
||||
"""Get the Roll-Pitch-Yaw angle values from the given rotation matrix.
|
||||
|
||||
Args:
|
||||
R (np.float[3,3]): 3-by-3 rotation matrix.
|
||||
|
||||
Returns:
|
||||
np.float[3]: roll-pitch-yaw angle values.
|
||||
"""
|
||||
# r = np.arctan2(R[1, 0], R[0, 0])
|
||||
# p = np.arctan2(-R[2, 0], np.sqrt(R[2, 1]**2 + R[2, 2]**2))
|
||||
# y = np.arctan2(R[2, 1], R[2, 2])
|
||||
|
||||
r = np.arctan2(R[2, 1], R[2, 2])
|
||||
p = np.arctan2(-R[2, 0], np.sqrt(R[2, 1] ** 2 + R[2, 2] ** 2))
|
||||
y = np.arctan2(R[1, 0], R[0, 0])
|
||||
|
||||
return np.array([r, p, y])
|
||||
|
||||
|
||||
def getSymbolicRPYFromMatrix(R):
|
||||
r = sympy.atan2(R[1, 0], R[0, 0])
|
||||
def get_symbolic_rpy_from_matrix(R):
|
||||
"""Get the symbolic Roll-Pitch-Yaw angles from the given rotation matrix.
|
||||
|
||||
Args:
|
||||
R (np.float[3,3], np.array of sympy.Symbol): symbolic 3-by-3 rotation matrix.
|
||||
|
||||
Returns:
|
||||
np.array of 3 sympy.Symbol: symbolic roll-pitch-yaw angles.
|
||||
"""
|
||||
# r = sympy.atan2(R[1, 0], R[0, 0])
|
||||
# p = sympy.atan2(-R[2, 0], sympy.sqrt(R[2, 1] ** 2 + R[2, 2] ** 2))
|
||||
# y = sympy.atan2(R[2, 1], R[2, 2])
|
||||
|
||||
r = sympy.atan2(R[2, 1], R[2, 2])
|
||||
p = sympy.atan2(-R[2, 0], sympy.sqrt(R[2, 1] ** 2 + R[2, 2] ** 2))
|
||||
y = sympy.atan2(R[2, 1], R[2, 2])
|
||||
y = sympy.atan2(R[1, 0], R[0, 0])
|
||||
|
||||
return np.array([r, p, y])
|
||||
|
||||
|
||||
def getMatrixFromRPY(rpy):
|
||||
def get_matrix_from_rpy(rpy):
|
||||
"""Get rotation matrix from the given Roll-Pitch-Yaw angles.
|
||||
|
||||
Args:
|
||||
rpy (np.float[3]): roll-pitch-yaw angles
|
||||
|
||||
Returns:
|
||||
np.float[3,3]: rotation matrix.
|
||||
"""
|
||||
cr, cp, cy = [np.cos(i) for i in rpy]
|
||||
sr, sp, sy = [np.sin(i) for i in rpy]
|
||||
R = np.array([[cy*cp, cy*sp*sr - sy*cr, cy*sp*cr + sy*sr],
|
||||
@@ -91,7 +199,16 @@ def getMatrixFromRPY(rpy):
|
||||
return R
|
||||
|
||||
|
||||
def getSymbolicMatrixFromRPY(rpy):
|
||||
def get_symbolic_matrix_from_rpy(rpy):
|
||||
"""
|
||||
Get the symbolic rotation matrix from the given Roll-Pitch-Yaw angles.
|
||||
|
||||
Args:
|
||||
rpy (np.float[3], np.array of 3 sympy.Symbol): roll-pitch-yaw angles.
|
||||
|
||||
Returns:
|
||||
3-by-3 np.array of sympy.Symbol: symbolic rotation matrix
|
||||
"""
|
||||
cr, cp, cy = [sympy.cos(i) for i in rpy]
|
||||
sr, sp, sy = [sympy.sin(i) for i in rpy]
|
||||
R = np.array([[cy * cp, cy * sp * sr - sy * cr, cy * sp * cr + sy * sr],
|
||||
@@ -100,7 +217,19 @@ def getSymbolicMatrixFromRPY(rpy):
|
||||
return R
|
||||
|
||||
|
||||
def getQuaternionFromMatrix(R, convert_to_quat=False, convention='xyzw'):
|
||||
def get_quaternion_from_matrix(R, convert_to_quat=False, convention='xyzw'):
|
||||
"""
|
||||
Get the quaternion from the given rotation matrix.
|
||||
|
||||
Args:
|
||||
R (np.float[3,3]): rotation matrix.
|
||||
convert_to_quat (bool): If True, it will return an instance of `quaternion.quaternion`.
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.float[4], quaternion.quaternion: quaternion
|
||||
"""
|
||||
w = 1./2 * np.sqrt(R[0, 0] + R[1, 1] + R[2, 2] + 1)
|
||||
x, y, z = 1./2 * np.array([np.sign(R[2, 1] - R[1, 2]) * np.sqrt(R[0, 0] - R[1, 1] - R[2, 2] + 1),
|
||||
np.sign(R[0, 2] - R[2, 0]) * np.sqrt(R[1, 1] - R[2, 2] - R[0, 0] + 1),
|
||||
@@ -116,7 +245,18 @@ def getQuaternionFromMatrix(R, convert_to_quat=False, convention='xyzw'):
|
||||
raise NotImplementedError("Asking for a convention that has not been implemented")
|
||||
|
||||
|
||||
def getSymbolicQuaternionFromMatrix(R, convention='xyzw'):
|
||||
def get_symbolic_quaternion_from_matrix(R, convention='xyzw'):
|
||||
"""
|
||||
Get the symbolic quaternion from the given rotation matrix.
|
||||
|
||||
Args:
|
||||
R (3-by-3 np.array of sympy.Symbol, np.float[3,3]): (symbolic) rotation matrix
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.array of 4 sympy.Symbol: symbolic quaternion.
|
||||
"""
|
||||
w = 1. / 2 * sympy.sqrt(R[0, 0] + R[1, 1] + R[2, 2] + 1)
|
||||
x, y, z = 1. / 2 * np.array([sympy.sign(R[2, 1] - R[1, 2]) * sympy.sqrt(R[0, 0] - R[1, 1] - R[2, 2] + 1),
|
||||
sympy.sign(R[0, 2] - R[2, 0]) * sympy.sqrt(R[1, 1] - R[2, 2] - R[0, 0] + 1),
|
||||
@@ -129,7 +269,18 @@ def getSymbolicQuaternionFromMatrix(R, convention='xyzw'):
|
||||
raise NotImplementedError("Asking for a convention that has not been implemented")
|
||||
|
||||
|
||||
def getMatrixFromQuaternion(q, convention='xyzw'):
|
||||
def get_matrix_from_quaternion(q, convention='xyzw'):
|
||||
"""
|
||||
Get rotation matrix from the given quaternion.
|
||||
|
||||
Args:
|
||||
q (np.float[4], quaternion.quaternion): quaternion
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.float[3,3]: rotation matrix.
|
||||
"""
|
||||
if isinstance(q, quaternion.quaternion):
|
||||
x, y, z, w = q.x, q.y, q.z, q.w
|
||||
elif isinstance(q, Iterable):
|
||||
@@ -147,11 +298,146 @@ def getMatrixFromQuaternion(q, convention='xyzw'):
|
||||
return R
|
||||
|
||||
|
||||
def getSymbolicMatrixFromQuaternion(q, convention='xyzw'):
|
||||
return getMatrixFromQuaternion(q, convention=convention)
|
||||
def get_symbolic_matrix_from_quaternion(q, convention='xyzw'):
|
||||
"""
|
||||
Get symbolic rotation matrix from the given quaternion.
|
||||
|
||||
Args:
|
||||
q (np.array of 4 sympy.Symbol, np.float[4]): (symbolic) quaternion.
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
3-by-3 np.array of sympy.Symbol: symbolic rotation matrix.
|
||||
"""
|
||||
return get_matrix_from_quaternion(q, convention=convention)
|
||||
|
||||
|
||||
def skew(vector):
|
||||
def get_rpy_from_quaternion(q, convention='xyzw'):
|
||||
"""
|
||||
Get the Roll-Pitch-Yaw angle from the given quaternion.
|
||||
|
||||
Args:
|
||||
q (np.float[4], quaternion.quaternion): quaternion
|
||||
convention: convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.float[3]: roll-pitch-yaw angles.
|
||||
"""
|
||||
if isinstance(q, quaternion.quaternion):
|
||||
x, y, z, w = q.x, q.y, q.z, q.w
|
||||
elif isinstance(q, Iterable):
|
||||
if convention == 'xyzw':
|
||||
x, y, z, w = q
|
||||
elif convention == 'wxyz':
|
||||
w, x, y, z = q
|
||||
else:
|
||||
raise NotImplementedError("Asking for a convention that has not been implemented")
|
||||
else:
|
||||
raise TypeError
|
||||
roll = np.arctan2(2*(w*x + y*z), 1 - 2 * (x**2 + y**2))
|
||||
pitch = np.arcsin(2 * (w*y - z*x))
|
||||
yaw = np.arctan2(2 * (w*z + x*y), 1 - 2 * (y**2 + z**2))
|
||||
return np.array([roll, pitch, yaw])
|
||||
|
||||
|
||||
def get_symbolic_rpy_from_quaternion(q, convention='xyzw'):
|
||||
"""
|
||||
Get the symbolic Roll-Pitch-Yaw angle from the given quaternion.
|
||||
|
||||
Args:
|
||||
q (np.float[4], np.array of 4 sympy.Symbol): quaternion
|
||||
convention: convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.array of 3 sympy.Symbol: symbolic roll-pitch-yaw angles.
|
||||
"""
|
||||
if isinstance(q, quaternion.quaternion):
|
||||
x, y, z, w = q.x, q.y, q.z, q.w
|
||||
elif isinstance(q, Iterable):
|
||||
if convention == 'xyzw':
|
||||
x, y, z, w = q
|
||||
elif convention == 'wxyz':
|
||||
w, x, y, z = q
|
||||
else:
|
||||
raise NotImplementedError("Asking for a convention that has not been implemented")
|
||||
else:
|
||||
raise TypeError
|
||||
|
||||
roll = sympy.atan2(2*(w*x + y*z), 1 - 2 * (x**2 + y**2))
|
||||
pitch = sympy.asin(2 * (w*y - z*x))
|
||||
yaw = sympy.atan2(2 * (w*z + x*y), 1 - 2 * (y**2 + z**2))
|
||||
|
||||
return np.array([roll, pitch, yaw])
|
||||
|
||||
|
||||
def get_quaternion_from_rpy(rpy, convert_to_quat=False, convention='xyzw'):
|
||||
"""
|
||||
Get quaternion from Roll-Pitch-Yaw angle.
|
||||
|
||||
Args:
|
||||
rpy (np.float[3]): roll-pitch-yaw angles
|
||||
convert_to_quat (bool): If True, it will return an instance of `quaternion.quaternion`.
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.float[4], quaternion.quaternion: quaternion
|
||||
"""
|
||||
r, p, y = rpy
|
||||
cr, sr = np.cos(r/2.), np.sin(r/2.)
|
||||
cp, sp = np.cos(p/2.), np.sin(p/2.)
|
||||
cy, sy = np.cos(y/2.), np.sin(y/2.)
|
||||
|
||||
w = cr * cp * cy + sr * sp * sy
|
||||
x = sr * cp * cy - cr * sp * sy
|
||||
y = cr * sp * cy + sr * cp * sy
|
||||
z = cr * cp * sy - sr * sp * cy
|
||||
|
||||
if convert_to_quat:
|
||||
return quaternion.quaternion(w, x, y, z)
|
||||
else:
|
||||
if convention == 'xyzw':
|
||||
return np.array([x, y, z, w])
|
||||
elif convention == 'wxyz':
|
||||
return np.array([w, x, y, z])
|
||||
else:
|
||||
raise NotImplementedError("Asking for a convention that has not been implemented")
|
||||
|
||||
|
||||
def get_symbolic_quaternion_from_rpy(rpy, convention='xyzw'):
|
||||
"""
|
||||
Get symbolic quaternion from Roll-Pitch-Yaw angle.
|
||||
|
||||
Args:
|
||||
rpy (np.float[3], np.array of 3 sympy.Symbol): (symbolic) roll-pitch-yaw angles
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.array of 4 sympy.Symbol: symbolic quaternion
|
||||
"""
|
||||
r, p, y = rpy
|
||||
cr, sr = sympy.cos(r/2.), sympy.sin(r/2.)
|
||||
cp, sp = sympy.cos(p/2.), sympy.sin(p/2.)
|
||||
cy, sy = sympy.cos(y/2.), sympy.sin(y/2.)
|
||||
|
||||
w = cr * cp * cy + sr * sp * sy
|
||||
x = sr * cp * cy - cr * sp * sy
|
||||
y = cr * sp * cy + sr * cp * sy
|
||||
z = cr * cp * sy - sr * sp * cy
|
||||
|
||||
if convention == 'xyzw':
|
||||
return np.array([x, y, z, w])
|
||||
elif convention == 'wxyz':
|
||||
return np.array([w, x, y, z])
|
||||
else:
|
||||
raise NotImplementedError("Asking for a convention that has not been implemented")
|
||||
|
||||
|
||||
def skew_matrix(vector):
|
||||
r"""
|
||||
Return the skew-symmetric matrix of the given vector, which allows to represents the cross product between the
|
||||
given vector and another vector, as the multiplication of the returned skew-symmetric matrix with the other
|
||||
@@ -184,7 +470,7 @@ def skew(vector):
|
||||
[-y, x, 0.]])
|
||||
|
||||
|
||||
def RotX(angle):
|
||||
def rotation_matrix_x(angle):
|
||||
"""
|
||||
Return the rotation matrix around the x-axis by the given angle.
|
||||
|
||||
@@ -200,7 +486,7 @@ def RotX(angle):
|
||||
[0., s, c]])
|
||||
|
||||
|
||||
def RotY(angle):
|
||||
def rotation_matrix_y(angle):
|
||||
"""
|
||||
Return the rotation matrix around the y-axis by the given angle.
|
||||
|
||||
@@ -216,7 +502,7 @@ def RotY(angle):
|
||||
[-s, 0, c]])
|
||||
|
||||
|
||||
def RotZ(angle):
|
||||
def rotation_matrix_z(angle):
|
||||
"""
|
||||
Return the rotation matrix around the z-axis by the given angle.
|
||||
|
||||
@@ -239,7 +525,17 @@ def RotZ(angle):
|
||||
quat_converter = QuaternionNumpyConverter(convention=1)
|
||||
|
||||
|
||||
def getQuaternionInverse(q, convention='xyzw'):
|
||||
def get_quaternion_inverse(q, convention='xyzw'):
|
||||
"""Return the inverse of the given quaternion.
|
||||
|
||||
Args:
|
||||
q (np.float[4], quaternion.quaternion): quaternion.
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.float[4], quaternion.quaternion: quaternion inverse.
|
||||
"""
|
||||
if isinstance(q, quaternion.quaternion):
|
||||
return q.inverse()
|
||||
elif isinstance(q, Iterable):
|
||||
@@ -255,7 +551,18 @@ def getQuaternionInverse(q, convention='xyzw'):
|
||||
raise TypeError
|
||||
|
||||
|
||||
def getQuaternionProduct(q1, q2, convention='xyzw'):
|
||||
def get_quaternion_product(q1, q2, convention='xyzw'):
|
||||
"""Return the quaternion product between two quaternions.
|
||||
|
||||
Args:
|
||||
q1 (np.float[4], quaternion.quaternion): first quaternion
|
||||
q2 (np.float[4], quaternion.quaternion): second quaternion
|
||||
convention (str): convention to be adopted when representing the quaternion. You can choose between 'xyzw' or
|
||||
'wxyz'.
|
||||
|
||||
Returns:
|
||||
np.float[4], quaternion.quaternion: resulting quaternion.
|
||||
"""
|
||||
if type(q1) != type(q2):
|
||||
raise TypeError("Expecting q1 and q2 to be of the same type")
|
||||
if isinstance(q1, quaternion.quaternion):
|
||||
@@ -318,7 +625,7 @@ def exponential_map(r):
|
||||
|
||||
|
||||
def angular_velocity_from_quaternion(q1, q2):
|
||||
"""
|
||||
r"""
|
||||
Convert the difference between 2 quaternions using the logarithm map.
|
||||
|
||||
Args:
|
||||
@@ -331,3 +638,22 @@ def angular_velocity_from_quaternion(q1, q2):
|
||||
q1 = quat_converter.convertTo(q1)
|
||||
q2 = quat_converter.convertTo(q2)
|
||||
return 2 * logarithm_map(q1 * q2)
|
||||
|
||||
|
||||
# Tests
|
||||
if __name__ == "__main__":
|
||||
import pybullet
|
||||
import tf.transformations as tft
|
||||
|
||||
q = np.array([-0.043, 0.567, 0.368, 0.736])
|
||||
rpy = get_rpy_from_quaternion(q)
|
||||
|
||||
print('\nRPY from quaternion: {}'.format(get_rpy_from_quaternion(q)))
|
||||
print('RPY <- matrix <- quaternion: {}'.format(get_rpy_from_matrix(get_matrix_from_quaternion(q))))
|
||||
print('Using pybullet: {}'.format(pybullet.getEulerFromQuaternion(q)))
|
||||
print('Using tf.transformations: {}'.format(tft.euler_from_quaternion(q)))
|
||||
|
||||
print('\nQuaternion from RPY: {}'.format(get_quaternion_from_rpy(rpy)))
|
||||
print('Quaternion <- matrix <- RPY: {}'.format(get_quaternion_from_matrix(get_matrix_from_rpy(rpy))))
|
||||
print('Using pybullet: {}'.format(pybullet.getQuaternionFromEuler(rpy)))
|
||||
print('Using tf.transformations: {}'.format(tft.quaternion_from_euler(*rpy)))
|
||||
|
||||
Reference in New Issue
Block a user