# -*- indent-tabs-mode: t -*- # joints # For every joint type there is a separate class that wraps that joint. # These classes are derived from the base class "Joint" that contains # all the common stuff (including destruction). # The ODE joint is created in the constructor and destroyed in the destructor. # So it's the respective Python wrapper class that has ownership of the # ODE joint. If joint groups are used it can happen that an ODE joint gets # destroyed outside of its Python wrapper (whenever you empty the group). # In such cases the Python wrapper has to be notified so that it dismisses # its pointer. This is done by calling _destroyed() on the respective # Python wrapper (which is done by the JointGroup wrapper). ###################################################################### # JointGroup cdef class JointGroup: """Joint group. Constructor:: JointGroup() """ # JointGroup ID cdef dJointGroupID gid # A list of Python joints that were added to the group cdef object jointlist def __new__(self, *args, **kw): self.gid = dJointGroupCreate(0) def __init__(self): self.jointlist = [] def __dealloc__(self): if self.gid!=NULL: dJointGroupDestroy(self.gid) # empty def empty(self): """empty() Destroy all joints in the group. """ dJointGroupEmpty(self.gid) for j in self.jointlist: j._destroyed() self.jointlist = [] def _addjoint(self, j): """_addjoint(j) Add a joint to the group. This is an internal method that is called by the joints. The group has to know the Python wrappers because it has to notify them when the group is emptied (so that the ODE joints won't get destroyed twice). The notification is done by calling _destroyed() on the Python joints. @param j: The joint to add @type j: Joint """ self.jointlist.append(j) ###################################################################### # Joint cdef class Joint: """Base class for all joint classes.""" # Joint id as returned by dJointCreateXxx() cdef dJointID jid # A reference to the world so that the world won't be destroyed while # there are still joints using it. cdef object world # The feedback buffer cdef dJointFeedback* feedback cdef readonly body1 cdef readonly body2 def __new__(self, *args, **kw): self.jid = NULL self.world = None self.feedback = NULL self.body1 = None self.body2 = None def __init__(self, *a, **kw): raise NotImplementedError, "The Joint base class can't be used directly." def __dealloc__(self): self.setFeedback(False) if self.jid!=NULL: dJointDestroy(self.jid) # _destroyed def _destroyed(self): """Notify the joint object about an external destruction of the ODE joint. This method has to be called when the underlying ODE object was destroyed by someone else (e.g. by a joint group). The Python wrapper will then refrain from destroying it again. """ self.jid = NULL # attach def attach(self, _Body body1, _Body body2): """attach(body1, body2) Attach the joint to some new bodies. TODO: What if there's only one body. @param body1: First body @param body2: Second body @type body1: Body @type body2: Body """ if self.body1 is not None: self.body1._remove_joint(self) if self.body2 is not None: self.body2._remove_joint(self) self.body1 = body1 self.body2 = body2 dJointAttach(self.jid, body1._bid, body2._bid) # Make sure the bodies keep a reference to the joint so that # the user doesn't have to body1._add_joint(self) body2._add_joint(self) # # getBody # def get_body(self, index): # """getBody(index) -> Body # # Return the bodies that this joint connects. If index is 0 the # "first" body will be returned, corresponding to the body1 # argument of the attach() method. If index is 1 the "second" body # will be returned, corresponding to the body2 argument of the # attach() method. # # @param index: Body index (0 or 1). # @type index: int # """ # # if (index == 0): # return self.body1 # elif (index == 1): # return self.body2 # else: # raise IndexError() # setFeedback def setFeedback(self, flag=1): """setFeedback(flag=True) Create a feedback buffer. If flag is True then a buffer is allocated and the forces/torques applied by the joint can be read using the getFeedback() method. If flag is False the buffer is released. @param flag: Specifies whether a buffer should be created or released @type flag: bool """ if flag: # Was there already a buffer allocated? then we're finished if self.feedback!=NULL: return # Allocate a buffer and pass it to ODE self.feedback = malloc(sizeof(dJointFeedback)) if self.feedback==NULL: raise MemoryError("can't allocate feedback buffer") dJointSetFeedback(self.jid, self.feedback) else: if self.feedback!=NULL: # Free a previously allocated buffer dJointSetFeedback(self.jid, NULL) free(self.feedback) self.feedback = NULL # getFeedback def getFeedback(self): """getFeedback() -> (force1, torque1, force2, torque2) Get the forces/torques applied by the joint. If feedback is activated (i.e. setFeedback(True) was called) then this method returns a tuple (force1, torque1, force2, torque2) with the forces and torques applied to body 1 and body 2. The forces/torques are given as 3-tuples. If feedback is deactivated then the method always returns None. """ cdef dJointFeedback* fb fb = dJointGetFeedback(self.jid) if (fb==NULL): return None f1 = (fb.f1[0], fb.f1[1], fb.f1[2]) t1 = (fb.t1[0], fb.t1[1], fb.t1[2]) f2 = (fb.f2[0], fb.f2[1], fb.f2[2]) t2 = (fb.t2[0], fb.t2[1], fb.t2[2]) return (f1,t1,f2,t2) cdef void _setParam(self, int param, dReal value): raise ValueError, "_setParam not implemented!" cdef dReal _getParam(self, int param): raise ValueError, "_getParam not implemented!" property lo_stop: def __get__(self): return self._getParam(ParamLoStop) def __set__(self, dReal value): self._setParam(ParamLoStop, value) property hi_stop: def __get__(self): return self._getParam(ParamHiStop) def __set__(self, dReal value): self._setParam(ParamHiStop, value) property velocity: def __get__(self): return self._getParam(ParamVel) def __set__(self, dReal value): self._setParam(ParamVel, value) property fmax: def __get__(self): return self._getParam(ParamFMax) def __set__(self, dReal value): self._setParam(ParamFMax, value) property fudge_factor: def __get__(self): return self._getParam(ParamFudgeFactor) def __set__(self, dReal value): self._setParam(ParamFudgeFactor, value) property bounce: def __get__(self): return self._getParam(ParamBounce) def __set__(self, dReal value): self._setParam(ParamBounce, value) property cfm: def __get__(self): return self._getParam(ParamCFM) def __set__(self, dReal value): self._setParam(ParamCFM, value) property stop_erp: def __get__(self): return self._getParam(ParamStopERP) def __set__(self, dReal value): self._setParam(ParamStopERP, value) property stop_cfm: def __get__(self): return self._getParam(ParamStopCFM) def __set__(self, dReal value): self._setParam(ParamStopCFM, value) property suspension_erp: def __get__(self): return self._getParam(ParamSuspensionERP) def __set__(self, dReal value): self._setParam(ParamSuspensionERP, value) property suspension_cfm: def __get__(self): return self._getParam(ParamSuspensionCFM) def __set__(self, dReal value): self._setParam(ParamSuspensionCFM, value) property lo_stop2: def __get__(self): return self._getParam(ParamLoStop) def __set__(self, dReal value): self._setParam(ParamLoStop, value) property hi_stop2: def __get__(self): return self._getParam(ParamHiStop2) def __set__(self, dReal value): self._setParam(ParamHiStop2, value) property velocity2: def __get__(self): return self._getParam(ParamVel2) def __set__(self, dReal value): self._setParam(ParamVel2, value) property fmax2: def __get__(self): return self._getParam(ParamFMax2) def __set__(self, dReal value): self._setParam(ParamFMax2, value) property fudge_factor2: def __get__(self): return self._getParam(ParamFudgeFactor2) def __set__(self, dReal value): self._setParam(ParamFudgeFactor2, value) property bounce2: def __get__(self): return self._getParam(ParamBounce2) def __set__(self, dReal value): self._setParam(ParamBounce2, value) property cfm2: def __get__(self): return self._getParam(ParamCFM2) def __set__(self, dReal value): self._setParam(ParamCFM2, value) property stop_erp2: def __get__(self): return self._getParam(ParamStopERP2) def __set__(self, dReal value): self._setParam(ParamStopERP2, value) property stop_cfm2: def __get__(self): return self._getParam(ParamStopCFM2) def __set__(self, dReal value): self._setParam(ParamStopCFM2, value) property suspension_erp2: def __get__(self): return self._getParam(ParamSuspensionERP2) def __set__(self, dReal value): self._setParam(ParamSuspensionERP2, value) property suspension_cfm2: def __get__(self): return self._getParam(ParamSuspensionCFM2) def __set__(self, dReal value): self._setParam(ParamSuspensionCFM2, value) ###################################################################### # BallJoint cdef class BallJoint(Joint): """Ball joint. Constructor:: BallJoint(world, jointgroup=None) """ def __new__(self, _World world not None, jointgroup=None, *args, **kw): cdef JointGroup jg cdef dJointGroupID jgid jgid=NULL if jointgroup!=None: jg=jointgroup jgid=jg.gid self.jid = dJointCreateBall(world._wid, jgid) def __init__(self, _World world not None, jointgroup=None): self.world = world if jointgroup!=None: jointgroup._addjoint(self) property anchor: """setAnchor(pos) Set the joint anchor point which must be specified in world coordinates. @param pos: Anchor position @type pos: 3-sequence of floats """ def __set__(self, pos): dJointSetBallAnchor(self.jid, pos[0], pos[1], pos[2]) def __get__(self): cdef dVector3 p dJointGetBallAnchor(self.jid, p) return (p[0],p[1],p[2]) property anchor2: """getAnchor2() -> 3-tuple of floats Get the joint anchor point, in world coordinates. This returns the point on body 2. If the joint is perfectly satisfied, this will be the same as the point on body 1. """ def __get__(self): cdef dVector3 p dJointGetBallAnchor2(self.jid, p) return (p[0],p[1],p[2]) # setParam cdef void _setParam(self, int param, dReal value): pass # getParam cdef dReal _getParam(self, int param): return 0.0 # HingeJoint cdef class HingeJoint(Joint): """Hinge joint. Constructor:: HingeJoint(world, jointgroup=None) """ def __new__(self, _World world not None, jointgroup=None, *args, **kw): cdef JointGroup jg cdef dJointGroupID jgid jgid=NULL if jointgroup!=None: jg=jointgroup jgid=jg.gid self.jid = dJointCreateHinge(world._wid, jgid) def __init__(self, _World world not None, jointgroup=None): self.world = world if jointgroup!=None: jointgroup._addjoint(self) property anchor: """setAnchor(pos) Set the hinge anchor which must be given in world coordinates. @param pos: Anchor position @type pos: 3-sequence of floats """ def __set__(self, pos): dJointSetHingeAnchor(self.jid, pos[0], pos[1], pos[2]) def __get__(self): cdef dVector3 p dJointGetHingeAnchor(self.jid, p) return (p[0],p[1],p[2]) property anchor2: """getAnchor2() -> 3-tuple of floats Get the joint anchor point, in world coordinates. This returns the point on body 2. If the joint is perfectly satisfied, this will be the same as the point on body 1. """ def __get__(self): cdef dVector3 p dJointGetHingeAnchor2(self.jid, p) return (p[0],p[1],p[2]) property axis: """setAxis(axis) Set the hinge axis. @param axis: Hinge axis @type axis: 3-sequence of floats """ # XXX use Soya vectors def __set__(self, axis): dJointSetHingeAxis(self.jid, axis[0], axis[1], axis[2]) def __get__(self): cdef dVector3 a dJointGetHingeAxis(self.jid, a) return (a[0],a[1],a[2]) property angle: """getAngle() -> float Get the hinge angle. The angle is measured between the two bodies, or between the body and the static environment. The angle will be between -pi..pi. When the hinge anchor or axis is set, the current position of the attached bodies is examined and that position will be the zero angle. """ def __get__(self): return dJointGetHingeAngle(self.jid) property angle_rate: """getAngleRate() -> float Get the time derivative of the angle. """ def __get__(self): return dJointGetHingeAngleRate(self.jid) # setParam cdef void _setParam(self, int param, dReal value): """setParam(param, value) Set limit/motor parameters for the joint. param is one of ParamLoStop, ParamHiStop, ParamVel, ParamFMax, ParamFudgeFactor, ParamBounce, ParamCFM, ParamStopERP, ParamStopCFM, ParamSuspensionERP, ParamSuspensionCFM. These parameter names can be optionally followed by a digit (2 or 3) to indicate the second or third set of parameters. @param param: Selects the parameter to set @param value: Parameter value @type param: int @type value: float """ dJointSetHingeParam(self.jid, param, value) # getParam cdef dReal _getParam(self, int param): """getParam(param) -> float Get limit/motor parameters for the joint. param is one of ParamLoStop, ParamHiStop, ParamVel, ParamFMax, ParamFudgeFactor, ParamBounce, ParamCFM, ParamStopERP, ParamStopCFM, ParamSuspensionERP, ParamSuspensionCFM. These parameter names can be optionally followed by a digit (2 or 3) to indicate the second or third set of parameters. @param param: Selects the parameter to read @type param: int """ return dJointGetHingeParam(self.jid, param) # SliderJoint cdef class SliderJoint(Joint): """Slider joint. Constructor:: SlideJoint(world, jointgroup=None) """ def __new__(self, _World world not None, jointgroup=None, *args, **kw): cdef JointGroup jg cdef dJointGroupID jgid jgid=NULL if jointgroup!=None: jg=jointgroup jgid=jg.gid self.jid = dJointCreateSlider(world._wid, jgid) def __init__(self, _World world not None, jointgroup=None): self.world = world if jointgroup!=None: jointgroup._addjoint(self) property axis: """setAxis(axis) Set the slider axis parameter. @param axis: Slider axis @type axis: 3-sequence of floats """ def __set__(self, axis): dJointSetSliderAxis(self.jid, axis[0], axis[1], axis[2]) def __get__(self): cdef dVector3 a dJointGetSliderAxis(self.jid, a) return (a[0],a[1],a[2]) property position: """getPosition() -> float Get the slider linear position (i.e. the slider's "extension"). When the axis is set, the current position of the attached bodies is examined and that position will be the zero position. """ def __get__(self): return dJointGetSliderPosition(self.jid) property position_rate: """getPositionRate() -> float Get the time derivative of the position. """ def __get__(self): return dJointGetSliderPositionRate(self.jid) # setParam cdef void _setParam(self, int param, dReal value): dJointSetSliderParam(self.jid, param, value) # getParam cdef dReal _getParam(self, int param): return dJointGetSliderParam(self.jid, param) # UniversalJoint cdef class UniversalJoint(Joint): """Universal joint. Constructor:: UniversalJoint(world, jointgroup=None) """ def __new__(self, _World world not None, jointgroup=None, *args, **kw): cdef JointGroup jg cdef dJointGroupID jgid jgid=NULL if jointgroup!=None: jg=jointgroup jgid=jg.gid self.jid = dJointCreateUniversal(world._wid, jgid) def __init__(self, _World world not None, jointgroup=None): self.world = world if jointgroup!=None: jointgroup._addjoint(self) property anchor: """setAnchor(pos) Set the hinge anchor which must be given in world coordinates. @param pos: Anchor position @type pos: 3-sequence of floats """ def __set__(self, pos): dJointSetUniversalAnchor(self.jid, pos[0], pos[1], pos[2]) def __get__(self): cdef dVector3 p dJointGetUniversalAnchor(self.jid, p) return (p[0],p[1],p[2]) property anchor2: """getAnchor2() -> 3-tuple of floats Get the joint anchor point, in world coordinates. This returns the point on body 2. If the joint is perfectly satisfied, this will be the same as the point on body 1. """ def __get__(self): cdef dVector3 p dJointGetUniversalAnchor2(self.jid, p) return (p[0],p[1],p[2]) property axis1: """setAxis1(axis) Set the first universal axis. Axis 1 and axis 2 should be perpendicular to each other. @param axis: Joint axis @type axis: 3-sequence of floats """ def __set__(self, axis): dJointSetUniversalAxis1(self.jid, axis[0], axis[1], axis[2]) def __get__(self): cdef dVector3 a dJointGetUniversalAxis1(self.jid, a) return (a[0],a[1],a[2]) property axis2: """setAxis2(axis) Set the second universal axis. Axis 1 and axis 2 should be perpendicular to each other. @param axis: Joint axis @type axis: 3-sequence of floats """ def __set__(self, axis): dJointSetUniversalAxis2(self.jid, axis[0], axis[1], axis[2]) def __get__(self): cdef dVector3 a dJointGetUniversalAxis2(self.jid, a) return (a[0],a[1],a[2]) # setParam cdef void _setParam(self, int param, dReal value): dJointSetUniversalParam(self.jid, param, value) # getParam cdef dReal _getParam(self, int param): return dJointGetUniversalParam(self.jid, param) # Hinge2Joint cdef class Hinge2Joint(Joint): """Hinge2 joint. Constructor:: Hinge2Joint(world, jointgroup=None) """ def __new__(self, _World world not None, jointgroup=None, *args, **kw): cdef JointGroup jg cdef dJointGroupID jgid jgid=NULL if jointgroup!=None: jg=jointgroup jgid=jg.gid self.jid = dJointCreateHinge2(world._wid, jgid) def __init__(self, _World world, jointgroup=None): self.world = world if jointgroup!=None: jointgroup._addjoint(self) property anchor: """setAnchor(pos) Set the hinge anchor which must be given in world coordinates. @param pos: Anchor position @type pos: 3-sequence of floats """ def __set__(self, pos): dJointSetHinge2Anchor(self.jid, pos[0], pos[1], pos[2]) def __get__(self): cdef dVector3 p dJointGetHinge2Anchor(self.jid, p) return (p[0],p[1],p[2]) property anchor2: """getAnchor2() -> 3-tuple of floats Get the joint anchor point, in world coordinates. This returns the point on body 2. If the joint is perfectly satisfied, this will be the same as the point on body 1. """ def __get__(self): cdef dVector3 p dJointGetHinge2Anchor2(self.jid, p) return (p[0],p[1],p[2]) property axis1: """setAxis1(axis) Set the first universal axis. Axis 1 and axis 2 should be perpendicular to each other. @param axis: Joint axis @type axis: 3-sequence of floats """ def __set__(self, axis): dJointSetHinge2Axis1(self.jid, axis[0], axis[1], axis[2]) def __get__(self): cdef dVector3 a dJointGetHinge2Axis1(self.jid, a) return (a[0],a[1],a[2]) property axis2: """setAxis2(axis) Set the second universal axis. Axis 1 and axis 2 should be perpendicular to each other. @param axis: Joint axis @type axis: 3-sequence of floats """ def __set__(self, axis): dJointSetHinge2Axis2(self.jid, axis[0], axis[1], axis[2]) def __get__(self): cdef dVector3 a dJointGetHinge2Axis2(self.jid, a) return (a[0],a[1],a[2]) property angle1: """getAngle1() -> float Get the first hinge-2 angle (around axis 1). When the anchor or axis is set, the current position of the attached bodies is examined and that position will be the zero angle. """ def __get__(self): return dJointGetHinge2Angle1(self.jid) property angle1_rate: """getAngle1Rate() -> float Get the time derivative of the first hinge-2 angle. """ def __get__(self): return dJointGetHinge2Angle1Rate(self.jid) property angle2_rate: """getAngle2Rate() -> float Get the time derivative of the second hinge-2 angle. """ def __get__(self): return dJointGetHinge2Angle2Rate(self.jid) # setParam cdef void _setParam(self, int param, dReal value): dJointSetHinge2Param(self.jid, param, value) # getParam cdef dReal _getParam(self, int param): return dJointGetHinge2Param(self.jid, param) # FixedJoint cdef class FixedJoint(Joint): """Fixed joint. Constructor:: FixedJoint(world, jointgroup=None) """ def __new__(self, _World world not None, jointgroup=None, *args, **kw): cdef JointGroup jg cdef dJointGroupID jgid jgid=NULL if jointgroup!=None: jg=jointgroup jgid=jg.gid self.jid = dJointCreateFixed(world._wid, jgid) def __init__(self, _World world not None, jointgroup=None): self.world = world if jointgroup!=None: jointgroup._addjoint(self) # setFixed def setFixed(self): """setFixed() Call this on the fixed joint after it has been attached to remember the current desired relative offset and desired relative rotation between the bodies. """ dJointSetFixed(self.jid) # ContactJoint cdef class ContactJoint(Joint): """Contact joint. Constructor:: ContactJoint(world, jointgroup, contact) """ def __new__(self, _World world not None, jointgroup, Contact contact, *args, **kw): cdef JointGroup jg cdef dJointGroupID jgid jgid=NULL if jointgroup!=None: jg=jointgroup jgid=jg.gid self.jid = dJointCreateContact(world._wid, jgid, &contact._contact) def __init__(self, _World world not None, jointgroup, Contact contact): self.world = world if jointgroup!=None: jointgroup._addjoint(self) # AMotor cdef class AMotor(Joint): """AMotor joint. Constructor:: AMotor(world, jointgroup=None) """ def __new__(self, _World world not None, jointgroup=None, *args, **kw): cdef JointGroup jg cdef dJointGroupID jgid jgid = NULL if jointgroup!=None: jg = jointgroup jgid = jg.gid self.jid = dJointCreateAMotor(world._wid, jgid) def __init__(self, _World world not None, jointgroup=None): self.world = world if jointgroup!=None: jointgroup._addjoint(self) property mode: """setMode(mode) Set the angular motor mode. mode must be either AMotorUser or AMotorEuler. @param mode: Angular motor mode @type mode: int """ def __set__(self, mode): dJointSetAMotorMode(self.jid, mode) def __get__(self): return dJointGetAMotorMode(self.jid) # setNumAxes def setNumAxes(self, int num): """setNumAxes(num) Set the number of angular axes that will be controlled by the AMotor. num may be in the range from 0 to 3. @param num: Number of axes (0-3) @type num: int """ dJointSetAMotorNumAxes(self.jid, num) # getNumAxes def getNumAxes(self): """getNumAxes() -> int Get the number of angular axes that are controlled by the AMotor. """ return dJointGetAMotorNumAxes(self.jid) # XXX this can be converted to properties by using Soya vectors and # choosing the relative orientation mode automatically based on the # coordinate system of the vector. # setAxis def setAxis(self, int anum, int rel, axis): """setAxis(anum, rel, axis) Set an AMotor axis. The anum argument selects the axis to change (0,1 or 2). Each axis can have one of three "relative orientation" modes, selected by rel: 0: The axis is anchored to the global frame. 1: The axis is anchored to the first body. 2: The axis is anchored to the second body. The axis vector is always specified in global coordinates regardless of the setting of rel. @param anum: Axis number @param rel: Relative orientation mode @param axis: Axis @type anum: int @type rel: int @type axis: 3-sequence of floats """ dJointSetAMotorAxis(self.jid, anum, rel, axis[0], axis[1], axis[2]) # getAxis def getAxis(self, int anum): """getAxis(anum) Get an AMotor axis. @param anum: Axis index (0-2) @type anum: int """ cdef dVector3 a dJointGetAMotorAxis(self.jid, anum, a) return (a[0],a[1],a[2]) # getAxisRel def getAxisRel(self, int anum): """getAxisRel(anum) -> int Get the relative mode of an axis. @param anum: Axis index (0-2) @type anum: int """ return dJointGetAMotorAxisRel(self.jid, anum) # XXX these should be converted to three properties each, one for each # axis. # setAngle def setAngle(self, int anum, angle): """setAngle(anum, angle) Tell the AMotor what the current angle is along axis anum. @param anum: Axis index @param angle: Angle @type anum: int @type angle: float """ dJointSetAMotorAngle(self.jid, anum, angle) # getAngle def getAngle(self, int anum): """getAngle(anum) -> float Return the current angle for axis anum. @param anum: Axis index @type anum: int """ return dJointGetAMotorAngle(self.jid, anum) # getAngleRate def getAngleRate(self, int anum): """getAngleRate(anum) -> float Return the current angle rate for axis anum. @param anum: Axis index @type anum: int """ return dJointGetAMotorAngleRate(self.jid, anum) # setParam cdef void _setParam(self, int param, dReal value): dJointSetAMotorParam(self.jid, param, value) # getParam cdef dReal _getParam(self, int param): return dJointGetAMotorParam(self.jid, param)