Commits

German Larrain committed 1c19ab3 Merge

Merge with dev

Comments (0)

Files changed (73)

+==============================
 INSTALL
+==============================
 
-(If your OS is Ubuntu, follow the instructions in "ARS installation - Ubuntu.txt", for an easier process that deals with requirements too)
+Detailed instructions for installation of ARS and its requirements are available
+in the online documentation [1].
 
-To install the software (only the "ars" package is part of the installation; docs, tests and demos are not), you need to:
+ARS itself is really easy to install :) but some of its prerequisites
+are not :( , depending on which operating system is used.
+The best option is Ubuntu, specially its 12.04 (i.e. Precise Pangolin)
+version. For that, follow the instructions in [2]. If you have another version
+of Ubuntu, steps may be the same or very similar. For other Linux distros, the
+steps regarding ``apt-get`` will probably change.
 
-1) check & install the requirements listed in README.
-2) open a command window, with the current directoy set to the project main directory
-3) execute the (included) standard python setup script (you may need administrator privileges):
-	python setup.py install
+Microsoft Windows is the OS less friendly to ARS, as well as to many other open
+source software. Nonetheless, it is very popular so we got it to run there too,
+for both XP and 7 (Vista untested). Read the corresponding section in [3].
 
-----------------------------------------
-TEST INSTALLATION
-Start the Python interpreter (with the command "python") and execute this statement:
-	import ars
+ARS has not been tested on Mac OSX but it should work fine because all the
+required software has been reported to run fine on it. ARS itself is pure Python
+and OS-agnostic thus possible issues will be related to the requirements [4].
+
+[1] http://ars-project.readthedocs.org/en/latest/installation/
+[2] http://ars-project.readthedocs.org/en/latest/installation/ubuntu_12-04.html
+[3] http://ars-project.readthedocs.org/en/latest/installation/windows.html
+[4] http://ars-project.readthedocs.org/en/latest/installation/prerequisites.html
+
+
+Installation steps
+------------------------------
+(Note: only the "ars" package is part of the installation; docs, tests and
+demos are not, although they are included in the archive file)
+
+To install ARS , you need to:
+
+1) check and install the requirements listed in the documentation.
+
+2a) If you have pip installed, you only need one command to get the package from
+the internet and install it in your system:
+	$ pip install ARS
+
+2b) If you don't have pip, open a command window, change the current directoy
+to the project main directory and execute the (included) standard Python setup
+script (you may need administrator privileges)
+	$ cd ARS-03
+	$ python setup.py install
+
+
+Test installation
+------------------------------
+Start the Python interpreter (with the command "python")
+	$ python
+and import the package
+	>>> import ars
+You may explore the contents of the package with
+	>>> help(ars)
 
 If no errors appear, you are all set.
-You may explore the contents by entering "help(ars)".
-To exit the interpreter, use "quit()".
+To exit the interpreter, use "quit()" or Ctrl+D.
 
-----------------------------------------
-RUN DEMO
-To run a demo, execute any file in the "bin" subfolder, such as:
-	python bin/FallingBall.py
-	python bin/IROS/example4_sinusoidal_terrain.py
 
-----------------------------------------
-BUILD DOCUMENTATION
+Run demos
+------------------------------
+To watch ARS in action, run any file in the "demos" subdirectory, such as:
+	$ python demos/FallingBall.py
+	$ python demos/IROS/example4_sinusoidal_terrain.py
 
-For epydoc, see ./docs/epydoc
-For sphinx, see ./docs/sphinx
+
+Build documentation
+------------------------------
+Sphinx is used to build the documentation. Most common output is HTML but a PDF
+file can be generated through Latex. A makefile is included.
+	$ docs/sphinx
+	$ make html
+	$ make latexpdf
+
+
+Note: in the past, epydoc was used. It may still possible to generate the
+documentation with it. For that, read the contents at docs/epydoc.
 `Open Dynamics Engine (ODE) <https://sourceforge.net/projects/opende/>`_
 and the `Visualization Toolkit (VTK) <http://www.vtk.org/>`_.
 
-Requirements
+Installation and Requirements
 ====================
-* Python 2.7 (developed with 2.7.3)
-  http://www.python.org/download/releases/2.7.3/
+For installation instructions and requirements, see the
+`online documentation <http://ars-project.readthedocs.org/en/latest/installation/>`_.
+
+ARS relies in these software:
+
+* Python 2.7
 * ODE (Open Dynamics Engine) 0.12 (from rev 1864 could work but is untested) with Python bindings
-  http://sourceforge.net/projects/opende/
-  http://sourceforge.net/projects/arsproject/files/ODE/
-* VTK (Visualization Toolkit) 5.8 (developed with 5.8.0 and used to be 5.2.1) with Python bindings
-  http://www.vtk.org/VTK/resources/software.html
-  https://sourceforge.net/projects/arsproject/files/VTK/
+* VTK (Visualization Toolkit) 5.8 with Python bindings
+* NumPy 1.6
 
 Documentation
 ====================
 -Add to README.txt a "Contributors" section and/or a "Thanks also to" section to list the names of people who’ve helped.
 
 Code structure (modules and packages)
+-convert sensors module into a package
 -rename Simulation.get_joint as get_sim_joint
+-make JointSensor.__init__ be able to receive a Joint OR a SimulatedJoint, which
+wraps a Joint. The idea is to avoid confusing calls such as "x.get_joint().get_joint()"
 -move ./tests to ars/test and convert to a package? See
 	http://guide.python-distribute.org/creation.html#arranging-your-file-and-directory-structure
 
 	edge_slider = window.findChild(QtGui.QSlider, "edgeCount")
 	
 	window.show()
-	application.exec_()
+	application.exec_()
 It contains the Program class which is the core application controller.
 
 """
-
 from abc import abstractmethod
 
 import ars.exceptions as exc
 
 
 class Program():
+
 	"""Main class of ARS.
 
 	To run a custom simulation, create a subclass.
 	which will be called during the simulation creation.
 
 	To use it, only two statements are necessary:
-	-create an object of this class (i.e. sim_program = ProgramSubclass() )
-	-call its 'start' method (i.e. sim_program.start() )
+
+	* create an object of this class
+		>>> sim_program = ProgramSubclass()
+	* call its 'start' method
+		>>> sim_program.start()
 
 	"""
+
 	DEBUG = False
 	PRINT_KEY_INFO = True
 
 	def __init__(self):
 		"""Constructor. Defines some attributes and calls some initialization
 		methods to:
-		-sets the basic mapping of key to action,
-		-create the visualization window according to class constants,
-		-create the simulation.
+
+		* set the basic mapping of key to action,
+		* create the visualization window according to class constants,
+		* create the simulation.
 
 		"""
 		self.do_create_window = True
 
 	def create_simulation(self, add_axes=True, add_floor=True):
 		"""Creates an empty simulation and:
-		-adds basic simulation objects (:meth:`add_basic_simulation_objects`),
-		-(if ``add_axes`` is ``True``) adds axes to the visualization at the
-		coordinates-system origin,
-		-(if ``add_floor`` is ``True``) adds a floor with a defined normal
-		vector and some visualization parameters,
-		-calls :meth:`create_sim_objects` (which must be implemented by
-		subclasses),
-		-gets the actors representing the simulation objects and adds them to
-		the graphics adapter.
+
+		#. adds basic simulation objects (:meth:`add_basic_simulation_objects`),
+		#. (if ``add_axes`` is ``True``) adds axes to the visualization at the
+			coordinates-system origin,
+		#. (if ``add_floor`` is ``True``) adds a floor with a defined normal
+			vector and some visualization parameters,
+		#. calls :meth:`create_sim_objects` (which must be implemented by
+			subclasses),
+		#. gets the actors representing the simulation objects and adds them to
+			the graphics adapter.
 
 		"""
 		# set up the simulation parameters
 	def create_sim_objects(self):
 		"""This method must be overriden (at least once in the inheritance tree)
 		by the subclass that will instatiated to run the simulator.
