blueprints.tendon module#

Tendons#

Tendons can be used to string together multiple Things. The can provide additional structural stability or connect otherwise kinetically distinct Things. In Mujoco, Tendon can be bound to Sites, Geoms and Joints according to certain constraints limiting which Things can be bound and which Tendon attributes are passed into the simulation. Tendons introduce cycles to the kinematic hierarchy and are therefore treated specially with the Path.bind method replacing NodeThing.attach within a special context manager.

Joint (Fixed) Tendons#

Joint Tendons are the simplest usecase since they only use a reduced set of attributes and do not support spliting paths into pulleys. The attributes allowed for Joint Tendons are:

We first create some example Things.

Creating Example Things#
>>> world = blue.World()
>>> body  = blue.Body(geoms=blue.geoms.Sphere(), # introduces mass to Body
>>>                   joints=blue.joints.Hinge())
>>> world.attach(body.shift(x=0, name='A'),
>>>              body.shift(x=3, name='B'),
>>>              body.shift(x=6, name='C'))
>>> tendon = blue.Tendon()

To bind the Joints to the tendon we first grab them from the world and then bind them to the tendon path within the Tendons context. When we bind a Joint to a Tendon, a coefficient must be specified weighing the interaction strength between Tendon and Joint.

Tendon Context#
>>> jA = world.bodies['A'].joints[0]
>>> jB = world.bodies['B'].joints[0]
>>> jC = world.bodies['C'].joints[0]
>>> with tendon as path:
>>>      path.bind(jA, coef=1.)
>>>      path.bind(jB, coef=2.)
>>>      path.bind(jC, coef=1.)
Alternatively#
>>> with tendon as path:
>>>      path.bind(jA, jB, jC, coef=[1.0, 2.0, 1.0])

The resulting XML is split into the normal kinematic tree and the Tendon referencing its bound Things via name attribute.

XML Structure Tendon#
<tendon>
        <fixed name="anonymous_tendon">
                <joint joint="anonymous_hinge_(0)" coef="1.0" />
                <joint joint="anonymous_hinge_(1)" coef="2.0" />
                <joint joint="anonymous_hinge_(2)" coef="1.0" />
        </fixed>
</tendon>

Note

Keep in mind that Fixed Joint Tendons do not support width and are hence invisible.

XML Structure World#
<worldbody>
        <body name="A">
                <joint type="hinge" name="anonymous_hinge_(0)" axis="0.0 0.0 1.0" />
                <geom size="1.0" type="sphere" name="anonymous_sphere_(0)" />
        </body>
        <body pos="3.0 0.0 0.0" name="B">
                <joint type="hinge" name="anonymous_hinge_(1)" axis="0.0 0.0 1.0" />
                <geom size="1.0" type="sphere" name="anonymous_sphere_(1)"/>
        </body>
        <body pos="6.0 0.0 0.0" name="C">
                <joint type="hinge" name="anonymous_hinge_(2)" axis="0.0 0.0 1.0" />
                <geom size="1.0" type="sphere" name="anonymous_sphere_(2)"/>
        </body>
</worldbody>

Site/Geom (Spatial) Tendons#

Spatial Tendons are bind to Sites and Geoms and support path splitting. The following rules must be satisfied for mujoco to accept the build.

  • Every path must start and end with a Site.

  • Every Geom must be sandwiched between two Sites.

  • Every Tendon binding Sites/Geoms must not also bind Joints.

Lets build some example Things and bind them to a Tendon.

Creating Example Things#
>>> body_A = blue.Body(sites=blue.sites.Sphere(radius=0.5)).shift(x=-3, name='A')
>>> body_B = blue.Body(geoms=blue.geoms.Sphere(radius=0.5),
>>>                    sites=blue.sites.Sphere(radius=0.5).shift(z=3), name='B')
>>> body_C = blue.Body(sites=blue.sites.Sphere(radius=0.5)).shift(x=3, name='C')
>>> world.attach(body_A, body_C, body_B)
>>> tendon = blue.Tendon(width=0.1)

When binding a Geom to a Tendon, an optional side_site argument may be specified. If the Tendon runs through the Geom at the begining of the Mujoco simulation, it is snapped out on the side of the specified side_site.

