Source code for ars.model.robot.joints

"""Module of all the classes related to physical joints.
These are objects that link 2 bodies together.

There are two base abstract classes for all joints:
:class:`Joint` and :class:`ActuatedJoint`.
They are not coupled (at all) with ODE or any other
physics or collision library/engine.

The classes that implement at least one of those interfaces are these:

* :class:`Fixed`
* :class:`Rotary`
* :class:`Universal`
* :class:`BallSocket`
* :class:`Slider`

There is also an auxiliary class: :class:`JointFeedback`.

"""
from abc import ABCMeta, abstractmethod

import ode

from ... import exceptions as exc
from ...lib.pydispatch import dispatcher

from . import signals


[docs]class Joint(object): """Entity that links 2 bodies together, enforcing one or more movement constraints. This is an abstract class. """ __metaclass__ = ABCMeta @abstractmethod def __init__(self, world, inner_joint, body1=None, body2=None): """Constructor. :param world: :type world: :class:`physics.base.World` :param inner_joint: :type inner_joint: :class:`ode.Joint` :param body1: :type body1: :class:`physics.base.Body` :param body2: :type body2: :class:`physics.base.Body` """ self._world = world self._inner_joint = inner_joint self._body1 = body1 self._body2 = body2 @property def body1(self): return self._body1 @property def body2(self): return self._body2
[docs]class ActuatedJoint(Joint): """A joint with an actuator that can exert force and/or torque to connected bodies. This is an abstract class. """ __metaclass__ = ABCMeta
[docs]class Fixed(Joint): def __init__(self, world, body1, body2): """Constructor. :param world: :type world: :class:`physics.base.World` :param body1: :type body1: :class:`physics.base.Body` :param body2: :type body2: :class:`physics.base.Body` """ try: inner_joint = ode.FixedJoint(world._inner_object) inner_joint.attach(body1.inner_object, body2.inner_object) inner_joint.setFixed() super(Fixed, self).__init__(world, inner_joint, body1, body2) except: raise exc.PhysicsObjectCreationError(type_='Fixed joint')
[docs]class Rotary(ActuatedJoint): def __init__(self, world, body1, body2, anchor, axis): """Constructor. :param world: :type world: :class:`physics.base.World` :param body1: :type body1: :class:`physics.base.Body` :param body2: :type body2: :class:`physics.base.Body` :param anchor: joint anchor point :type anchor: 3-tuple of floats :param axis: rotation axis :type axis: 3-tuple of floats """ try: inner_joint = ode.HingeJoint(world._inner_object) inner_joint.attach(body1.inner_object, body2.inner_object) inner_joint.setAnchor(anchor) # TODO: see contrib.Ragdoll.addHingeJoint for possible modification inner_joint.setAxis(axis) # TODO: necessary? lo_stop = -ode.Infinity hi_stop = ode.Infinity inner_joint.setParam(ode.ParamLoStop, lo_stop) inner_joint.setParam(ode.ParamHiStop, hi_stop) super(Rotary, self).__init__(world, inner_joint, body1, body2) except: raise exc.PhysicsObjectCreationError(type_='Rotary joint')
[docs] def add_torque(self, torque): """Apply torque about the rotation axis. :param torque: magnitude :type torque: float """ dispatcher.send(signals.JOINT_PRE_ADD_TORQUE, sender=self, torque=torque) try: self._inner_joint.addTorque(torque) except: raise exc.JointError(self, 'Failed to add torque to this joint') dispatcher.send(signals.JOINT_POST_ADD_TORQUE, sender=self, torque=torque)
@property def angle(self): """Return the angle between the two bodies. The zero angle is determined by the position of the bodies when joint's anchor was set. :return: value ranging ``-pi`` and ``+pi`` :rtype: float """ try: return self._inner_joint.getAngle() except: raise exc.JointError(self, 'Failed to get the angle of this joint') @property def angle_rate(self): """Return the rate of change of the angle between the two bodies. :return: angle rate :rtype: float """ try: return self._inner_joint.getAngleRate() except: raise exc.JointError(self, 'Failed to get the angle rate of this joint')
[docs] def set_speed(self, speed, max_force=None): """Set rotation speed to ``speed``. The joint will set that speed by applying a force up to ``max_force``, so it is not guaranteed that ``speed`` will be reached. :param speed: speed to set :type speed: float :param max_force: if not None, the maximum force the joint can apply when trying to set the rotation speed :type max_force: float or None """ dispatcher.send(signals.JOINT_PRE_SET_SPEED, sender=self, speed=speed) if max_force is not None: self._inner_joint.setParam(ode.ParamFMax, max_force) self._inner_joint.setParam(ode.ParamVel, speed) dispatcher.send(signals.JOINT_POST_SET_SPEED, sender=self, speed=speed)
[docs]class Universal(Joint): def __init__(self, world, body1, body2, anchor, axis1, axis2): """Constructor. :param world: :type world: :class:`physics.base.World` :param body1: :type body1: :class:`physics.base.Body` :param body2: :type body2: :class:`physics.base.Body` :param anchor: joint anchor point :type anchor: 3-tuple of floats :param axis1: first universal axis :type axis1: 3-tuple of floats :param axis2: second universal axis :type axis2: 3-tuple of floats """ try: inner_joint = ode.UniversalJoint(world._inner_object) inner_joint.attach(body1.inner_object, body2.inner_object) inner_joint.setAnchor(anchor) # TODO: see contrib.Ragdoll.addHingeJoint for possible modification inner_joint.setAxis1(axis1) inner_joint.setAxis2(axis2) # TODO: necessary? lo_stop1 = -ode.Infinity hi_stop1 = ode.Infinity lo_stop2 = -ode.Infinity hi_stop2 = ode.Infinity inner_joint.setParam(ode.ParamLoStop, lo_stop1) inner_joint.setParam(ode.ParamHiStop, hi_stop1) inner_joint.setParam(ode.ParamLoStop2, lo_stop2) inner_joint.setParam(ode.ParamHiStop2, hi_stop2) super(Universal, self).__init__(world, inner_joint, body1, body2) except: raise exc.PhysicsObjectCreationError(type_='Universal joint')
[docs]class BallSocket(Joint): def __init__(self, world, body1, body2, anchor): """Constructor. :param world: :type world: :class:`physics.base.World` :param body1: :type body1: :class:`physics.base.Body` :param body2: :type body2: :class:`physics.base.Body` :param anchor: joint anchor point :type anchor: 3-tuple of floats """ try: inner_joint = ode.BallJoint(world._inner_object) inner_joint.attach(body1.inner_object, body2.inner_object) inner_joint.setAnchor(anchor) super(BallSocket, self).__init__(world, inner_joint, body1, body2) except: raise exc.PhysicsObjectCreationError(type_='Ball and Socket joint')
[docs]class Slider(ActuatedJoint): """Joint with one DOF that constrains two objects to line up along an axis. It is different from a Piston joint (which has two DOF) in that the Slider does not allow rotation. """ def __init__(self, world, body1, body2, axis): """Constructor. :param world: :type world: :class:`physics.base.World` :param body1: :type body1: :class:`physics.base.Body` :param body2: :type body2: :class:`physics.base.Body` :param axis: rotation axis :type axis: 3-tuple of floats """ try: inner_joint = ode.SliderJoint(world._inner_object) inner_joint.attach(body1.inner_object, body2.inner_object) inner_joint.setAxis(axis) # TODO: necessary? # see http://ode-wiki.org/wiki/index.php?title=Manual:_Joint_Types_and_Functions#Parameter_Functions lo_stop = -ode.Infinity hi_stop = ode.Infinity inner_joint.setParam(ode.ParamLoStop, lo_stop) inner_joint.setParam(ode.ParamHiStop, hi_stop) super(Slider, self).__init__(world, inner_joint, body1, body2) except: raise exc.PhysicsObjectCreationError(type_='Slider joint')
[docs] def add_force(self, force): """Apply a force of magnitude ``force`` along the joint's axis. :type force: float """ dispatcher.send(signals.JOINT_PRE_ADD_FORCE, sender=self, force=force) try: self._inner_joint.addForce(force) except: raise exc.JointError(self, 'Failed to add force to this joint') dispatcher.send(signals.JOINT_POST_ADD_FORCE, sender=self, force=force)
@property def position(self): """Return position of the joint with respect to its initial position. The zero position is established when the joint's axis is set. :rtype: float """ try: return self._inner_joint.getPosition() except: raise exc.JointError(self, 'Failed to get the position') @property def position_rate(self): """Return position's time derivative, i.e. "speed". :rtype: float """ try: return self._inner_joint.getPositionRate() except: raise exc.JointError(self, 'Failed to get the position rate')
#============================================================================== # aux classes #==============================================================================
[docs]class JointFeedback(object): """Data structure to hold the forces and torques resulting from the interaction of 2 bodies through a joint. All attributes are private. The results (:attr:`force1`, :attr:`force2`, :attr:`torque1`, :attr:`torque2`) are all length-3 tuples of floats. """ def __init__(self, body1, body2, force1=None, force2=None, torque1=None, torque2=None): """Constructor. :param body1: :type body1: :class:`physics.base.Body` :param body2: :type body2: :class:`physics.base.Body` :param force1: :type force1: 3-tuple of floats :param force2: :type force2: 3-tuple of floats :param torque1: :type torque1: 3-tuple of floats :param torque2: :type torque2: 3-tuple of floats """ self._body1 = body1 self._body2 = body2 self._force1 = force1 self._force2 = force2 self._torque1 = torque1 self._torque2 = torque2 @property def body1(self): return self._body1 @property def body2(self): return self._body2 @property def force1(self): return self._force1 @property def force2(self): return self._force2 @property def torque1(self): return self._torque1 @property def torque2(self): return self._torque2