+
 		It shall contain statements calling its 'sim' attribute's methods for
-		adding objects (e.g. add_sphere). For example:
-		self.sim.add_sphere(0.5, (1,10,1), density=1)
+		adding objects (e.g. add_sphere).
+
+		For example:
+
+		>>> self.sim.add_sphere(0.5, (1,10,1), density=1)
 
 		"""
 		raise NotImplementedError()
 	def create_screenshot_recorder(self, base_filename, periodically=False):
 		"""Create a screenshot (of the frames displayed in the graphics window)
 		recorder.
-		
+
 		Each image will be written to a numbered file according to
 		``base_filename``. By default it will create an image each time
 		:meth:`record_frame` is called. If ``periodically`` is ``True`` then
 
 	def record_frame(self):
 		"""Record a frame using a screenshot recorder.
-		
+
 		If frames are meant to be written periodically, a new one will be
 		recorded only if enough time has elapsed, otherwise it will return
 		``False``. The filename index will be ``time / period``.
-		
+
 		If frames are not meant to be written periodically, then index equals
 		simulator's frame number.
 
-
-# Created on 2012.01.29
-#
-# @author: german
-
 from ars.utils.mathematical import mult_by_scalar3
 
-#===============================================================================
+#==============================================================================
 # GEOMETRY
-#===============================================================================
+#==============================================================================
 
 X_AXIS = (1,0,0)
 Y_AXIS = (0,1,0)
 OUTWARDS_AXIS = Z_AXIS
 
 
-#===============================================================================
+#==============================================================================
 # MATH & ALGEBRA
-#===============================================================================
+#==============================================================================
 
 EYE_3X3 = ((1,0,0),(0,1,0),(0,0,1))
 
 
-#===============================================================================
+#==============================================================================
 # COLORS
-#===============================================================================
+#==============================================================================
 
 def convert_color(R_int, G_int, B_int):
 	return mult_by_scalar3((R_int,G_int,B_int), 1.0 / 256)
 COLOR_SNOW = 		convert_color(255,250,250)
 COLOR_VIOLET = 		convert_color(238,130,238)
 COLOR_YELLOW = 		convert_color(255,255,0)
-COLOR_WHITE = 		convert_color(255,255,255)
+COLOR_WHITE = 		convert_color(255,255,255)
+"""ARS's exceptions class hierarchy.
 
-# Created on 2011.10.31
-#
-# @author: german
-
-"""
-ARS's exception class hierarchy.
 """
 
 ###############################################################################
 
 
 class ArsError(Exception):
+
 	"""Base class for exceptions in this library.
 
 	Attributes:
 		msg  -- explanation of the error
+
 	"""
 
 	def __init__(self, msg=None):
 		super(ArsError, self).__init__()
 		self.msg = msg
 
+
 class PhysicsEngineException(ArsError):
+
 	"""Exception raised for errors in a physics engine.
 
 	Attributes:
 		msg  -- explanation of the error
+
 	"""
+
 	pass
 
+
 class JointError(PhysicsEngineException):
+
 	"""Exception raised for errors related to physical joints.
 
 	Attributes:
 		joint	-- joint in which the error occurred
 		msg		-- explanation of the error
+
 	"""
 
 	def __init__(self, joint, msg=None):
 		PhysicsEngineException.__init__(self, msg)
 		self.joint = joint
 
+
 class PhysicsObjectCreationError(PhysicsEngineException):
+
 	"""Exception raised for errors in physics-engine objects creation.
 
 	Attributes:
 		type	-- type of the object being created
 		msg		-- explanation of the error
+
 	"""
 
 	def __init__(self, type, msg=None):

ars/graphics/__init__.py

-
-# Created on 2011.08.09
-#
-# @author: german
-
 from abc import ABCMeta, abstractmethod
 
 TIMER_PERIOD = 50 # milliseconds
 TIMER_EVENT = 'TimerEvent'
 KEY_PRESS_EVENT = 'KeyPressEvent'
 
+
 class Axes:
 	__metaclass__ = ABCMeta
 
 	@abstractmethod
-	def __init__(self, position=(0,0,0), cylinder_radius=0.05):
+	def __init__(self, pos=(0, 0, 0), rot=None, cylinder_radius=0.05):
 		self._actor = None
 
 	@property
 	def actor(self):
 		return self._actor
 
+
 class Body:
+
 	"""
 	Abstract class. Not coupled (at all) with VTK or any other graphics library
+
 	"""
+
 	__metaclass__ = ABCMeta
 
 	adapter = None
 
 	@abstractmethod
-	def __init__(self, center, rotation):
+	def __init__(self, center, rot):
 		self._position = center
-		self._rotation = rotation
+		self._rotation = rot
 		self._color = None
 		self._actor = None
 
-	def update_position_rotation(self, position, rotation):
-		self._position = position
-		self._rotation = rotation
-		self.adapter._update_body(self._actor, position, rotation)
+	def update_position_rotation(self, pos, rot):
+		self._position = pos
+		self._rotation = rot
+		self.adapter._update_pose(self._actor, pos, rot)
 
 	@property
 	def actor(self):
 	def set_color(self, color):
 		self._color = color
 
+
 class Box(Body):
 	__metaclass__ = ABCMeta
 
 	@abstractmethod
-	def __init__(self, size, position, rotation=None):
-		super(Box, self).__init__(position, rotation)
+	def __init__(self, size, pos, rot=None):
+		super(Box, self).__init__(pos, rot)
+
 
 class Cone(Body):
 	__metaclass__ = ABCMeta
 
 	@abstractmethod
-	def __init__(self, height, radius, center, rotation=None, resolution = 100):
-		super(Cone, self).__init__(center, rotation)
+	def __init__(self, height, radius, center, rot=None, resolution = 100):
+		super(Cone, self).__init__(center, rot)
+
 
 class Sphere(Body):
 	__metaclass__ = ABCMeta
 
 	@abstractmethod
-	def __init__(self, radius, center, rotation=None, phiResolution = 50, thetaResolution = 50):
-		super(Sphere, self).__init__(center, rotation)
+	def __init__(self, radius, center, rot=None, phiResolution = 50, thetaResolution = 50):
+		super(Sphere, self).__init__(center, rot)
+
 
 class Cylinder(Body):
 	__metaclass__ = ABCMeta
 
 	@abstractmethod
-	def __init__(self, length, radius, center, rotation=None, resolution=10):
-		super(Cylinder, self).__init__(center, rotation)
+	def __init__(self, length, radius, center, rot=None, resolution=10):
+		super(Cylinder, self).__init__(center, rot)
+
 
 class Capsule(Body):
 	__metaclass__ = ABCMeta
 
 	@abstractmethod
-	def __init__(self, length, radius, center, rotation=None, resolution=10):
-		super(Capsule, self).__init__(center, rotation)
+	def __init__(self, length, radius, center, rot=None, resolution=10):
+		super(Capsule, self).__init__(center, rot)
+
 
 class Trimesh(Body):
 	__metaclass__ = ABCMeta
 
 	@abstractmethod