Binding Sites and Geoms#
>>> sA = world.bodies['A'].sites[0]
>>> sB = world.bodies['B'].sites[0]
>>> gB = world.bodies['B'].geoms[0]
>>> sC = world.bodies['C'].sites[0]
>>> with tendon as path:
>>>      path.bind(sA)
>>>      path.bind(gB, side_site=sB)
>>>      path.bind(sC)
_images/tendon_side_site.png

The resulting XML structure are separated into the cyclical references in the header of the Mujoco XML and its components in the normal kinematic hierarchy.

XML Structure Tendon#
<tendon>
        <spatial width="0.1" name="anonymous_tendon">
                <site site="anonymous_sphere_(0)" />
                <geom geom="anonymous_sphere" sidesite="anonymous_sphere_(2)" />
                <site site="anonymous_sphere_(1)" />
        </spatial>
</tendon>
XML Structure World#
<worldbody>
        <body pos="-3.0 0.0 0.0" name="A">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(0)" />
        </body>
        <body pos="3.0 0.0 0.0" name="C">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(1)" />
        </body>
        <body name="B">
                <geom size="0.5" type="sphere" name="anonymous_sphere" />
                <site size="0.5 0.0 0.0" type="sphere" pos="0.0 0.0 3.0" name="anonymous_sphere_(2)" />
        </body>
</worldbody>

Path Splitting#

Mujoco supports pullies which split a path into multiple subpaths. The forces acting on each subpath are distributed proportionally. The begining of a new path is not necessarily directly connected to end of the parent path.

Creating Example Things#
>>> world   = blue.World()
>>> body_A  = blue.Body(pos=[0, 0, 0], sites=blue.sites.Sphere(radius=0.5))
>>> body_B  = blue.Body(pos=[2, 0, 0], sites=blue.sites.Sphere(radius=0.5))
>>> body_C1 = blue.Body(pos=[4,-2, 0], sites=blue.sites.Sphere(radius=0.5))
>>> body_C2 = blue.Body(pos=[4, 2, 0], sites=blue.sites.Sphere(radius=0.5))
>>> body_D1 = blue.Body(pos=[6,-2, 0], sites=blue.sites.Sphere(radius=0.5))
>>> body_D2 = blue.Body(pos=[6, 0, 0], sites=blue.sites.Sphere(radius=0.5))
>>> body_D3 = blue.Body(pos=[6, 4, 0], sites=blue.sites.Sphere(radius=0.5))
>>> tendon  = blue.Tendon(width=0.1)

Using the method split a pulley is introduced that splits the path into n branches. A branch path can also be split.

Binding with Multiple Paths#
>>> with tendon as path:
>>>      path.bind(body_A.sites)
>>>      path.bind(body_B.sites)
>>>      path_1, path_2, path_3 = path.split(3)
>>>      # PATH ONE
>>>      path_1.bind(body_B.sites)
>>>      path_1.bind(body_C1.sites)
>>>      path_1.bind(body_D1.sites)
>>>      # PATH TWO
>>>      path_2.bind(body_C2.sites)
>>>      path_2.bind(body_D2.sites)
>>>      # PATH THREE
>>>      path_3.bind(body_C2.sites)
>>>      path_3.bind(body_D3.sites)

The example above introduces a pulley with the divisor 3. The path bindings can be seen in the following image from left to right with path_1 on the bottom and path_2 and path_3 ontop both sharing the same initial Site.

_images/tendon_example.png

The resulting XML structures are:

XML Structure Tendon#
<tendon>
        <spatial width="0.1" name="anonymous_tendon">
                <site site="anonymous_sphere_(0)" />
                <site site="anonymous_sphere_(1)" />
                <pulley divisor="3" />
                <site site="anonymous_sphere_(3)" />
                <site site="anonymous_sphere_(6)" />
                <pulley divisor="3" />
                <site site="anonymous_sphere_(3)" />
                <site site="anonymous_sphere_(5)" />
                <pulley divisor="3" />
                <site site="anonymous_sphere_(1)" />
                <site site="anonymous_sphere_(2)" />
                <site site="anonymous_sphere_(4)" />
        </spatial>
