import os
import sys
import numpy as np
import xml.etree.ElementTree as xml
import blueprints as blue
[docs]
class BaseGeom(blue.GeomType, blue.thing.NodeThing, blue.thing.MoveableThing, blue.thing.ColoredThing):
"""
Geoms introduce physical matter into the simulation. For different Geom shapes individual
Geom classes are available, with shape depended size attributes. All other attributes are
specified in :class:`BaseGeom`.
.. note::
Though units are not bound to one specific system of measurements, it is highly recommended
to use `MKS <https://en.wikipedia.org/wiki/MKS_system_of_units>`__, since all defaults are
defined based on meters, kilograms and seconds and most mujoco models online also use the
metric system. If you want to switch from the imperial to the metric system the following resource might be
of interest to `convert units from imperial to metric <https://xkcd.com/526/>`__. If you insist on
using imperial units, take a look at the following resource to `convert units from metric to imperial <https://rick.nerial.uk/>`__.
Most attribute descriptions are partially taken from `Mujoco <https://mujoco.readthedocs.io/en/latest/XMLreference.html#body-geom>`__.
"""
[docs]
@blue.restrict
def __init__(self,
pos: np.ndarray|list[int|float] = [0., 0., 0.],
alpha: int|float = 0,
beta: int|float = 0,
gamma: int|float = 0,
material: blue.MaterialType|None = None,
mass: int|float|None = None,
density: int|float = 1000.,
margin: int|float = 0.0,
gap: int|float = 0.0,
sliding_friction: int|float = 1,
torsional_friction: int|float = 0.005,
rolling_friction: int|float = 0.0001,
shellinertia: bool|None = None,
color: object|None = None,
name: str|None = None,
x: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
y: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
z: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
quat: list[int|float]|np.ndarray|None = None,
**kwargs):
"""
Parameters
----------
pos : list[int | float] | np.ndarray | None, optional
Represents the position of the object. Changing this attribute also changes the properties :attr:`x`, :attr:`y` and :attr:`z`.
alpha : int | float | None, optional
(Improper) euler angle of rotation around the x-axis in radian. Changing this value also changes the :attr:`euler` property.
beta : int | float | None, optional
(Improper) euler angle of rotation around the y-axis in radian. Changing this value also changes the :attr:`euler` property.
gamma : int | float | None, optional
(Improper) euler angle of rotation around the z-axis in radian. Changing this value also changes the :attr:`euler` property.
material : blue.MaterialType, optional
The :class:`Material <blueprints.material.Material>` of the Geom.
:class:`Textures <blueprints.texture.BaseTexture> are applied via Materials.
density : int | float | None, optional
Material density used to compute the geom mass and inertia. The computation is based on
the geom shape and the assumption of uniform density. The internal default of 1000 is the
density of water in SI units. This attribute is used only when the mass attribute above is
unspecified.
margin : int | float | None, optional
A contact is considered active only if the distance between the two geom surfaces is below
margin-gap.
gap : int | float | None, optional
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are
ignored by the constraint solver but are included in mjData.contact for the purpose of custom
computations. When this value is positive, geom distances between margin and margin-gap
correspond to such inactive contacts.
mass : int | float | None, optional
If this attribute is specified, the density attribute below is ignored and the geom density
is computed from the given mass, using the geom shape and the assumption of uniform density.
The computed density is then used to obtain the geom inertia. Recall that the geom mass and
inertia are only used during compilation, to infer the body mass and inertia if necessary.
shellinertia : bool | None, optional
If true, the geom’s inertia is computed assuming that all the mass is concentrated on the
boundary. In this case density is interpreted as surface density rather than volumetric
density.
sliding_friction : int | float, optional
Friction parameter for sliding used to compute the forces on contact pairs.
torsional_friction : int | float, optional
Friction parameter for torsion used to compute the forces on contact pairs.
rolling_friction : int | float, optional
Friction parameter for rolling used to compute the forces on contact pairs.
color : blue.ColorType
The color of the Geom. For a detailed description see :class:`Color `
name : str | None, optional
The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme.
x : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the X position coordinate.
y : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Y position coordinate.
z : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Z position coordinate.
quat: list [ int | float ] | np.ndarray | None, optional
If set, the quaternion orientation overwrites the euler angles ``alpha``, ``beta`` and ``gamma``.
**kwargs
Keyword arguments are passed to ``super().__init__``.
"""
self.mass = mass # float(mass) if mass is not None else mass
self.density = density
self.shellinertia = shellinertia
self.margin = margin
self.gap = gap
self.sliding_friction = sliding_friction
self.torsional_friction = torsional_friction
self.rolling_friction = rolling_friction
# MATERIAL
self.material = material
# PSEUDO CHILDREN
self._tendons = []
self._CHILDREN = {'tendons': {'type': blue.TendonType,
'children': self._tendons}}
# SETTING COLOR BLUEPRINT COLOR DEFAULT FOR GEOMS
color = color if color is not None else 'grey'
super().__init__(pos=pos,
alpha=alpha,
beta=beta,
gamma=gamma,
name=name,
color=color,
x=x,
y=y,
z=z,
quat=quat,
**kwargs)
@blue.restrict
def _build(self, parent, world, indicies, **kwargs):
"""
This method is called to build the xml.
Parameters
----------
parent : xml.etree.ElementTree.Element
The xml element of its parent
world : WorldType
The World from which the build method was called initially
indicies : dict
All indecies used for retrieving data during the simulation.
Returns
-------
xml.etree.ElementTree.Element
The builded xml element of the Thing.
"""
self._index = indicies['geom']
indicies['geom'] += 1
if self.material is not None:
self.material._build(parent, world, indicies, **kwargs)
kwargs['material'] = self.material.asset.name
return super()._build(parent, world, indicies, **kwargs)
@blue.restrict
@classmethod
def _from_xml_element(cls,
xml_element: xml.Element,
material: blue.MaterialType|None = None,
**kwargs) -> blue.ThingType:
# Strip material name from XML before parsing — we inject the object directly
mat_attr = xml_element.get('material')
if mat_attr is not None:
xml_element = xml.Element(xml_element.tag, {k: v for k, v in xml_element.items() if k != 'material'})
init_args, post_args, rest_args = cls._xml_element_args(xml_element)
init_args['copy'] = False
if material is not None:
init_args['material'] = material
init_args.update(kwargs)
obj = object.__new__(cls)
obj.__init__(**init_args)
for key, val in post_args.items():
setattr(obj, key, val)
return obj
@property
def type(self) -> str:
"""
This is a derived attribute, which is used to specify the ``type`` attribute in the
mujoco ``geom`` tag.
Returns
-------
str
The type is the lower case name of the Geom class.
"""
return self.__class__.__name__.lower()
@property
def material(self):
"""
Materials can be used to specify reflective properties of a Geom,
as well as to apply :class:`Textures <blueprints.texture.BaseTexture>` to them.
Returns
-------
blue.MaterialType
The Material of the Geom
"""
return self._material
@material.setter
@blue.restrict
def material(self, material: blue.MaterialType|None) -> None:
self._material = material.copy() if material is not None else material
@property
def friction(self) -> np.ndarray:
"""
Contact friction parameters for dynamically generated contact pairs. The first number is
the sliding friction, acting along both axes of the tangent plane. The second number is the
torsional friction, acting around the contact normal. The third number is the rolling
friction, acting around both axes of the tangent plane.
Returns
-------
np.ndarray
Individual components are found in :attr:`sliding_friction`, :attr:`torsional_friction`
and :attr:`rolling_friction`.
"""
return np.array([self.sliding_friction,
self.torsional_friction,
self.rolling_friction], dtype=np.float32)
@friction.setter
@blue.restrict
def friction(self, friction: np.ndarray|list[int|float]):
"""
Contact friction parameters for dynamically generated contact pairs. The first number is
the sliding friction, acting along both axes of the tangent plane. The second number is the
torsional friction, acting around the contact normal. The third number is the rolling
friction, acting around both axes of the tangent plane.
Parameters
----------
friction : np.ndarray | list[int | float]
Individual components are found in :attr:`sliding_friction`, :attr:`torsional_friction`
and :attr:`rolling_friction`.
"""
self.sliding_friction = friction[0]
self.torsional_friction = friction[1]
self.rolling_friction = friction[2]
@property
def mass(self) -> float:
"""
If this attribute is specified, the density attribute below is ignored and the geom density
is computed from the given mass, using the geom shape and the assumption of uniform density.
The computed density is then used to obtain the geom inertia. Recall that the geom mass and
inertia are only used during compilation, to infer the body mass and inertia if necessary.
Unit defaults are set in SI units, but the physical properties can in principle be
interpreted in any system of measurements.
Returns
-------
float
The default unit convention is kilogram.
"""
return self._mass
@mass.setter
@blue.restrict
def mass(self, mass: int|float|None) -> None:
"""
If this attribute is specified, the density attribute below is ignored and the geom density
is computed from the given mass, using the geom shape and the assumption of uniform density.
The computed density is then used to obtain the geom inertia. Recall that the geom mass and
inertia are only used during compilation, to infer the body mass and inertia if necessary.
Unit defaults are set in SI units, but the physical properties can in principle be
interpreted in any system of measurements.
Parameters
----------
mass : int | float | None
The default unit convention is kilogram.
"""
self._mass = float(mass) if mass is not None else self._DEFAULT_VALS()['mass']
@property
def density(self) -> float:
"""
Unit defaults are set in SI units, but the physical properties can in principle be
interpreted in any system of measurements.
Material density used to compute the geom mass and inertia. The computation is based on
the geom shape and the assumption of uniform density. The internal default of 1000 is the
density of water in SI units. This attribute is used only when the mass attribute above is
unspecified.
Returns
-------
float
Density is either volumetric if :attr:`shellinertia` is false, otherwise its planar.
"""
return self._density
@density.setter
@blue.restrict
def density(self, density: int|float|None) -> None:
"""
Unit defaults are set in SI units, but the physical properties can in principle be
interpreted in any system of measurements.
Material density used to compute the geom mass and inertia. The computation is based on
the geom shape and the assumption of uniform density. The internal default of 1000 is the
density of water in SI units. This attribute is used only when the mass attribute above is
unspecified.
Parameters
----------
density : int | float | None
Density is either volumetric if :attr:`shellinertia` is false, otherwise its planar.
"""
self._density = float(density)
@property
def shellinertia(self) -> bool:
"""
If shellinertia is True all mass will be distributed along the surface of the object.
Changing this attribute also changes the effect of :attr:`density` from volumetric to planar.
Returns
-------
bool
Indicates whether mass is concentrated on the surface.
"""
return self._shellinertia
@shellinertia.setter
@blue.restrict
def shellinertia(self, shellinertia: bool|None) -> None:
"""
If shellinertia is True all mass will be distributed along the surface of the object.
Changing this attribute also changes the effect of :attr:`density` from volumetric to planar.
Parameters
----------
shellinertia : bool | None
Indicates whether mass is concentrated on the surface.
"""
self._shellinertia = shellinertia
@property
def margin(self) -> float:
"""
Margin defines the minimum distance used to detect a contact.
Returns
-------
float
Default units are interpreted in meters.
"""
return self._margin
@margin.setter
@blue.restrict
def margin(self, margin: int|float) -> None:
"""
Margin defines the minimum distance used to detect a contact.
Parameters
----------
margin : int | float
Default units are interpreted in meters.
"""
self._margin = float(margin)
@property
def gap(self) -> float:
"""
Gap defines the gap on minimum distance used to detect a contact.
Returns
-------
float
Default units are interpreted in meters.
"""
return self._gap
@gap.setter
@blue.restrict
def gap(self, gap: int|float) -> None:
"""
Gap defines the gap on minimum distance used to detect a contact.
Parameters
----------
gap : int | float
Default units are interpreted in meters.
"""
self._gap = float(gap)
@property
def sliding_friction(self) -> float:
"""
Friction parameter for sliding used to computed the forces on contact pairs.
Returns
-------
float
First component of :attr:`friction`.
"""
return self._sliding_friction
@sliding_friction.setter
@blue.restrict
def sliding_friction(self, sliding_friction: int|float) -> None:
"""
Friction parameter for sliding used to computed the forces on contact pairs.
Parameters
----------
sliding_friction : int | float
First component of :attr:`friction`.
"""
self._sliding_friction = float(sliding_friction)
@property
def torsional_friction(self) -> int|float:
"""
Friction parameter for torsion used to computed the forces on contact pairs.
Returns
-------
float
Second component of :attr:`friction`.
"""
return self._torsional_friction
@torsional_friction.setter
@blue.restrict
def torsional_friction(self, torsional_friction: int|float) -> None:
"""
Friction parameter for torsion used to computed the forces on contact pairs.
Parameters
----------
torsional_friction : int | float
Second component of :attr:`friction`.
"""
self._torsional_friction = float(torsional_friction)
@property
def rolling_friction(self) -> int|float:
"""
Friction parameter for rolling used to computed the forces on contact pairs.
Returns
-------
float
Third component of :attr:`friction`.
"""
return self._rolling_friction
@rolling_friction.setter
@blue.restrict
def rolling_friction(self, rolling_friction: int|float) -> None:
"""
Friction parameter for rolling used to computed the forces on contact pairs.
Parameters
----------
rolling_friction : int | float
Third component of :attr:`friction`.
"""
self._rolling_friction = float(rolling_friction)
[docs]
class Capsule(blue.CapsuleGeomType, blue.tube.BaseTube, BaseGeom):
"""
Capsules consist of a cylinder with two half spheres at its ends. It can either be constructed from
its standard constructor for position, orientation, radius and length, or with the :meth:`blueprints.tube.BaseTube`
constructor. For a detailed description see :class:`blueprints.tube.BaseTube`.
.. code-block:: python
:caption: Standard constructor:
>>> geom = blue.geoms.Capsule(alpha=TAU/8, length=3)
>>> geom.head
array([ 0. 1.06066017 -1.06066017], dtype=np.float32)
>>> geom.tail
array([ 0. -1.06066017 1.06066017], dtype=np.float32)
.. code-block:: python
:caption: From points constructor:
>>> geom = blue.geoms.Capsule.from_points([0, -1, 4], [2, 1, 0], radius=0.5)
>>> geom.pos
array([1. 0. 2.], dtype=np.float32)
>>> geom.alpha, geom.beta, geom.gamma
-2.6779450445889874 0.420534335283965 0.6847192030022825
>>> geom.length
4.898979485566356
Attributes
----------
head : np.ndarra
The first end of this Geoms. If it was defined by the :meth:`blueprints.tube.BaseTube` constructor,
``head`` is the first argument given.
tail : np.ndarra
The second end of this Geoms. If it was defined by the :meth:`blueprints.tube.BaseTube` constructor,
``tail`` is the second argument given.
"""
[docs]
@blue.restrict
def __init__(self,
pos: np.ndarray|list[int|float] = [0., 0., 0.],
radius: int|float = 1.,
length: int|float = 1.,
alpha: int|float = 0.,
beta: int|float = 0.,
gamma: int|float = 0.,
margin: int|float = 0.0,
gap: int|float = 0.0,
material: blue.MaterialType|None = None,
color: object|None = None,
name: str|None = None,
x: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
y: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
z: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
quat: list[int|float]|np.ndarray|None = None,
**kwargs):
"""
Parameters
----------
pos : list[int | float] | np.ndarray | None, optional
Represents the position of the object. Changing this attribute also changes the properties :attr:`x`, :attr:`y` and :attr:`z`.
radius : int | float, optional
Represents the radius of the cylinder and the two half spheres.
length : int | float, optional
The length of the cylinder.
alpha : int | float | None, optional
(Improper) euler angle of rotation around the x-axis in radian. Changing this value also changes the :attr:`euler` property.
beta : int | float | None, optional
(Improper) euler angle of rotation around the y-axis in radian. Changing this value also changes the :attr:`euler` property.
gamma : int | float | None, optional
(Improper) euler angle of rotation around the z-axis in radian. Changing this value also changes the :attr:`euler` property.
margin : int | float | None, optional
A contact is considered active only if the distance between the two geom surfaces is below
margin-gap.
gap : int | float | None, optional
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are
ignored by the constraint solver but are included in mjData.contact for the purpose of custom
computations. When this value is positive, geom distances between margin and margin-gap
correspond to such inactive contacts.
material : blue.MaterialType, optional
The :class:`Material <blueprints.material.Material>` of the Geom.
:class:`Textures <blueprints.texture.BaseTexture> are applied via Materials.
color : object
The color of the Geom. See :class:`Color <blueprints.thing.colored.Color>` for a detailed description.
name : str | None, optional
The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme.
x : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the X position coordinate.
y : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Y position coordinate.
z : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Z position coordinate.
quat: list [ int | float ] | np.ndarray | None, optional
If set, the quaternion orientation overwrites the euler angles ``alpha``, ``beta`` and ``gamma``.
**kwargs
Keyword arguments are passed to ``super().__init__``.
"""
self.radius = float(radius)
self.length = float(length)
super().__init__(pos=pos,
alpha=alpha,
beta=beta,
gamma=gamma,
material=material,
margin=margin,
gap=gap,
color=color,
name=name,
x=x,
y=y,
z=z,
quat=quat,
**kwargs)
@property
def size(self) -> np.ndarray:
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Returns
-------
np.ndarray
The size defines the radius and half length of the capsule.
"""
return np.array([self.radius,
self.length/2], dtype=np.float32)
@size.setter
@blue.restrict
def size(self, size: np.ndarray|list[int|float]):
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Parameters
----------
size : np.ndarray | list[int | float]
The size defines the radius and half length of the capsule.
"""
self.radius = float(size[0])
self.length = float(size[1]) * 2
[docs]
class Cylinder(blue.CylinderGeomType, blue.tube.BaseTube, BaseGeom):
"""
Cylinders can either be constructed from their standard constructor for position, orientation,
radius and length, or with the :meth:`blueprints.tube.BaseTube` constructor. For a detailed description
see :class:`blueprints.tube.BaseTube`.
.. code-block:: python
:caption: Standard constructor:
>>> geom = blue.geoms.Cylinder(alpha=TAU/8, length=3)
>>> geom.head
array([ 0. 1.06066017 -1.06066017], dtype=np.float32)
>>> geom.tail
array([ 0. -1.06066017 1.06066017], dtype=np.float32)
.. code-block:: python
:caption: From points constructor:
>>> geom = blue.geoms.Cylinder.from_points([0, -1, 4], [2, 1, 0], radius=0.5)
>>> geom.pos
array([1. 0. 2.], dtype=np.float32)
>>> geom.alpha, geom.beta, geom.gamma
-2.6779450445889874 0.420534335283965 0.6847192030022825
>>> geom.length
4.898979485566356
Attributes
----------
head : np.ndarra
The first end of this Geoms. If it was defined by the :meth:`blueprints.tube.BaseTube` constructor,
``head`` is the first argument given.
tail : np.ndarra
The second end of this Geoms. If it was defined by the :meth:`blueprints.tube.BaseTube` constructor,
``tail`` is the second argument given.
"""
[docs]
@blue.restrict
def __init__(self,
pos: np.ndarray|list[int|float] = [0., 0., 0.],
radius: int|float = 1.,
length: int|float = 1.,
alpha: int|float = 0.,
beta: int|float = 0.,
gamma: int|float = 0.,
margin: int|float = 0.0,
gap: int|float = 0.0,
material: blue.MaterialType|None = None,
color: object|None = None,
name: str|None = None,
x: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
y: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
z: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
quat: list[int|float]|np.ndarray|None = None,
**kwargs):
"""
Parameters
----------
pos : list[int | float] | np.ndarray | None, optional
Represents the position of the object. Changing this attribute also changes the properties :attr:`x`, :attr:`y` and :attr:`z`.
radius : int | float, optional
Represents the radius of the cylinder and the two half spheres.
length : int | float, optional
The length of the cylinder.
alpha : int | float | None, optional
(Improper) euler angle of rotation around the x-axis in radian. Changing this value also changes the :attr:`euler` property.
beta : int | float | None, optional
(Improper) euler angle of rotation around the y-axis in radian. Changing this value also changes the :attr:`euler` property.
gamma : int | float | None, optional
(Improper) euler angle of rotation around the z-axis in radian. Changing this value also changes the :attr:`euler` property.
margin : int | float | None, optional
A contact is considered active only if the distance between the two geom surfaces is below
margin-gap.
gap : int | float | None, optional
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are
ignored by the constraint solver but are included in mjData.contact for the purpose of custom
computations. When this value is positive, geom distances between margin and margin-gap
correspond to such inactive contacts.
material : blue.MaterialType, optional
The :class:`Material <blueprints.material.Material>` of the Geom.
:class:`Textures <blueprints.texture.BaseTexture> are applied via Materials.
color : object
The color of the Geom. See :class:`Color <blueprints.thing.colored.Color>` for a detailed description.
name : str | None, optional
The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme.
x : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the X position coordinate.
y : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Y position coordinate.
z : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Z position coordinate.
quat: list [ int | float ] | np.ndarray | None, optional
If set, the quaternion orientation overwrites the euler angles ``alpha``, ``beta`` and ``gamma``.
**kwargs
Keyword arguments are passed to ``super().__init__``.
"""
self.radius = float(radius)
self.length = float(length)
super().__init__(pos=pos,
alpha=alpha,
beta=beta,
gamma=gamma,
margin=margin,
gap=gap,
material=material,
color=color,
name=name,
x=x,
y=y,
z=z,
quat=quat,
**kwargs)
@property
def size(self) -> np.ndarray:
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Returns
-------
np.ndarray
The size defines the radius and half length of the cylinder.
"""
return np.array([self.radius,
self.length/2], dtype=np.float32)
@size.setter
@blue.restrict
def size(self, size: np.ndarray|list[int|float]):
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Parameters
----------
size : np.ndarray | list[int | float]
The size defines the radius and half length of the cylinder.
"""
self.radius = float(size[0])
self.length = float(size[1]) * 2
[docs]
class Box(blue.BoxGeomType, blue.tube.BaseTube, BaseGeom):
"""
Boxes can either be constructed from their standard constructor for position, orientation,
and x-, y-, z-length, or with the :meth:`blueprints.tube.BaseTube` constructor. For a detailed
description see :class:`blueprints.tube.BaseTube`.
.. code-block:: python
:caption: Standard constructor:
>>> geom = blue.geoms.Box(x_length=2, y_length=4, z_length=1)
>>> geom.head
array([ 0. 0. -0.5], dtype=np.float32)
>>> geom.tail
array([ 0. 0. 0.5], dtype=np.float32)
.. code-block:: python
:caption: From points constructor:
>>> geom = blue.geoms.Box.from_points([4, 0,-2], [2, 4, 0], radius=0.5)
>>> geom.pos
array([ 3. 2. -1.], dtype=np.float32)
>>> geom.alpha, geom.beta, geom.gamma
-1.1071487177940904 -0.4205343352839653 0.20135792079032988
>>> geom.x_length, geom.y_length, geom.z_length
0.5 0.5 4.898979485566356
Attributes
----------
head : np.ndarra
The first end of this Geoms. If it was defined by the :meth:`blueprints.tube.BaseTube` constructor,
``head`` is the first argument given.
tail : np.ndarra
The second end of this Geoms. If it was defined by the :meth:`blueprints.tube.BaseTube` constructor,
``tail`` is the second argument given.
"""
[docs]
@blue.restrict
def __init__(self,
pos: np.ndarray|list[int|float] = [0., 0., 0.],
x_length: int|float = 1.,
y_length: int|float = 1.,
z_length: int|float = 1.,
alpha: int|float = 0.,
beta: int|float = 0.,
gamma: int|float = 0.,
margin: int|float = 0.0,
gap: int|float = 0.0,
material: blue.MaterialType|None = None,
color: object|None = None,
name: str|None = None,
x: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
y: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
z: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
quat: list[int|float]|np.ndarray|None = None,
**kwargs):
"""
Parameters
----------
pos : list[int | float] | np.ndarray | None, optional
Represents the position of the object. Changing this attribute also changes the properties :attr:`x`, :attr:`y` and :attr:`z`.
x_length : int | float, optional
The true length of the Box along the x-axis.
y_length : int | float, optional
The true length of the Box along the y-axis.
z_length : int | float, optional
The true length of the Box along the z-axis.
alpha : int | float | None, optional
(Improper) euler angle of rotation around the x-axis in radian. Changing this value also changes the :attr:`euler` property.
beta : int | float | None, optional
(Improper) euler angle of rotation around the y-axis in radian. Changing this value also changes the :attr:`euler` property.
gamma : int | float | None, optional
(Improper) euler angle of rotation around the z-axis in radian. Changing this value also changes the :attr:`euler` property.
margin : int | float | None, optional
A contact is considered active only if the distance between the two geom surfaces is below
margin-gap.
gap : int | float | None, optional
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are
ignored by the constraint solver but are included in mjData.contact for the purpose of custom
computations. When this value is positive, geom distances between margin and margin-gap
correspond to such inactive contacts.
material : blue.MaterialType, optional
The :class:`Material <blueprints.material.Material>` of the Geom.
:class:`Textures <blueprints.texture.BaseTexture> are applied via Materials.
color : object
The color of the Geom. See :class:`Color <blueprints.thing.colored.Color>` for a detailed description.
name : str | None, optional
The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme.
x : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the X position coordinate.
y : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Y position coordinate.
z : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Z position coordinate.
quat: list [ int | float ] | np.ndarray | None, optional
If set, the quaternion orientation overwrites the euler angles ``alpha``, ``beta`` and ``gamma``.
**kwargs
Keyword arguments are passed to ``super().__init__``.
"""
self.x_length = float(x_length)
self.y_length = float(y_length)
self.z_length = float(z_length)
super().__init__(pos=pos,
alpha=alpha,
beta=beta,
gamma=gamma,
margin=margin,
gap=gap,
material=material,
color=color,
name=name,
x=x,
y=y,
z=z,
quat=quat,
**kwargs)
@property
def size(self) -> np.ndarray:
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Returns
-------
np.ndarray
The size defines the half lengths of the Box.
"""
return np.array([self.x_length/2,
self.y_length/2,
self.z_length/2], dtype=np.float32)
@size.setter
@blue.restrict
def size(self, size: np.ndarray|list[int|float]):
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Parameters
----------
size : np.ndarray | list[int | float]
The size defines the half lengths of the Box.
"""
self.x_length = float(size[0]) * 2
self.y_length = float(size[1]) * 2
self.z_length = float(size[2]) * 2
[docs]
class Plane(blue.PlaneGeomType, BaseGeom):
"""
Planes can either be finite or infinite. They are normal to the Z-axis of their frame of reference
and the outside of the plane lies in the positive Z-axis. The inside of the plane is rendered semi
transparent. Finite Planes are rendered as rectangles with size specified in :attr:`x_length`
:attr:`y_length`. If those size parameters are set to zero or are set negative, the plain is instead
infinite. The :attr:`spacing` defines the size of grid subdivisions which are used in rendering.
"""
[docs]
@blue.restrict
def __init__(self,
pos: np.ndarray|list[int|float] = [0., 0., 0.],
x_length: int|float = 0.,
y_length: int|float = 0.,
spacing: int|float = 1.,
alpha: int|float = 0.,
beta: int|float = 0.,
gamma: int|float = 0.,
margin: int|float = 0.0,
gap: int|float = 0.0,
material: blue.MaterialType|None = None,
color: object|None = None,
name: str|None = None,
x: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
y: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
z: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
quat: list[int|float]|np.ndarray|None = None,
**kwargs):
"""
Parameters
----------
pos : list[int | float] | np.ndarray | None, optional
Represents the position of the object. Changing this attribute also changes the properties :attr:`x`, :attr:`y` and :attr:`z`.
x_length : int | float, optional
Length of the X-axis side. If both lengths are set to zero or negative the Plane is infinite.
y_length : int | float, optional
Length of the Y-axis side. If both lengths are set to zero or negative the Plane is infinite.
spacing : int | float, optional
Spacing between the grid subdivisions.
alpha : int | float | None, optional
(Improper) euler angle of rotation around the x-axis in radian. Changing this value also changes the :attr:`euler` property.
beta : int | float | None, optional
(Improper) euler angle of rotation around the y-axis in radian. Changing this value also changes the :attr:`euler` property.
gamma : int | float | None, optional
(Improper) euler angle of rotation around the z-axis in radian. Changing this value also changes the :attr:`euler` property.
margin : int | float | None, optional
A contact is considered active only if the distance between the two geom surfaces is below
margin-gap.
gap : int | float | None, optional
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are
ignored by the constraint solver but are included in mjData.contact for the purpose of custom
computations. When this value is positive, geom distances between margin and margin-gap
correspond to such inactive contacts.
material : blue.MaterialType, optional
The :class:`Material <blueprints.material.Material>` of the Geom.
:class:`Textures <blueprints.texture.BaseTexture> are applied via Materials.
color : object
The color of the Geom. See :class:`Color <blueprints.thing.colored.Color>` for a detailed description.
name : str | None, optional
The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme.
x : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the X position coordinate.
y : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Y position coordinate.
z : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Z position coordinate.
quat: list [ int | float ] | np.ndarray | None, optional
If set, the quaternion orientation overwrites the euler angles ``alpha``, ``beta`` and ``gamma``.
**kwargs
Keyword arguments are passed to ``super().__init__``.
"""
self.x_length = float(x_length)
self.y_length = float(y_length)
self.spacing = float(spacing)
super().__init__(pos=pos,
alpha=alpha,
beta=beta,
gamma=gamma,
margin=margin,
gap=gap,
material=material,
color=color,
name=name,
x=x,
y=y,
z=z,
quat=quat,
**kwargs)
@property
def size(self) -> np.ndarray:
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Returns
-------
np.ndarray
The first two components are half lengths for the X-axis and the Y-axis and the third is the spacing between grid subdivisions.
"""
return np.array([self.x_length/2,
self.y_length/2,
self.spacing], dtype=np.float32)
@size.setter
@blue.restrict
def size(self, size: np.ndarray|list[int|float]):
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Parameters
----------
size : np.ndarray | list[int | float]
The first two components are half lengths for the X-axis and the Y-axis and the third is the spacing between grid subdivisions.
"""
self.x_length = float(size[0]) * 2
self.y_length = float(size[1]) * 2
self.spacing = float(size[2])
[docs]
class Sphere(blue.SphereGeomType, BaseGeom):
"""
Spheres are defined via radius and position.
Attributes
----------
radius : float
The radius of the sphere
"""
[docs]
@blue.restrict
def __init__(self,
pos: np.ndarray|list[int|float] = [0., 0., 0.],
radius: int|float = 1.,
alpha: int|float = 0.,
beta: int|float = 0.,
gamma: int|float = 0.,
margin: int|float = 0.0,
gap: int|float = 0.0,
material: blue.MaterialType|None = None,
color: object|None = None,
name: str|None = None,
x: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
y: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
z: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
quat: list[int|float]|np.ndarray|None = None,
**kwargs):
"""
Parameters
----------
pos : list[int | float] | np.ndarray | None, optional
Represents the position of the Thing. Changing this attribute also changes the properties :attr:`x`, :attr:`y` and :attr:`z`.
radius : int | float, optional
The radius of the Sphere.
alpha : int | float | None, optional
(Improper) euler angle of rotation around the x-axis in radian. Changing this value also changes the :attr:`euler` property.
beta : int | float | None, optional
(Improper) euler angle of rotation around the y-axis in radian. Changing this value also changes the :attr:`euler` property.
gamma : int | float | None, optional
(Improper) euler angle of rotation around the z-axis in radian. Changing this value also changes the :attr:`euler` property.
margin : int | float | None, optional
A contact is considered active only if the distance between the two geom surfaces is below
margin-gap.
gap : int | float | None, optional
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are
ignored by the constraint solver but are included in mjData.contact for the purpose of custom
computations. When this value is positive, geom distances between margin and margin-gap
correspond to such inactive contacts.
material : blue.MaterialType, optional
The :class:`Material <blueprints.material.Material>` of the Geom.
:class:`Textures <blueprints.texture.BaseTexture> are applied via Materials.
color : object
The color of the Geom. See :class:`Color <blueprints.thing.colored.Color>` for a detailed description.
name : str | None, optional
The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme.
x : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the X position coordinate.
y : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Y position coordinate.
z : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Z position coordinate.
quat: list [ int | float ] | np.ndarray | None, optional
If set, the quaternion orientation overwrites the euler angles ``alpha``, ``beta`` and ``gamma``.
**kwargs
Keyword arguments are passed to ``super().__init__``.
"""
self.radius = float(radius)
super().__init__(pos=pos,
alpha=alpha,
beta=beta,
gamma=gamma,
margin=margin,
gap=gap,
material=material,
color=color,
name=name,
x=x,
y=y,
z=z,
quat=quat,
**kwargs)
@property
def size(self) -> np.ndarray:
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Returns
-------
np.ndarray
The only component of size is the radius, which is interpreted as meters by default parameters and convention.
"""
return np.array([self.radius])
@size.setter
@blue.restrict
def size(self, size: np.ndarray|list[int|float]):
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Parameters
----------
size : np.ndarray | list[int | float]
The only component of size is the radius, which is interpreted as meters by default parameters and convention.
"""
self.radius = float(size[0])
[docs]
class Ellipsoid(blue.EllipsoidGeomType, BaseGeom):
"""
Ellipsoids are defined with via :attr:´x_radius`, :attr:´y_radius` and :attr:´z_radius`. The surface of
the Ellipsoid contains the solutions to the equation:
:math:`\\left( \\frac{x}{r_x} \\right)^2 + \\left( \\frac{y}{r_y} \\right)^2 + \\left( \\frac{z}{r_z} \\right)^2 = 1`
Attributes
----------
x_radius : float
This attribute defines the radius for the X-axis.
y_radius : float
This attribute defines the radius for the Y-axis.
z_radius : float
This attribute defines the radius for the Z-axis.
"""
[docs]
@blue.restrict
def __init__(self,
pos: np.ndarray|list[int|float] = [0., 0., 0.],
x_radius: int|float = 1.,
y_radius: int|float = 2.,
z_radius: int|float = 3.,
alpha: int|float = 0.,
beta: int|float = 0.,
gamma: int|float = 0.,
margin: int|float = 0.0,
gap: int|float = 0.0,
material: blue.MaterialType|None = None,
color: object|None = None,
name: str|None = None,
x: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
y: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
z: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
quat: list[int|float]|np.ndarray|None = None,
**kwargs):
"""
Parameters
----------
pos : list[int | float] | np.ndarray | None, optional
Represents the position of the object. Changing this attribute also changes the properties :attr:`x`, :attr:`y` and :attr:`z`.
x_radius : int | float, optional
This attribute defines the radius for the X-axis.
y_radius : int | float, optional
This attribute defines the radius for the Y-axis.
z_radius : int | float, optional
This attribute defines the radius for the Z-axis.
alpha : int | float | None, optional
(Improper) euler angle of rotation around the x-axis in radian. Changing this value also changes the :attr:`euler` property.
beta : int | float | None, optional
(Improper) euler angle of rotation around the y-axis in radian. Changing this value also changes the :attr:`euler` property.
gamma : int | float | None, optional
(Improper) euler angle of rotation around the z-axis in radian. Changing this value also changes the :attr:`euler` property.
margin : int | float | None, optional
A contact is considered active only if the distance between the two geom surfaces is below
margin-gap.
gap : int | float | None, optional
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are
ignored by the constraint solver but are included in mjData.contact for the purpose of custom
computations. When this value is positive, geom distances between margin and margin-gap
correspond to such inactive contacts.
material : blue.MaterialType, optional
The :class:`Material <blueprints.material.Material>` of the Geom.
:class:`Textures <blueprints.texture.BaseTexture> are applied via Materials.
color : object
The color of the Geom. See :class:`Color <blueprints.thing.colored.Color>` for a detailed description.
name : str | None, optional
The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme.
x : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the X position coordinate.
y : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Y position coordinate.
z : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Z position coordinate.
quat: list [ int | float ] | np.ndarray | None, optional
If set, the quaternion orientation overwrites the euler angles ``alpha``, ``beta`` and ``gamma``.
**kwargs
Keyword arguments are passed to ``super().__init__``.
"""
self.x_radius = float(x_radius)
self.y_radius = float(y_radius)
self.z_radius = float(z_radius)
super().__init__(pos=pos,
alpha=alpha,
beta=beta,
gamma=gamma,
margin=margin,
gap=gap,
material=material,
color=color,
name=name,
x=x,
y=y,
z=z,
quat=quat,
**kwargs)
@property
def size(self) -> np.ndarray:
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Returns
-------
np.ndarray
The components contain the :attr:`x_radius`, :attr:`y_radius` and :attr:`z_radius` attribute.
"""
return np.array([self.x_radius,
self.y_radius,
self.z_radius], dtype=np.float32)
@size.setter
@blue.restrict
def size(self, size: np.ndarray|list[int|float]):
"""
.. note::
In mujoco half lengths are used instead of true lengths of objects. This makes distance
calculations easier, but we find that it is overall more confusing then beneficial which
is why blueprints uses proper lengths. Conversion is handled in the background, such that
users only need to specify true lengths.
Parameters
----------
size : np.ndarray | list[int | float]
The components contain the :attr:`x_radius`, :attr:`y_radius` and :attr:`z_radius` attribute.
"""
self.x_radius = float(size[0])
self.y_radius = float(size[1])
self.z_radius = float(size[2])
[docs]
class Mesh(blue.MeshGeomType, BaseGeom):
"""
Meshes can be used to build more complex shapes either by specifying vertecies manually or by loading
mesh files directly.
>>> blue.geoms.Mesh(filename='utahteapot.stl')
Mesh<unnamed_mesh:1>
>>> vertecies = [[0, 0, 0],
[0, 2, 0],
[2, 2, 0],
[2, 0, 0],
[1, 1, 2]]
>>> mesh = blue.geoms.Mesh(vertecies=vertecies, centered=True)
>>> mesh.vertecies
[[-1. -1. -1.]
[-1. 1. -1.]
[ 1. 1. -1.]
[ 1. -1. -1.]
[ 0. 0. 1.]]
The vertex data is accessed through a :class:`blueprints.assets.MeshAsset` which
in turn stores the data in a :class:`blueprints.cache.MeshCache`. As long as no vertex data are
modified copies of the :class:`Mesh` will reference the same Asset to avoid redundancy, otherwise a
new Asset and or Cache is created. The creation of a new Asset is computationally fairly cheap, the
creation of a new Cache may come with significant costs if the mesh file is large.
Modifications of the following attributes might trigger the creation of a new Asset:
1. ``pos``
2. ``euler``
3. ``scale``
Modifications of the following attributes might trigger the creation of a new Cache:
1. ``size``
2. ``vertecies``
Raises
------
Exception
If neither a filename, vertecies or an asset are given an error is raised.
"""
[docs]
@blue.restrict
def __init__(self,
pos: np.ndarray|list[int|float] = [0., 0., 0.],
vertecies: list[np.ndarray|list[float|int]]|None = None,
filename: str|None = None,
centered: bool = True,
asset: blue.assets.MeshAsset|None = None,
material: blue.MaterialType|None = None,
margin: int|float = 0.0,
gap: int|float = 0.0,
x: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
y: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
z: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
**kwargs) -> None:
"""
Parameters
----------
pos : list[int | float] | np.ndarray | None, optional
Represents the position of the object. Changing this attribute also changes the properties :attr:`x`, :attr:`y` and :attr:`z`.
vertecies : list[np.ndarray | list[float | int]] | None, optional
Either a list of positions of vertecies or None.
filename : str | None, optional
The name for the file from which the Mesh data is loaded. The supported formats are ``.stl`` in binary and ``.obj`` in binary and ascii.
centered : bool, optional
If this argument is set, vertecies are centered around the references frames origin.
asset : blue.assets.MeshAsset | None, optional
A possible asset, from which the Mesh is constructed.
margin : int | float | None, optional
A contact is considered active only if the distance between the two geom surfaces is below
margin-gap.
gap : int | float | None, optional
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are
ignored by the constraint solver but are included in mjData.contact for the purpose of custom
computations. When this value is positive, geom distances between margin and margin-gap
correspond to such inactive contacts.
material : blue.MaterialType, optional
The :class:`Material <blueprints.material.Material>` of the Geom.
:class:`Textures <blueprints.texture.BaseTexture> are applied via Materials.
x : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the X position coordinate.
y : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Y position coordinate.
z : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Z position coordinate.
**kwargs
Keyword arguments are passed to ``super().__init__``.
Raises
------
Exception
If neither a filename, vertecies or an asset are given an error is raised.
"""
kwargs_nameless = kwargs.copy()
if 'name' in kwargs_nameless:
del kwargs_nameless['name']
if sum(x is not None for x in (vertecies, filename, asset)) > 1:
raise Exception('Not more than one argument (vertecies, filename or asset) is allowed to be None.')
if pos is None:
x = float(x) if x is not None else 0.
y = float(y) if y is not None else 0.
z = float(z) if z is not None else 0.
pos = np.array([x, y, z], dtype=np.float32)
if asset is not None:
self.asset = asset
elif filename is not None:
if not os.path.isfile(filename):
# TRY DIRNAME PREFIX TO RESOLVE RELATIVE REF TO MAIN.PY
filename = f'{os.path.dirname(sys.argv[0])}/{filename}'
if not os.path.isfile(filename):
raise Exception(f'File not found for path {filename}')
if not os.path.isabs(filename):
filename = os.path.abspath(filename)
if not os.path.isabs(filename):
path = os.path.abspath(os.path.dirname(sys.argv[0]))
filename = f'{path}/{filename}'
self.asset = blue.assets.MeshAsset(filename=filename,
pos=pos,
centered=centered,
xml_data=vertecies is not None,
**kwargs_nameless)
elif vertecies is not None:
self.asset = blue.assets.MeshAsset(vertecies=vertecies,
pos=pos,
centered=centered,
xml_data=True,
**kwargs_nameless)
else:
raise Exception('No asset could be constructed! Please provide either a filename, vertcies or an asset.')
# SEPARATE CHANGES FOR POST-FREEZE ASSIGNMENT
self.asset.freeze = True
kwchanges = {}
if len(self.asset._references) > 0:
proto_parent = next(iter(self.asset._references))
for key, val in kwargs.items():
if hasattr(proto_parent, key):
equal = getattr(proto_parent, key) == val
equal = bool(np.all(equal)) if isinstance(equal, np.ndarray) else equal
if not equal:
kwchanges[key] = val
kwargs[key] = getattr(proto_parent, key)
if not bool(np.all(proto_parent.pos == pos)):
kwchanges['pos'] = pos
pos = proto_parent.pos
self.asset._add(self)
#print(self.copy)
super().__init__(pos=pos,
material=material,
margin=margin,
gap=gap,
**kwargs)
self.asset.freeze = False
for key, val in kwchanges.items():
if key in self._BLUEPRINT_ATTR():
setattr(self, key, val)
@blue.restrict
def _build(self,
parent,
world,
indicies,
**kwargs):
"""
This method is called to build the xml.
Parameters
----------
parent : xml.etree.ElementTree.Element
The xml element of its parent
world : WorldType
The World from which the build method was called initially
Returns
-------
xml.etree.ElementTree.Element
The builded xml element of the Thing.
"""
if self.material is not None:
self.material._build(parent, world, indicies, **kwargs)
kwargs['material'] = self.material.asset.name
self._xml_root = xml.SubElement(parent,
self._MUJOCO_OBJ,
mesh=self.asset.name,
**self._mujoco_specs(kwargs))
if self.asset._built:
self._index = self.asset._index
else:
self._index = indicies['mesh']
indicies['mesh'] += 1
self.asset._build(parent=parent,
world=world,
indicies=indicies,
**kwargs)
return self._xml_root
@blue.restrict
@classmethod
def _from_xml_element(cls,
xml_element: xml.Element,
asset: blue.AssetType,
material: blue.MaterialType|None = None) -> blue.ThingType:
"""
This method reconstructs a Mesh from an xml element.
Parameters
----------
xml_element : xml.Element
The xml element from which a Mesh is reconstructed.
asset : blue.AssetType
The asset from which the Mesh takes its data.
material : blue.MaterialType | None, optional
The Material assigned to the Mesh.
Returns
-------
blue.ThingType
The reconstructed Mesh.
"""
# Strip material name from XML before parsing
if xml_element.get('material') is not None:
xml_element = xml.Element(xml_element.tag, {k: v for k, v in xml_element.items() if k != 'material'})
init_args, post_args, rest_args = cls._xml_element_args(xml_element)
geom_type = rest_args['type']
geom = object.__new__(blue.REGISTER.GEOM_THINGS[geom_type])
init_args['asset'] = asset
if material is not None:
init_args['material'] = material
geom.__init__(**init_args)
for key, val in post_args.items():
setattr(geom, key, val)
return geom
@property
def vertecies(self) -> list[np.ndarray]:
"""
Modifying this attribute will create a new :class:`blueprints.cache.MeshCache` object if
the previous Cache was the children of multiple parents. Otherwise the Cache get modified
directly.
Returns
-------
list[np.ndarray]
A list of all vertex positions.
"""
return self.asset.vertecies.copy()
@vertecies.setter
@blue.restrict
def vertecies(self, vertecies: np.ndarray|list[np.ndarray|list[float|int]]) -> None:
"""
Modifying this attribute will create a new :class:`blueprints.cache.MeshCache` object if
the previous Cache was the children of multiple parents. Otherwise the Cache get modified
directly.
Parameters
----------
vertecies : np.ndarray | list[np.ndarray | list[float | int]]
A list of all vertex positions.
"""
self.asset._prepare_for_modification(self)
self.asset.vertecies = vertecies
@property
def pos(self) -> np.ndarray:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Returns
-------
np.ndarray | list[float | int]
"""
return self.asset.pos.copy()
@pos.setter
@blue.restrict
def pos(self, pos: np.ndarray|list[float|int]) -> None:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Parameters
----------
pos : np.ndarray | list[float | int]
Description
"""
self.asset._prepare_for_modification(self)
self.asset.pos = pos
@property
def _pos(self) -> np.ndarray:
"""
Returns
-------
np.ndarray | list[float | int]
"""
return self.asset._pos.copy()
@_pos.setter
@blue.restrict
def _pos(self, pos: np.ndarray|list[float|int]) -> None:
"""
Parameters
----------
pos : np.ndarray | list[float | int]
"""
self.asset._prepare_for_modification(self)
self.asset._pos = pos
@property
def euler(self) -> np.ndarray:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Returns
-------
np.ndarray
"""
return self.asset.euler
@euler.setter
@blue.restrict
def euler(self, euler: np.ndarray|list[float|int]) -> None:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Parameters
----------
euler : np.ndarray | list[float | int]
Description
Deleted Parameters
------------------
vertex : list[np.ndarray | list[float | int]]
Description
pos : np.ndarray | list[float | int]
Description
"""
self.asset._prepare_for_modification(self)
self.asset.euler = euler
@property
def size(self) -> np.ndarray:
"""
Modifying this attribute will create a new :class:`blueprints.cache.MeshCache` object if
the previous Cache was the children of multiple parents. Otherwise the Cache get modified
directly.
.. note::
Altering this attribute also changes the :attr:`vertecies` attribute. To get the same
effect without changing the vertecies, see :attr:`scale`.
Returns
-------
np.ndarray
The size of the Mesh is calculated as the size of the Box with edges along the axes of
the Meshes frame of reference, that captures all vertecies.
"""
return self.asset.size
@size.setter
@blue.restrict
def size(self, size: np.ndarray|list[float|int]) -> None:
"""
Modifying this attribute will create a new :class:`blueprints.cache.MeshCache` object if
the previous Cache was the children of multiple parents. Otherwise the Cache get modified
directly.
.. note::
Altering this attribute also changes the :attr:`vertecies` attribute. To get the same
effect without changing the vertecies, see :attr:`scale`.
Parameters
----------
size : np.ndarray | list[float | int]
Setting the size of the Mesh is scales the vertecies along the axes of the Meshes frame
of reference such that it fits into a Box of the given size.
"""
self.asset._prepare_for_modification(self)
self.asset.size = size
@property
def scale(self) -> np.ndarray:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
.. note::
If :attr:`vertecies` should be changed directly such that it adheres to the given scale,
use :attr:`size` instead.
Returns
-------
np.ndarray
The scaling of X-axis, Y-axis and Z-axis in each component. Analogous to :attr:`size` without changing :attr:`vertecies`.
"""
return self.asset.scale
@scale.setter
@blue.restrict
def scale(self, scale: np.ndarray|list[float|int]) -> None:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
.. note::
If :attr:`vertecies` should be changed directly such that it adheres to the given scale,
use :attr:`size` instead.
Parameters
----------
scale : np.ndarray | list[float | int]
The scaling of X-axis, Y-axis and Z-axis in each component. Analogous to :attr:`size` without changing :attr:`vertecies`.
"""
self.asset._prepare_for_modification(self)
self.asset.scale = scale
[docs]
class HField(blue.HFieldGeomType, BaseGeom):
"""
Height field are mesh-like Geoms with a rectangular shape and varing heights.
The height data can either be given as a file or as a 2D numpy array (alternatively 2D list).
Supported fileformats are PNG and the mujoco native HF format.
.. code-block::
:caption: Terrain Data
>>> ...
>>> spiral = np.array([ ... ])
>>> hfield = blue.geoms.HField(terrain=spiral)
# ALTERNATIVELY
>>> hfield = blue.geoms.HField(filename='spiral.hf')
>>> world.attach(hfield, plane, ball)
.. image:: /_static/spiral.gif
Terrain data can be edited at runtime by setting the hfield attribute.
.. code-block::
:caption: Runtime Modification
>>> n_frames = 1000
>>> terrain = blue.perlin((n_frames, 100, 100), periodic=True)
>>> ... # FURTHER TERRAIN PROCESSING
>>> hfield = blue.geoms.HField(terrain=terrain, color='orange')
>>> world.attach(hfield, copy=False)
>>> for n in range(total_steps//20):
>>> world.step(n_steps=20)
>>> hfield.terrain = terrain[n % n_frames, ...]
.. image:: /_static/hfield_update.gif
"""
[docs]
@blue.restrict
def __init__(self,
pos: np.ndarray|list[int|float] = [0., 0., 0.],
terrain: np.ndarray|list[int|float]|None = None,
filename: str|None = None,
x_length: int|float|None = 1,
y_length: int|float|None = 1,
z_length: int|float|None = 1,
height_offset: int|float|None = 1,
margin: int|float = 0.0,
gap: int|float = 0.0,
material: blue.MaterialType|None = None,
asset: blue.assets.HFieldAsset|None = None,
x: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
y: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
z: int|float|np.int32|np.int64|np.float32|np.float64|None = None,
**kwargs) -> None:
"""
Parameters
----------
pos : list[int | float] | np.ndarray | None, optional
Represents the position of the Thing. Changing this attribute also changes the properties :attr:`x`, :attr:`y` and :attr:`z`.
terrain : np.ndarray | list [ int | float ] | None
The terrain data in the shape of a 2D array.
filename : str | None, optional
The name for the file from which the HField data is loaded. The supported formats are ``.png`` and ``.hf``.
x_length : int | float, optional
The length of the HField along the x-axis.
y_length : int | float, optional
The length of the HField along the y-axis.
z_length : int | float, optional
The length of the HField along the z-axis.
height_offset : int | float | None
The offset in height for the terrain data points.
margin : int | float | None, optional
A contact is considered active only if the distance between the two geom surfaces is below
margin-gap.
gap : int | float | None, optional
This attribute is used to enable the generation of inactive contacts, i.e., contacts that are
ignored by the constraint solver but are included in mjData.contact for the purpose of custom
computations. When this value is positive, geom distances between margin and margin-gap
correspond to such inactive contacts.
material : blue.MaterialType, optional
The :class:`Material <blueprints.material.Material>` of the Geom.
:class:`Textures <blueprints.texture.BaseTexture> are applied via Materials.
asset : blue.assets.HFieldAsset | None, optional
The Asset of the HField
name : str | None, optional
The user specified name might potentially be altered to avoid a naming conflict by appending an enumeration scheme.
x : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the X position coordinate.
y : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Y position coordinate.
z : int | float |np.int32 | np.int64 | np.float32 | np.float64 | None, optional
If `pos` is not specified, this argument sets the Z position coordinate.
**kwargs
Keyword arguments passed to ``super().__init__``
"""
#self.terrain = terrain
kwargs_nameless = kwargs.copy()
if 'name' in kwargs_nameless:
del kwargs_nameless['name']
if sum(x is not None for x in (terrain, filename, asset)) > 1:
raise Exception('Not more than one argument (terrain, file or asset) is allowed to be None.')
if pos is None:
x = float(x) if x is not None else 0.
y = float(y) if y is not None else 0.
z = float(z) if z is not None else 0.
pos = np.array([x, y, z], dtype=np.float32)
if asset is not None:
self.asset = asset
elif filename is not None:
if not os.path.isfile(filename):
# TRY DIRNAME PREFIX TO RESOLVE RELATIVE REF TO MAIN.PY
filename = f'{os.path.dirname(sys.argv[0])}/{filename}'
if not os.path.isfile(filename):
raise Exception(f'File not found for path {filename}')
if not os.path.isabs(filename):
filename = os.path.abspath(filename)
if not os.path.isabs(filename):
path = os.path.abspath(os.path.dirname(sys.argv[0]))
filename = f'{path}/{filename}'
self.asset = blue.assets.HFieldAsset(filename=filename,
pos=pos,
x_length=x_length,
y_length=y_length,
z_length=z_length,
height_offset=height_offset,
xml_data=False,
**kwargs_nameless)
elif terrain is not None:
self.asset = blue.assets.HFieldAsset(terrain=terrain,
pos=pos,
x_length=x_length,
y_length=y_length,
z_length=z_length,
height_offset=height_offset,
xml_data=True,
**kwargs_nameless)
else:
raise Exception('No asset could be constructed! Please provide either a filename, terrain or an asset.')
self.asset._add(self)
self.asset.freeze = True
super().__init__(pos=pos,
material=material,
margin=margin,
gap=gap,
**kwargs)
self.asset.freeze = False
def __getitem__(self,
key: tuple[slice|int]) -> np.ndarray|np.float32:
"""
Returns
-------
np.ndarray | np.float32
The values from the selected index/slice of the height field.
"""
return self.asset[key]
def __setitem__(self,
key: tuple[slice|int],
value: int|float|list[int|float]|np.ndarray) -> None:
"""
Parameters
----------
key : tuple[slice | int]
The indecies/slices of acces
value : int|float|list[int|float]|np.ndarray
The value to be assigned in the selected parts of the field.
"""
self.asset._prepare_for_modification(self)
self.asset[key] = value
@blue.restrict
def _build(self,
parent,
world,
indicies,
**kwargs):
"""
This method is called to build the xml.
Parameters
----------
parent : xml.etree.ElementTree.Element
The xml element of its parent
world : WorldType
The World from which the build method was called initially
Returns
-------
xml.etree.ElementTree.Element
The builded xml element of the Thing.
"""
if self.material is not None:
self.material._build(parent, world, indicies, **kwargs)
kwargs['material'] = self.material.asset.name
self._xml_root = xml.SubElement(parent,
self._MUJOCO_OBJ,
hfield=self.asset.name,
**self._mujoco_specs(kwargs))
if self.asset._built:
self._index = self.asset._index
else:
self._index = indicies['hfield']
self.asset._build(parent=parent,
world=world,
indicies=indicies,
**kwargs)
return self._xml_root
# MUJOCO PROPERTIES
@property
def pos(self) -> np.ndarray:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Returns
-------
np.ndarray | list[float | int]
"""
return self.asset.pos.copy()
@pos.setter
@blue.restrict
def pos(self, pos: np.ndarray|list[float|int]) -> None:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Parameters
----------
pos : np.ndarray | list[float | int]
Description
Deleted Parameters
------------------
vertex : list[np.ndarray | list[float | int]]
Description
"""
self.asset._prepare_for_modification(self)
self.asset.pos = pos
@property
def terrain(self) -> np.ndarray:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Returns
-------
np.ndarray | list[float | int]
"""
return self.asset.terrain.copy()
@terrain.setter
@blue.restrict
def terrain(self, terrain: np.ndarray|list[float|int]) -> None:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Parameters
----------
terrain : np.ndarray | list[float | int]
Description
Deleted Parameters
------------------
vertex : list[np.ndarray | list[float | int]]
Description
"""
#self.asset._prepare_for_modification(self)
self.asset.terrain = terrain
@property
def euler(self) -> np.ndarray:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Returns
-------
np.ndarray
"""
return self.asset.euler.copy()
@euler.setter
@blue.restrict
def euler(self, euler: np.ndarray|list[float|int]) -> None:
"""
Modifying this attribute will create a new :class:`blueprints.assets.MeshAsset` object if
the previous Asset was the children of multiple parents. Otherwise the Asset get modified
directly.
Parameters
----------
euler : np.ndarray | list[float | int]
Description
Deleted Parameters
------------------
vertex : list[np.ndarray | list[float | int]]
Description
pos : np.ndarray | list[float | int]
Description
"""
self.asset._prepare_for_modification(self)
self.asset.euler = euler
@property
def size(self) -> np.ndarray:
"""
Modifying this attribute will create a new :class:`blueprints.cache.MeshCache` object if
the previous Cache was the children of multiple parents. Otherwise the Cache get modified
directly.
.. note::
Altering this attribute also changes the :attr:`vertecies` attribute. To get the same
effect without changing the vertecies, see :attr:`scale`.
Returns
-------
np.ndarray
The size of the Mesh is calculated as the size of the Box with edges along the axes of
the Meshes frame of reference, that captures all vertecies.
"""
return self.asset.size.copy()
@size.setter
@blue.restrict
def size(self, size: np.ndarray|list[float|int]) -> None:
"""
Modifying this attribute will create a new :class:`blueprints.cache.MeshCache` object if
the previous Cache was the children of multiple parents. Otherwise the Cache get modified
directly.
.. note::
Altering this attribute also changes the :attr:`vertecies` attribute. To get the same
effect without changing the vertecies, see :attr:`scale`.
Parameters
----------
size : np.ndarray | list[float | int]
Setting the size of the Mesh is scales the vertecies along the axes of the Meshes frame
of reference such that it fits into a Box of the given size.
"""
self.asset._prepare_for_modification(self)
self.asset.size = size
# BLUEPRINTS ATTRIBUTES
@property
def x_length(self) -> float:
"""
Length along the ``X``-axis of the HField.
Returns
-------
float
"""
return self.asset.x_length
@property
def y_length(self) -> float:
"""
Length along the ``Y``-axis of the HField.
Returns
-------
float
"""
return self.asset.y_length
@property
def z_length(self) -> float:
"""
Length along the ``Z``-axis of the HField.
Returns
-------
float
"""
return self.asset.z_length
@property
def height_offset(self):
"""
Height offset along the ``Z``-axis of the HField.
Returns
-------
float
"""
return self.asset.height_offset
@x_length.setter
@blue.restrict
def x_length(self, x_length: int|float|np.int32|np.int64|np.float32|np.float64) -> None:
self.asset.x_length = float(x_length)
@y_length.setter
@blue.restrict
def y_length(self, y_length: int|float|np.int32|np.int64|np.float32|np.float64) -> None:
self.asset.y_length = float(y_length)
@z_length.setter
@blue.restrict
def z_length(self, z_length: int|float|np.int32|np.int64|np.float32|np.float64) -> None:
self.asset.z_length = float(z_length)
@height_offset.setter
@blue.restrict
def height_offset(self, height_offset: int|float|np.int32|np.int64|np.float32|np.float64) -> None:
self.asset.height_offset = float(height_offset)
GEOM_THINGS = {'capsule': Capsule,
'cylinder': Cylinder,
'plane': Plane,
'sphere': Sphere,
'ellipsoid': Ellipsoid,
'box': Box,
'mesh': Mesh,
'hfield': HField}