-	def __init__(self, vertices, faces, position=None, rotation=None):
-		super(Trimesh, self).__init__(position, rotation)
+	def __init__(self, vertices, faces, pos=None, rot=None):
+		super(Trimesh, self).__init__(pos, rot)
+
 
 class Adapter:
+
 	"""
 	Abstract class. Not coupled (at all) with VTK or any other graphics library
+
 	"""
+
 	__metaclass__ = ABCMeta
 
 	@abstractmethod
 		self._window_started = False
 
 	@abstractmethod
-	def create_window(self, title, position, size, zoom, cam_position, color):
+	def create_window(self, title, pos, size, zoom, cam_position, color):
 		raise NotImplementedError()
 
 	@abstractmethod
 
 	@staticmethod
 	@abstractmethod
-	def _update_body(body, position, rotation):
+	def _update_pose(obj, pos, rot):
 		raise NotImplementedError()
 
+
 class ScreenshotRecorder:
 	__metaclass__ = ABCMeta
 
 		Writes the current image displayed in the render window as a file
 		named 'self.base_filename' with	the 'index' number appended, and the
 		corresponding extension.
+
 		"""
 		raise NotImplementedError()

ars/graphics/adapters.py

-
-# Created on 2011.11.01
-#
-# @author: german
-
 import vtk
 
 import ars.exceptions as exc
 		self._zoom = None
 		self._cam_position = None
 
-	def create_window(self, title, position=None, size=(1000,600), zoom=1.0, cam_position=(10,8,10),
+	def create_window(self, title, pos=None, size=(1000,600), zoom=1.0, cam_position=(10,8,10),
 					background_color=(0.1,0.1,0.4)):
 
 		self._title = title
 		raise exc.ArsError()
 
 	@staticmethod
-	def _update_body(body, position, rotation):
-		t = VtkAdapter._create_transform_matrix(position, rotation)
-		VtkAdapter._set_object_transform_matrix(body, t)
+	def _update_pose(obj, pos, rot):
+		trans = gemut.Transform(pos, rot)
+		vtk_tm = VtkAdapter._create_transform_matrix(trans)
+		VtkAdapter._set_object_transform_matrix(obj, vtk_tm)
 
 	def _timer_callback(self, obj, event):
 		self.timer_count += 1
 		"""
 		obj: the vtkRenderWindowInteractor
 		event: "KeyPressEvent"
+
 		"""
-
 		key = obj.GetKeySym().lower()
 		if self.on_key_press_parent_callback:
 			self.on_key_press_parent_callback(key)
 	#===========================================================================
 
 	@staticmethod
-	def _set_object_transform_matrix(obj, transMat):
-		obj.PokeMatrix(transMat)
+	def _set_object_transform_matrix(obj, vtk_tm):
+		"""Set ``obj``'s pose according to the transform ``vtk_tm``.
+
+		:param obj: object to be modified
+		:type obj: :class:`vtk.vtkProp3D`
+		:param vtk_tm: homogeneous transform
+		:type vtk_tm: :class:`vtk.vtkMatrix4x4`
+
+		"""
+		obj.PokeMatrix(vtk_tm)
 
 	@staticmethod
-	def _create_transform_matrix(position, rotMatrix):
+	def _create_transform_matrix(trans):
+		"""Create a homogeneous transform matrix valid for VTK.
+
+		:param trans: homogeneous transform
+		:type trans: :class:`ars.utils.geometry.Transform`
+		:return: a VTK-valid transform matrix
+		:rtype: :class:`vtk.vtkMatrix4x4`
+
 		"""
-		position: a 3-tuple
-		rotMatrix: a 9-tuple
-		"""
-		t = gemut.Transform(position, rotMatrix)
 		vtk_matrix = vtk.vtkMatrix4x4()
-		vtk_matrix.DeepCopy(t.get_long_tuple())
+		vtk_matrix.DeepCopy(trans.get_long_tuple())
 
 		return vtk_matrix
 
+
 class VtkBody:
 	adapter = VtkAdapter
 
 		"""
 		Returns the color of the body. If it is an assembly,
 		it is not checked whether all the objects' colors are equal.
+
 		"""
-
 		# dealing with vtkAssembly properties is more complex
 		if isinstance(self._actor, vtk.vtkAssembly):
 			props_3D = self._actor.GetParts()
 		"""
 		Sets the color of the body. If it is an assembly,
 		all the objects' color is set.
+
 		"""
-
 		# dealing with vtkAssembly properties is more complex
 		if isinstance(self._actor, vtk.vtkAssembly):
 			props_3D = self._actor.GetParts()
 			self._actor.GetProperty().SetColor(color)
 		self._color = color
 
+
 class Axes(VtkBody, gp.Axes):
-	def __init__(self, position=(0,0,0), cylinder_radius=0.05):
-		gp.Axes.__init__(self, position, cylinder_radius)
+	def __init__(self, pos=(0, 0, 0), rot=None, cylinder_radius=0.05):
+		gp.Axes.__init__(self, pos, cylinder_radius)
 
 		# 2 different methods may be used here
 		# see http://stackoverflow.com/questions/7810632/
 		axesActor.AxisLabelsOn()
 		axesActor.SetShaftTypeToCylinder()
 		axesActor.SetCylinderRadius(cylinder_radius)
+		VtkAdapter._update_pose(axesActor, pos, rot)
 
 		self._actor = axesActor
 
+
 class Box(VtkBody, gp.Box):
-	def __init__(self, size, position, rotation=None):
-		gp.Box.__init__(self, size, position, rotation)
+	def __init__(self, size, pos, rot=None):
+		gp.Box.__init__(self, size, pos, rot)
 
 		box = vtk.vtkCubeSource()
 		box.SetXLength(size[0])
 		boxMapper.SetInputConnection(box.GetOutputPort())
 		boxActor = vtk.vtkActor()
 
-		VtkAdapter._update_body(boxActor, position, rotation)
+		VtkAdapter._update_pose(boxActor, pos, rot)
 		boxActor.SetMapper(boxMapper)
 
 		self._actor = boxActor
 
+
 class Cone(VtkBody, gp.Cone):
-	def __init__(self, height, radius, center, rotation=None, resolution = 20):
-		gp.Cone.__init__(self, height, radius, center, rotation, resolution)
+	def __init__(self, height, radius, center, rot=None, resolution = 20):
+		gp.Cone.__init__(self, height, radius, center, rot, resolution)
 
 		cone = vtk.vtkConeSource()
 		cone.SetHeight(height)
 		coneMapper.SetInputConnection(cone.GetOutputPort())
 		coneActor = vtk.vtkActor()
 
-		VtkAdapter._update_body(coneActor, center, rotation)
+		VtkAdapter._update_pose(coneActor, center, rot)
 		coneActor.SetMapper(coneMapper)
 
 		self._actor = coneActor
 
+
 class Sphere(VtkBody, gp.Sphere):
+
 	"""
 	VTK: sphere (represented by polygons) of specified radius centered at the
 	origin. The resolution (polygonal discretization) in both the latitude
 	(phi) and longitude (theta)	directions can be specified.
+
 	"""
 