</tendon>
XML Structure World#
<worldbody>
        <body name="anonymous_body_(0)">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(0)" />
        </body>
        <body pos="2.0 0.0 0.0" name="anonymous_body_(1)">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(1)" />
        </body>
        <body pos="4.0 -2.0 0.0" name="anonymous_body_(2)">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(2)" />
        </body>
        <body pos="4.0 2.0 0.0" name="anonymous_body_(3)">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(3)" />
        </body>
        <body pos="6.0 -2.0 0.0" name="anonymous_body_(4)">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(4)" />
        </body>
        <body pos="6.0 0.0 0.0" name="anonymous_body_(5)">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(5)" />
        </body>
        <body pos="6.0 4.0 0.0" name="anonymous_body_(6)">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(6)" />
        </body>
</worldbody>

Copies of Tendons#

Copying Tendons takes special attention since Tendons introduce cyclical references (i.e. multiple children of a NodeThing can be bound to the same Tendon). Therefore Tendons only get copied when the (root) Thing copied by the user has as its descendants all references of the Tendon.

Let’s see an example of this:

Creating and Binding Example Things#
>>> tendon = blue.Tendon(width=0.1)
>>> root = blue.Body(name='root')
>>> body_site = blue.Body(sites=blue.sites.Sphere(radius=0.5))
>>> root.attach(body_site.shift(x=0, name='A'),
>>>             body_site.shift(x=2, name='B'))
>>> A = root.bodies['A']
>>> B = root.bodies['B']
>>> C = body_site.shift(x=4, name='C')
>>> with tendon as path:
>>>      path.bind(A.sites)
>>>      path.bind(B.sites)
>>>      path.bind(C.sites)

We have created three bodies with sites (A, B, C) that are bound together with a Tendon. But only A and B are attached to root, while C is not. If both are attached to the World without creating a copy, they world will contain the tendon.

World Attachment without Copy#
>>> world.attach(root, C, copy=False)
XML Structure Tendon#
<tendon>
        <spatial width="0.1" name="anonymous_tendon">
                <site site="anonymous_sphere_(0)" />
                <site site="anonymous_sphere_(1)" />
                <site site="anonymous_sphere_(2)" />
        </spatial>
</tendon>
XML Structure World#
<worldbody>
        <body name="root">
                <body name="A">
                        <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(0)" />
                </body>
                <body pos="2.0 0.0 0.0" name="B">
                        <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(1)" />
                </body>
        </body>
        <body pos="4.0 0.0 0.0" name="C">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(2)" />
        </body>
</worldbody>

If we however create a copy of root and C separately the Tendon will be discarded, since neither root nor C contain all refferences of the Tendon as their descendants (the references of the Tendon are the bound Sites "anonymous_sphere_(0)", "anonymous_sphere_(1)" and "anonymous_sphere_(2)").

World Attachment without Copy#
>>> world.attach(root, C, copy=True)
XML Structure Tendon#
 </tendon>
XML Structure World#
<worldbody>
        <body name="root">
                <body name="A">
                        <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(0)" />
                </body>
                <body pos="2.0 0.0 0.0" name="B">
                        <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(1)" />
                </body>
        </body>
        <body pos="4.0 0.0 0.0" name="C">
                <site size="0.5 0.0 0.0" type="sphere" name="anonymous_sphere_(2)" />
        </body>
</worldbody>
class blueprints.tendon.Tendon[source]#

Bases: TendonType, NodeThing, ColoredThing, FocalThing

__init__(name=None, limited=None, act_force_limited=None, min_length=0.0, max_length=0.0, min_act_force=0.0, max_act_force=0.0, frictionloss=0.0, width=0.003, color=None, stiffness=0.0, damping=0.0, armature=0.0, **kwargs)[source]#

Some attribute descriptions are taken from Mujoco.