-	def __init__(self, radius, center, rotation=None, phiResolution = 20, thetaResolution = 20):
-		gp.Sphere.__init__(self, radius, center, rotation, phiResolution, thetaResolution)
+	def __init__(self, radius, center, rot=None, phiResolution = 20, thetaResolution = 20):
+		gp.Sphere.__init__(self, radius, center, rot, phiResolution, thetaResolution)
 
 		sphere = vtk.vtkSphereSource()
 		sphere.SetRadius(radius)
 		sphereMapper.SetInputConnection(sphere.GetOutputPort())
 		sphereActor = vtk.vtkActor()
 
-		VtkAdapter._update_body(sphereActor, center, rotation)
+		VtkAdapter._update_pose(sphereActor, center, rot)
 		sphereActor.SetMapper(sphereMapper)
 
 		self._actor = sphereActor
 
+
 class Cylinder(VtkBody, gp.Cylinder):
-	def __init__(self, length, radius, center, rotation=None, resolution=20):
-		gp.Cylinder.__init__(self, length, radius, center, rotation, resolution)
+	def __init__(self, length, radius, center, rot=None, resolution=20):
+		gp.Cylinder.__init__(self, length, radius, center, rot, resolution)
 
 		# VTK: The axis of the cylinder is aligned along the global y-axis.
 		cyl = vtk.vtkCylinderSource()
 		cylMapper.SetInputConnection(transFilter.GetOutputPort())
 		cylActor = vtk.vtkActor()
 
-		VtkAdapter._update_body(cylActor, center, rotation)
+		VtkAdapter._update_pose(cylActor, center, rot)
 		cylActor.SetMapper(cylMapper)
 
 		self._actor = cylActor
 
+
 class Capsule(VtkBody, gp.Capsule):
-	def __init__(self, length, radius, center, rotation=None, resolution=20):
-		gp.Capsule.__init__(self, length, radius, center, rotation, resolution)
+	def __init__(self, length, radius, center, rot=None, resolution=20):
+		gp.Capsule.__init__(self, length, radius, center, rot, resolution)
 		# simplify this construction using those corresponding to Cylinder and Sphere?
 
 		sphere1 = vtk.vtkSphereSource()
 		assembly.AddPart(sphereActor1)
 		assembly.AddPart(sphereActor2)
 
-		VtkAdapter._update_body(assembly, center, rotation)
+		VtkAdapter._update_pose(assembly, center, rot)
 		self._actor = assembly
 
+
 class Trimesh(VtkBody, gp.Trimesh):
-	def __init__(self, vertices, faces, position, rotation=None):
-		gp.Trimesh.__init__(self, vertices, faces, position, rotation)
+	def __init__(self, vertices, faces, pos, rot=None):
+		gp.Trimesh.__init__(self, vertices, faces, pos, rot)
 
 		# create points
 		points = vtk.vtkPoints()
 
 		# actor: represents an object (geometry & properties) in a rendered scene
 		triangles_actor = vtk.vtkActor()
-		VtkAdapter._update_body(triangles_actor, position, rotation)
+		VtkAdapter._update_pose(triangles_actor, pos, rot)
 		triangles_actor.SetMapper(mapper)
 
 		self._actor = triangles_actor
 
+
 class ScreenshotRecorder(gp.ScreenshotRecorder):
+
 	"""
 	Based on an official example script, very simple:
 	http://www.vtk.org/Wiki/VTK/Examples/Python/Screenshot
+
 	"""
 
 	def __init__(self, base_filename='screenshot_', graphics_adapter=None):
 		Writes the current image displayed in the render window as a PNG file
 		named 'self.base_filename' with	the 'index' number appended, and a
 		'.png' extension.
+
 		"""
 		# TODO: see if the workaround (get renWin and create image_getter every time)
 		# was needed because we used image_getter.SetInput instead of SetInputConnection

ars/model/__init__.py

-
-# Created on 2011.10.31
-#
-# @author: german

ars/model/collision/adapters.py

 	#==========================================================================
 
 	def get_position(self):
+		"""Get the position of the geom.
+
+		:return: position
+		:rtype: 3-sequence of floats
+
+		"""
 		return self._inner_object.getPosition()
 
 	def get_rotation(self):
+		"""Get the orientation of the geom.
+
+		:return: rotation matrix
+		:rtype: 9-sequence of floats
+
+		"""
 		return self._inner_object.getRotation()
 
 	def set_position(self, pos):
+		"""Set the position of the geom.
+
+		:param pos: position
+		:type pos: 3-sequence of floats
+
+		"""
 		self._inner_object.setPosition(pos)
 
 	def set_rotation(self, rot):
+		"""Set the orientation of the geom.
+
+		:param rot: rotation matrix
+		:type rot: 9-sequence of floats
+
+		"""
 		self._inner_object.setRotation(rot)
 
 

ars/model/collision/base.py

 
 	@abstractmethod
 	def get_position(self):
+		"""Get the position of the geom.
+
+		:return: position
+		:rtype: 3-sequence of floats
+
+		"""
 		pass
 
 	@abstractmethod
 	def get_rotation(self):
+		"""Get the orientation of the geom.
+
+		:return: rotation matrix
+		:rtype: 9-sequence of floats
+
+		"""
 		pass
 
 	@abstractmethod
 	def set_position(self, pos):
+		"""Set the position of the geom.
+
+		:param pos: position
+		:type pos: 3-sequence of floats
+
+		"""
 		pass
 
 	@abstractmethod
 	def set_rotation(self, rot):
+		"""Set the orientation of the geom.
+
+		:param rot: rotation matrix
+		:type rot: 9-sequence of floats
+
+		"""
 		pass
 
 
 class RayContactData:
 	"""Contact information of a collision between `ray` and `shape`"""
 
-	def __init__(self, ray=None, shape=None, position=None, normal=None, depth=None):
-		# -position: point at which the ray intersects the surface of the other
+	def __init__(self, ray=None, shape=None, pos=None, normal=None, depth=None):
+		# -pos: point at which the ray intersects the surface of the other
 		# 	shape/geom.
 		# -normal: surface normal of the other geom at the contact point.
 		# -depth: distance from the start of the ray to the contact point.
 		# 	TODO: is it true?
 		self.ray = ray
 		self.shape = shape
-		self.position = position
+		self.position = pos
 		self.normal = normal
 		self.depth = depth
 

ars/model/geometry/__init__.py

-
-# Created on 2011.10.31
-#
-# @author: german
-
-
 # put here auxiliar class and functions, such as tensor matrix and Orientation

ars/model/physics/adapters.py

 	#==========================================================================
 
 	def get_position(self):
+		"""Get the position of the body.
+
+		:return: position
+		:rtype: 3-sequence of floats
+
+		"""
 		return self._inner_object.getPosition()
 
 	def get_linear_velocity(self):
 		return self._inner_object.getLinearVel()
 
 	def get_rotation(self):
+		"""Get the orientation of the body.
+
+		:return: rotation matrix
+		:rtype: 9-sequence of floats
+
+		"""
 		return self._inner_object.getRotation()
 
 	def get_angular_velocity(self):
 		return self._inner_object.getMass().I
 
 	def set_position(self, pos):
+		"""Set the position of the body.
+
+		Sends :data:`signals.BODY_PRE_SET_POSITION` and
+		:data:`signals.BODY_POST_SET_POSITION`.
+
+		:param pos: position
+		:type pos: 3-sequence of floats
+
+		"""
 		dispatcher.send(signals.BODY_PRE_SET_POSITION, sender=self)
 		self._inner_object.setPosition(pos)
 		dispatcher.send(signals.BODY_POST_SET_POSITION, sender=self)
 
 	def set_rotation(self, rot):
+		"""Set the orientation of the body.
+
+		Sends :data:`signals.BODY_PRE_SET_ROTATION` and
+		:data:`signals.BODY_POST_SET_ROTATION`.
+
+		:param rot: rotation matrix
+		:type rot: 9-sequence of floats
+
+		"""
 		dispatcher.send(signals.BODY_PRE_SET_ROTATION, sender=self)
 		self._inner_object.setRotation(rot)
 		dispatcher.send(signals.BODY_POST_SET_ROTATION, sender=self)

ars/model/physics/base.py

 	__metaclass__ = ABCMeta
 
 	@abstractmethod
-	def __init__(self, mass=None, density=None, position=None, rotation=None,
+	def __init__(self, mass=None, density=None, pos=None, rot=None,
 			*args, **kwargs):
 
 		if mass is not None:
 
 	@abstractmethod
 	def get_position(self):
+		"""Get the position of the body.
+
+		:return: position
+		:rtype: 3-sequence of floats
+
+		"""
 		raise NotImplementedError()
 
 	@abstractmethod
 
 	@abstractmethod
 	def get_rotation(self):
+		"""Get the orientation of the body.
+
+		:return: rotation matrix
+		:rtype: 9-sequence of floats
+
+		"""
 		raise NotImplementedError()
 
 	@abstractmethod
 
 	@abstractmethod
 	def set_position(self, pos):
-		"""Subclasses implementing this method must send the correponding
-		signals, defined in :mod:`ars.model.physics.signals`.
+		"""Set the position of the body.
+
+		Sends :data:`signals.BODY_PRE_SET_POSITION` and
+		:data:`signals.BODY_POST_SET_POSITION`.
+
+		:param pos: position
+		:type pos: 3-sequence of floats
 
 		"""
 		raise NotImplementedError()
 
 	@abstractmethod
 	def set_rotation(self, rot):
-		"""Subclasses implementing this method must send the correponding
-		signals, defined in :mod:`ars.model.physics.signals`.
+		"""Set the orientation of the body.
+
+		Sends :data:`signals.BODY_PRE_SET_ROTATION` and
+		:data:`signals.BODY_POST_SET_ROTATION`.
+
+		:param rot: rotation matrix
+		:type rot: 9-sequence of floats
 
 		"""
 		raise NotImplementedError()

ars/model/robot/__init__.py

-
-# Created on 2011.10.31
-#
-# @author: german

ars/model/robot/sensors.py

+"""Module of all the classes related to sensors.
 
-# Created on 2011.10.31
-#
-# @author: german
+There are base classes for sensors whose source is a body, joint or simulation.
+It also considers those which read information automatically by subscribing
+to certain signals.
+
+Some practical sensors are:
+
+* :class:`RotaryJointSensor`, :class:`JointTorque`
+* :class:`Laser`
+* :class:`GPS`, :class:`Velometer`, :class:`Accelerometer`,
+  :class:`Inclinometer`
+* :class:`KineticEnergy`, :class:`PotentialEnergy`, :class:`TotalEnergy`,
+  :class:`SystemTotalEnergy`
+
+It also contains the auxiliary classes :class:`SensorData` and
+:class:`SensorDataQueue`.
+
+"""
+from abc import ABCMeta, abstractmethod
+
+from ars.lib.pydispatch import dispatcher
+import ars.model.collision.adapters as coll
+import ars.utils.containers
+from ars.utils.generic import get_current_epoch_time
+from ars.utils.geometry import calc_inclination
+from ars.utils.mathematical import calc_acceleration
+
+from ars.model.robot import signals
+
+#==============================================================================
+# Abstract classes
+#==============================================================================
+
+
+class Sensor:
+
+	"""Abstract base class for all sensors."""
+
+	__metaclass__ = ABCMeta
+
+	@abstractmethod
+	def __init__(self, source):
+		self._source = source
+		self.data_queue = SensorDataQueue()
+
+	def on_change(self, time=get_current_epoch_time()):
+		data = self._build_data()
+		data.set_time(time)
+		self.data_queue.put(data)
+
+	@abstractmethod
+	def _build_data(self):
+		"""Create and return a :class:`SensorData` object."""
+		raise NotImplementedError()
+
+	def get_measurement(self):
+		"""Return a measurement of the sensor packed in a data structure."""
+		return self._build_data()
+
+	@property
+	def source(self):
+		return self._source
+
+
+class BaseSignalSensor:
+
+	"""Base class for sensors that handle signals with :meth:`on_send`."""
+
+	__metaclass__ = ABCMeta
+
+	any_sender = dispatcher.Any
+
+	@abstractmethod
+	def __init__(self, sender=any_sender, autotime=False):
+		"""Constructor.
+
+		:param sender: object that will send the signal; if it is
+			:attr:`any_sender`, subscription will be to any object
+		:param autotime: if True and :meth:`_get_time` is not overriden, every
+			measurement's time will set to the computer time in that instant
+
+		"""
+		self._sender = sender
+		self._autotime = autotime
+
+		self.data_queue = SensorDataQueue()
+
+	def on_send(self, sender, *args, **kwargs):
+		"""Handle signal sent/fired by ``sender`` through the dispatcher.
+
+		Takes care of building a data object, set time to it and save it in the
+		``data_queue``.
+
+		:param sender: signal sender
+		:param args: signal arguments
+		:param kwargs: signal keyword arguments
+
+		"""
+		data = self._build_data(sender, *args, **kwargs)
+		time = self._get_time()
+		if time is not None:
+			data.set_time(time)
+		self.data_queue.put(data)
+
+	@abstractmethod
+	def _build_data(self, sender, *args, **kwargs):
+		"""Build and return a SensorData object with current data.
+
+		:return: object containing data based on the state of the sender
+		:rtype: :class:`SensorData`
+
+		"""
+		raise NotImplementedError()
+
+	def _get_time(self):
+		"""Return the time to set to the :class`SensorData` instance.
+
+		Override this to define it based on some object's state or property.
+
+		:return: time value for the data
+		:rtype: :class:`float` or None
+
+		"""
+		time = None
+		if self._autotime:
+			time = get_current_epoch_time()
+		return time
+
+	@property
+	def sender(self):
+		"""Return the sender of the signal to which the sensor listens."""
+		return self._sender
+
+
+class SingleSignalSensor(BaseSignalSensor):
+
+	"""Abstract base class for sensors subscribed to one signal."""
+
+	__metaclass__ = ABCMeta
+
+	@abstractmethod
+	def __init__(self, signal, *args, **kwargs):
+		"""Constructor.
+
+		:param signal: signal to subscribe to
+
+		"""
+		super(SingleSignalSensor, self).__init__(*args, **kwargs)
+		self._signal = signal
+
+		# subscribe :meth:`on_send` handler to the signal sent by ``sender``
+		dispatcher.connect(self.on_send, signal=self._signal,
+			sender=self._sender)
+
+
+class MultipleSignalsSensor(BaseSignalSensor):
+
+	"""Abstract base class for sensors subscribed to multiple signals."""
+
+	__metaclass__ = ABCMeta
+
+	@abstractmethod
+	def __init__(self, signals, *args, **kwargs):
+		"""Constructor.
+
+		:param signals: signals to subscribe to
+		:type signals: iterable
+
+		"""
+		super(MultipleSignalsSensor, self).__init__(*args, **kwargs)
+		self._signals = signals
+
+		# subscribe :meth:`on_send` handler to the signals in :attr:`_signals`
+		# sent by ``sender``
+		for signal in self._signals:
+			dispatcher.connect(self.on_send, signal=signal,
+				sender=self._sender)
+
+
+class BodySensor(Sensor):
+
+	"""Abstract base class for sensors whose source of data is a body."""
+
+	__metaclass__ = ABCMeta
+
+	def __init__(self, body):
+		super(BodySensor, self).__init__(body)
+
+	@property
+	def body(self):
+		return self.source
+
+
+class JointSensor(Sensor):
+
+	"""Abstract base class for sensors whose source of data is a joint."""
+
+	__metaclass__ = ABCMeta
+
+	def __init__(self, joint):
+		super(JointSensor, self).__init__(joint)
+
+	@property
+	def joint(self):
+		return self.source
+
+
+class ActuatedJointSensor(JointSensor):
+
+	"""Sensor whose source of data is an ``ActuatedJoint`` joint."""
+
+	__metaclass__ = ABCMeta
+
+
+class SimulationSensor(Sensor):
+
+	"""Abstract base class for sensors whose source of data is a simulation."""
+
+	__metaclass__ = ABCMeta
+
+	def __init__(self, sim):
+		"""Constructor.
+
+		:param sim: simulation
+		:type sim: :class:`ars.model.simulator.Simulation`
+
+		"""
+		super(SimulationSensor, self).__init__(sim)
+
+	@property
+	def sim(self):
+		return self.source
+
+#==============================================================================
+# classes
+#==============================================================================
+
+
+class RotaryJointSensor(JointSensor):
+
+	"""Sensor measuring the angle (and its rate) of a rotary joint."""
+
+	def _build_data(self):
+		kwargs = {
+			'angle': self.joint.angle,
+			'angle_rate': self.joint.angle_rate,
+			}
+		return SensorData(**kwargs)
+
+
+class JointTorque(SingleSignalSensor):
+
+	"""Sensor measuring torque added to a joint."""
+
+	signal = signals.JOINT_POST_ADD_TORQUE
+
+	def __init__(self, sim_joint, sim):
+		# The sender to connect to is sim_joint.joint because
+		# :class:`SimulatedJoint` wraps the "real" joint object that sends the
+		# signal.
+		super(JointTorque, self).__init__(signal=self.signal,
+			sender=sim_joint.joint)
+		self._sim = sim
+
+	def _build_data(self, sender, *args, **kwargs):
+		return SensorData(**{'torque': kwargs.get('torque')})
+
+	def _get_time(self):
+		return self._sim.sim_time
+
+
+class JointForce(SingleSignalSensor):
+
+	"""Sensor measuring force 'added' to a joint."""
+
+	signal = signals.JOINT_POST_ADD_FORCE
+
+	def __init__(self, sim_joint, sim):
+		super(JointForce, self).__init__(signal=self.signal,
+			sender=sim_joint.joint)
+		self._sim = sim
+
+	def _build_data(self, sender, *args, **kwargs):
+		return SensorData(**{'force': kwargs.get('force')})
+
+	def _get_time(self):
+		return self._sim.sim_time
+
+
+class JointPower(MultipleSignalsSensor):
+
+	"""Sensor measuring power applied by a joint (due to force and torque)."""
+
+	signals = [JointTorque.signal, JointForce.signal]
+	
+	def __init__(self, sim_joint, sim):
+		super(JointPower, self).__init__(signals=self.signals,
+			sender=sim_joint.joint)
+		self._sim = sim
+
+	def _build_data(self, sender, *args, **kwargs):
+		power = 0.0
+		# both are scalars (float)
+		torque = kwargs.get('torque')
+		force = kwargs.get('force')
+
+		try:
+			if torque is not None:
+				power += torque * sender.angle_rate
+			if force is not None:
+				power += force * sender.position_rate
+		except Exception as e:
+			print('Error when calculating power. Exception:\n%s' % e)
+
+		return SensorData(**{'power': power})
+	
+	def _get_time(self):
+		return self._sim.sim_time
+
+
+class Laser(Sensor):
+
+	"""Laser scanner."""
+
+	def __init__(self, space, max_distance=10.0):
+		self._ray = coll.Ray(space, max_distance)
+		super(Laser, self).__init__(self._ray)
+
+	def on_change(self, time=get_current_epoch_time()):
+		super(Laser, self).on_change(time)
+		self._ray.clear_contacts()
+
+	def set_position(self, pos):
+		"""Set position of the ray.
+
+		Useful mainly when it is not attached to a body.
+
+		:param pos: position
+		:type pos: 3-sequence of floats
+
+		"""
+		# TODO: if mounted, w.r.t what?
+		self._ray.set_position(pos)
+
+	def set_rotation(self, rot):
+		"""Set rotation of the ray.
+
+		Useful mainly when it is not attached to a body.
+
+		:param rot: rotation matrix
+		:type rot: 9-sequence of floats
+
+		"""
+		# TODO: if mounted, w.r.t what?
+		self._ray.set_rotation(rot)
+
+	def get_ray(self):
+		return self._ray
+
+	def _build_data(self):
+		ray_contact = self._ray.get_closer_contact()
+		if ray_contact is None:
+			kwargs = {'distance': None}
+		else:
+			kwargs = {'distance': ray_contact.depth}
+		return SensorData(**kwargs)
+
+
+class LaserRangeFinder(BodySensor):
+
+	"""Laser range finder."""
+
+	pass
+
+
+class GPS(BodySensor):
+
+	"""Retrieve a body's XYZ position."""
+
+	def _build_data(self):
+		kwargs = {'position': self.body.get_position()}
+		return SensorData(**kwargs)
+
+
+class Velometer(BodySensor):
+
+	"""Calculate and retrieve a body's linear and angular velocity."""
+
+	def _build_data(self):
+		kwargs = {'linear velocity': self.body.get_linear_velocity(),
+					'angular velocity': self.body.get_angular_velocity(), }
+		return SensorData(**kwargs)
+
+
+class Accelerometer(BodySensor):
+
+	"""Calculate and retrieve a body's linear and angular acceleration.
+
+	.. warning::
+	  The provided `time_step` is used to calculate the acceleration based on
+	  the velocity measured at two instants in time. If subsequent calls to
+	  `on_change` are separated by a simulation time period different to the
+	  given `time_step`, the results will be invalid.
+
+	"""
+
+	def __init__(self, body, time_step):
+		super(Accelerometer, self).__init__(body)
+		self._time_step = time_step
+
+	def _build_data(self):
+		linear_vel_prev, angular_vel_prev = self.body.get_saved_velocities()
+
+		linear_vel = self.body.get_linear_velocity()
+		angular_vel = self.body.get_angular_velocity()
+		self.body.save_velocities()
+
+		linear_accel = calc_acceleration(self._time_step,
+			linear_vel_prev, linear_vel)
+		angular_accel = calc_acceleration(self._time_step,
+			angular_vel_prev, angular_vel)
+
+		kwargs = {'linear acceleration': linear_accel,
+				  'angular acceleration': angular_accel, }
+		return SensorData(**kwargs)
+
+
+class Inclinometer(BodySensor):
+
+	"""Retrieve a body's `pitch` and `roll`."""
+
+	def _build_data(self):
+		pitch, roll = calc_inclination(self.body.get_rotation())
+		kwargs = {'pitch': pitch, 'roll': roll}
+		return SensorData(**kwargs)
+
+
+class KineticEnergy(BodySensor):
+
+	r"""Retrieve a body's kinetic energy, both due to translation and rotation.
+
+	.. math::
+		E_t &= \frac{1}{2} m v^2 = \frac{1}{2} m \cdot v^\top v \\
+		E_r &= \frac{1}{2} I \omega^2 = \frac{1}{2} \omega^\top \mathbf{I} \omega
+
+	"""
+
+	def _build_data(self):
+		kwargs = {
+			'translation energy': self.body.calc_translation_kinetic_energy(),
+			'rotation energy': self.body.calc_rotation_kinetic_energy(), }
+		return SensorData(**kwargs)
+
+
+class PotentialEnergy(BodySensor):
+
+	r"""Retrieve a body's potential energy.
+
+	Calculated based on the current position (`x`) and world's gravitational
+	acceleration (`g`).
+
+	.. math::
+		E_p = m \cdot g \cdot h = - m \cdot g^\top x
+
+	"""
+
+	def __init__(self, body, gravity):
+		super(PotentialEnergy, self).__init__(body)
+		self._gravity = gravity
+
+	def _build_data(self):
+		potential_e = self.body.calc_potential_energy(self._gravity)
+		kwargs = {'potential energy': potential_e, }
+		return SensorData(**kwargs)
+
+
+class TotalEnergy(BodySensor):
+
+	r"""Retrieve a body's potential and kinetic energy.
+
+	The kinetic energy accounts for translation and rotation.
+
+	.. math::
+		E_p &= m \cdot g \cdot h = - m \cdot g^\top x \\
+		E_k &= \frac{1}{2} m \cdot v^\top v
+		  + \frac{1}{2} \omega^\top \mathbf{I} \omega
+
+	"""
+
+	def __init__(self, body, gravity, disaggregate=False):
+		super(TotalEnergy, self).__init__(body)
+		self._gravity = gravity
+		self._disaggregate = disaggregate
+
+	def _build_data(self):
+		potential_e = self.body.calc_potential_energy(self._gravity)
+		linear_ke = self.body.calc_translation_kinetic_energy()
+		angular_ke = self.body.calc_rotation_kinetic_energy()
+
+		if self._disaggregate:
+			kwargs = {
+				'potential energy': potential_e,
+				'kinetic energy': linear_ke + angular_ke, }
+		else:
+			kwargs = {'total energy': potential_e + linear_ke + angular_ke, }
+		return SensorData(**kwargs)
+
+
+class SystemTotalEnergy(SimulationSensor):
+
+	r"""Retrieve a system's total potential and kinetic energy.
+
+	It considers all bodies in the simulation. The kinetic energy accounts for
+	translation and rotation.
+
+	.. math::
+		E_p &= m \cdot g \cdot h = - m \cdot g^\top x \\
+		E_k &= \frac{1}{2} m \cdot v^\top v
+		  + \frac{1}{2} \omega^\top \mathbf{I} \omega
+
+	"""
+
+	def __init__(self, sim, disaggregate=False):
+		super(SystemTotalEnergy, self).__init__(sim)
+		self._disaggregate = disaggregate
+
+	def _build_data(self):
+		gravity = self.sim.gravity
+		bodies = self.sim.get_bodies()
+		potential_e = 0.0
+		linear_ke = 0.0
+		angular_ke = 0.0
+
+		for body in bodies:
+			potential_e += body.calc_potential_energy(gravity)
+			linear_ke += body.calc_translation_kinetic_energy()
+			angular_ke += body.calc_rotation_kinetic_energy()
+
+		if self._disaggregate:
+			kwargs = {
+				'potential energy': potential_e,
+				'kinetic energy': linear_ke + angular_ke, }
+		else:
+			kwargs = {'total energy': potential_e + linear_ke + angular_ke, }
+		return SensorData(**kwargs)
+
+#==============================================================================
+# aux classes
+#==============================================================================
+
+
+class SensorData:
+
+	"""Data structure to pack a sensor measurement's information."""
+
+	def __init__(self, *args, **kwargs):
+		self._time = None
+		self._args = args		# as a tuple?
+		self._kwargs = kwargs 	# as a dictionary?
+
+	def get_time(self):
+		return self._time
+
+	def set_time(self, time):
+		self._time = time
+
+	def get_args(self):
+		return self._args
+
+	def get_kwargs(self):
+		return self._kwargs
+
+	def get_arg(self, index):
+		return self._args[index]
+
+	def get_kwarg(self, key):
+		return self._kwargs[key]
+
+	def __str__(self):
+		line = '|'
+		if self._time is not None:
+			line = ' %f |' % self._time
+		for arg in self._args:
+			line += ' %s |' % str(arg)
+		for key in self._kwargs:
+			line += ' %s: %s |' % (str(key), str(self._kwargs[key]))
+		return line
+
+
+class SensorDataQueue(ars.utils.containers.Queue):
+
+	"""Queue-like container for sensor measurements."""
+
+	pass