Parameters:
  • limited (bool | None) – Sets whether length limits constraints are imposed on the mujoco solver. Leaving this argument as None (recommended) enables autolimits derived from the other attributes.

  • act_force_limited (bool | None) – This argument is the equivalent to limited for actuator forces.

  • min_length (int | float) – Minimal length of Tendon

  • max_length (int | float) – Maximal length of Tendon

  • min_act_force (int | float) – Minimal force allowed to act on Tendon

  • max_act_force (int | float) – Maximal force allowed to act on Tendon

  • frictionloss (int | float) – Friction loss caused by dry friction. This value should be positive.

  • width (int | float) – Crosssection radius of the Tendon

  • stiffness (int | float) – If set to a positive value it acts as a Spring coefficient.

  • damping (int | float) – If set to a positive value it acts as a Damping coefficient.

  • armature (int | float) – Inertia associated with changes in tendon length

  • name (str | None)

  • color (object | None)

Return type:

None

attach(*items, copy=False)[source]#

Attachements are not allowed for Tendons. Use Path.bind() instead.

Return type:

None

property actuatorfrclimited: bool | None#

Indicates whether the Actuator forces applied are limited.

Returns:

In the attribute is None, the value is set to auto.

Return type:

bool | None

property range: ndarray#

The length range of the Tendon.

Returns:

The two entries indicate minimum and maximum Tendon length.

Return type:

np.ndarray

property actuatorfrcrange: ndarray#

The Actuator force range of the Tendon.

Returns:

The two entries indicate minimum and maximum Actuator force applied to the Tendon.

Return type:

np.ndarray

property max_length: float#

Changing this value results in a change in the first component of range.

The user should make sure, that the initial length of the tendon as computed by the distance between its bound Things does not exeed this attribute.

Returns:

Maximum length of the Tendon

Return type:

float

property min_length: float#

Changing this value results in a change in the second component of range.

Returns:

Minimum length of the Tendon

Return type:

float

property max_act_force: float#

Changing this value results in a change in the first component of actuatorfrcrange.

Returns:

Maximum force applicable by an Actuator

Return type:

float

property min_act_force: float#

Changing this value results in a change in the second component of actuatorfrcrange.

Returns:

Minimum force applicable by an Actuator

Return type:

float

property limited: bool | None#

Indicates whether the Tendon length is limited by range.

Returns:

In the attribute is None, the value is set to auto.

Return type:

bool | None

property act_force_limited: bool | None#

Indicates whether the Actuator forces applied are limited.

Returns:

In the attribute is None, the value is set to auto.

Return type:

bool | None

property frictionloss: float#

Friction loss caused by dry friction.

Returns:

To enable friction loss, set this attribute to a positive value.

Return type:

float

property width: float#

This attribute is purely cosmetical.

Returns:

Width of the Tendon.

Return type:

föoat

property stiffness: float#

A positive value generates a spring force (linear in position) acting along the Tendon.

Returns:

Stiffness of the Tendon

Return type:

float

property damping: float#

A positive value generates a damping force (linear in velocity) acting along the tendon. If possible, Joint.damping should be used for stability.

Returns:

Tendon dampin.

Return type:

float

property armature: float#

Setting this attribute to a positive value \(m\) adds a kinetic energy term \(\frac{1}{2}mv^2\), where \(v\) is the tendon velocity.

Returns:

Inertia associated with changes in Tendon length

Return type:

float

class blueprints.tendon.Path[source]#

Bases: PathType

Paths are used to specify the <pulley> element in Mujoco. A path can be strung along multiple Sites, Joints and Geoms. Every path must start and end with a Site and every Geom must be sandwitched between two Sites.

__init__(tendon)[source]#

Warning

This class is not to be instantiated by the user directly. Use the contextmanager Tendon.__enter__() instead (see above).

bind(*things, side_site=None, coef=None)[source]#

Binding is performed in order. If a side_site is specified only one Geom to be bound can be passed.

Parameters:
  • *things (list [ blue.ThingType ]) – The Sites and Geoms to be bound to a Tendon path

  • side_site (blue.SiteType | None) – A side Site for a Geom specifies to which side of the geom the Tendon should snap out if passing through the Geom initially.

  • coef (int | float | list [ int | float] | None) – Numeric coefficients for Joints. The argument must have the same shape as *things.

Return type:

None

split(number)[source]#

If a pulley should split the Tendon into \(n\) Paths, this method handles the split. A split Path does not necessarily start at the last element of its parent Path. Once split, a Path can no longer bind Things.

Parameters:

number (int) – The number of new Paths

Returns:

The new Paths connected through a pulley

Return type:

tuple [ blue.PathType ]