ars/model/simulator/__init__.py

 		return actors
 
 	def add_object(self, sim_object):
-		"""Adds an object to the internal dictionary of simulated ones"""
+		"""Add ``sim_object`` to the internal dictionary of simulated objects.
+
+		If its name equals an already registered key, it will be modified
+		using its string representation, for example:
+
+		>>> sim.add_object(sim_object)
+		sphere/<ars.model.simulator.SimulatedBody object at 0x3a4bed0>
+
+		:param sim_object: object to add
+		:type sim_object: :class:`SimulatedObject`
+		:return: name/key of the object
+		:rtype: string
+
+		"""
 		name = sim_object.get_name()
 		if (name in self._objects.keys()) and name:
-			name = name + '/' + str(sim_object.body)
+			name = name + '/' + str(sim_object)
 			sim_object.set_name(name)
 		self._objects[name] = sim_object
 		return name
 		The only movement allowed is translation along ``axis``.
 
 		:return: the name under which the slider was stored, which could be
-		different from the given ``name``
+			different from the given ``name``
 
 		"""
 		body1 = obj1.body
 			angle))
 		# Matrix (of the rotation to apply)
 		# multiplies from the LEFT the actual one
-		rot_final = mu.matrix_as_tuple(mu.matrix3_multiply(rot_to_apply, rot_now))
+		rot_final = mu.matrix_as_tuple(mu.matrix_multiply(rot_to_apply, rot_now))
 		self.set_rotation(rot_final)
 
 	def offset_by_position(self, offset_pos):
 
 	@abstractmethod
 	def get_position(self):
+		"""Get the position of the object.
+
+		:return: position
+		:rtype: 3-sequence of floats
+
+		"""
 		pass
 
 	@abstractmethod
-	def set_position(self, position):
+	def set_position(self, pos):
+		"""Set the orientation of the object.
+
+		:param pos: position
+		:type pos: 3-sequence of floats
+
+		"""
 		pass
 
 	@abstractmethod
 	def get_rotation(self):
+		"""Get the orientation of the object.
+
+		:return: rotation matrix
+		:rtype: 9-sequence of floats
+
+		"""
 		pass
 
 	@abstractmethod
-	def set_rotation(self, rot_matrix):
+	def set_rotation(self, rot):
+		"""Set the orientation of the object.
+
+		:param rot: rotation matrix
+		:type rot: 9-sequence of floats
+
+		"""
 		pass
 
+	def get_pose(self):
+		"""Get the pose (3D position and rotation) of the object.
+
+		:return: pose
+		:rtype: :class:`ars.utils.geometry.Transform`
+
+		"""
+		return gemut.Transform(self.get_position(), self.get_rotation())
+
+	def set_pose(self, pose):
+		"""Set the pose (3D position and rotation) of the object.
+
+		:param pose:
+		:type pose: :class:`ars.utils.geometry.Transform`
+
+		"""
+		self.set_position(pose.get_translation())
+		rot = pose.get_rotation()
+		self.set_rotation(mu.matrix_as_tuple(rot))
+
 
 class SimulatedBody(SimulatedPhysicsObject):
 	"""Class encapsulating the physics, collision and graphical objects
 		return self._body
 
 	def get_position(self):
+		"""Get the position of the body.
+
+		:return: position
+		:rtype: 3-sequence of floats
+
+		"""
 		return self._body.get_position()
 
-	def set_position(self, position):
-		self._body.set_position(position)
+	def set_position(self, pos):
+		"""Set the orientation of the body.
+
+		:param pos: position
+		:type pos: 3-sequence of floats
+
+		"""
+		self._body.set_position(pos)
 
 	def get_rotation(self):
+		"""Get the orientation of the body.
+
+		:return: rotation matrix
+		:rtype: 9-sequence of floats
+
+		"""
 		return self._body.get_rotation()
 
-	def set_rotation(self, rot_matrix):
-		self._body.set_rotation(rot_matrix)
+	def set_rotation(self, rot):
+		"""Set the orientation of the body.
+
+		:param rot: rotation matrix
+		:type rot: 9-sequence of floats
+
+		"""
+		self._body.set_rotation(rot)
 
 	def _get_attr_in_body(self, attr, *args):
 		"""Return attribute `attr` from object `self._body`.
 		# It is necessary to have this method, even if it's not implemented
 		raise NotImplementedError()
 
-	def set_position(self, position):
+	def set_position(self, pos):
 		# It is necessary to have this method, even if it's not implemented
 		raise NotImplementedError()
 
 		# It is necessary to have this method, even if it's not implemented
 		raise NotImplementedError()
 
-	def set_rotation(self, rot_matrix):
+	def set_rotation(self, rot):
 		# It is necessary to have this method, even if it's not implemented
 		raise NotImplementedError()

ars/utils/containers.py

-
-# Created on 2012.04.28
-#
-# @author: german
-
 import collections # High-performance container datatypes
 
+
 class Queue:
+
 	"""FIFO"""
-	
+
 	def __init__(self):
 		self._queue = collections.deque()
-	
+
 	def pull(self):
 		"""Remove and return the first element of the queue"""
 
 		except IndexError:
 			raise Exception('no elements are present')
 		return element
-		
+
 	def put(self, element):
 		"""Appends the element as the last object of the queue"""
 		self._queue.append(element) # Add x to the right side of the deque
-	
+
 	def is_empty(self):
 		return len(self._queue) == 0
-	
+
 	def count(self):
 		return len(self._queue)
-	
+
 	def clear(self):
 		"""Remove all elements of the queue"""
 		self._queue.clear()
-	
+
 	def convert_to_list(self):
 		"""Return the elements in an ordered list"""
 		return list(self._queue)
 			line += '%s\n' % str(item)
 		return line
 
+
 # class Stack(Queue)
 # override 'put' and 'pull' methods, and change deque's called methods
-
-
-# Created on 2011.08.23
-#
-# @author: german
-
 """
 Generic utility functions to write variables and tuples to file;
 write and read setting from a file; modify and create tuples.
+
 """
 
 import ConfigParser as cp
 import itertools as it
 import time
 
+
 def write_var_to_file(text_file, var):
 	text_file.write(str(var) + '\n')
 
+
 def write_tuple_to_file(text_file, tuple_):
 	line = ''
 	separator = ','
 	line += new_line
 	text_file.write(line)
 
+
 def write_settings(filename, section, mapped_values):
 	"""
 	usage example
 	gu.write_settings('test.cfg', 'mySection', {'a_key':1.1111, 'another_key':False})
+
 	"""
 	config = cp.RawConfigParser()
 	config.add_section(section)
 	with open(filename, 'w') as configfile:
 		config.write(configfile)
 
+
 def read_settings(filename, section):
 	"""
 	usage example
 	settings = gu.read_settings('test.cfg', 'mySection')
 	which is a dictionary, e.g. {'a_key':1.1111, 'another_key':False}
+
 	"""
 	config = cp.RawConfigParser()
 	config.read(filename)
 
 	return dict(config.items(section))
 
+
 def insert_in_tuple(tuple_, index, item_):
 	tuple_to_list = list(tuple_)
 	tuple_to_list.insert(index, item_)
 	return tuple(tuple_to_list)
 
+
 def nested_iterable_to_tuple(iterable_):
 	return tuple(it.chain.from_iterable(iterable_))
 
+
 def get_current_epoch_time():
 	"""Returns the current time (float) in seconds since the Epoch. Fractions
 	of a second may be present if the OS provides them (UNIX-like do).
 	http://goo.gl/7cKVz
+
 	"""
 	return time.time()

ars/utils/geometry.py

 """
 
 
-def _rot_matrix_to_rpy_angles(rot_matrix, zyx=False):
+def _rot_matrix_to_rpy_angles(rot, zyx=False):
 	"""The roll-pitch-yaw angles corresponding to a rotation matrix.
+
 	The 3 angles RPY correspond to sequential rotations about the X, Y and Z
 	axes respectively.
 
 
 	Translated to Python by German Larrain.
 
-	Original version in Matlab, part of	'The Robotics Toolbox for Matlab (RTB)'
+	Original version in Matlab, part of 'The Robotics Toolbox for Matlab (RTB)'
 	as '/robot/tr2rpy.m'
 	Copyright (C) 1993-2011, by Peter I. Corke. See `rtb_license`.
 
 	"""
-	m = mut.matrix_as_3x3_tuples(rot_matrix)
+	m = mut.matrix_as_3x3_tuples(rot)
 
 	# "eps: distance from 1.0 to the next largest double-precision number"
 	eps = 2e-52 # http://www.mathworks.com/help/techdoc/ref/eps.html
 
 
 class Transform:
-	def __init__(self, position, rot_matrix):
+
+	r"""An homogeneous transform.
+
+	It is a composition of rotation and translation. Mathematically it can be
+	expressed as
+
+	.. math::
+
+		\left[
+		\begin{array}{ccc|c}
+			 &  &  &  \\
+			 & R &  & T \\
+			 & &  &  \\
+			\hline
+			0 & 0 & 0 & 1
+		  \end{array}
+		\right]
+
+	where `R` is the 3x3 submatrix describing rotation and `T` is the
+	3x1 submatrix describing translation.
+
+	source:
+	http://en.wikipedia.org/wiki/Denavit%E2%80%93Hartenberg_parameters#Denavit-Hartenberg_matrix
+
+	"""
+
+	def __init__(self, pos=None, rot=None):
+		"""Constructor.
+
+		With empty arguments it's just a 4x4 identity matrix.
+
+		:param pos: a size 3 vector, or 3x1 or 1x3 matrix
+		:type pos: tuple, :class:`numpy.ndarray` or None
+		:param rot: 3x3 or 9x1 rotation matrix
+		:type rot: tuple, :class:`numpy.ndarray` or None
+