Commits

German Larrain committed c156241 Merge

Merge with dev

  • Participants
  • Parent commits b73b3c3, f9c1381
  • Branches default

Comments (0)

Files changed (198)

File .hgignore

-relre:^.*[.]pyc$
-
-syntax: regexp
-^html_docs$
-glob:.project
-glob:.pydevproject
-glob:gui.ui
 2c3d3e20fe0e73d786f8b210ae111cecca0f9645 ARS version 0.1.0
 2c3d3e20fe0e73d786f8b210ae111cecca0f9645 ARS version 0.1.0
 aaeb4bf9275726059116a1fb6a1b7dcbfaf2b11d ARS version 0.1.0
+2cf1bc37723261db618dc1e1bbb23bdccb6fb516 initial tests version 4.0.0
+c35f1a476262b7724ac88f95784b870176f2ac8a initial tests version 3.1.0
+2cf1bc37723261db618dc1e1bbb23bdccb6fb516 version 4.0.0
+0000000000000000000000000000000000000000 version 4.0.0
+c35f1a476262b7724ac88f95784b870176f2ac8a version 3.1.0
+0000000000000000000000000000000000000000 version 3.1.0
+aaeb4bf9275726059116a1fb6a1b7dcbfaf2b11d version 0.1.0
+aaeb4bf9275726059116a1fb6a1b7dcbfaf2b11d ARS version 0.1.0
+0000000000000000000000000000000000000000 ARS version 0.1.0
+5ddcfa2fd4477addf6e402397ea4a24110034764 version 0.2.0
+ef537d62f3c4983bfac19cc0cb5cf62ef3a2f1e5 version 0.3dev1
+83e4b0f8234299b9c276ce0d386678669c9cb48f version 0.3dev2
+ab8537c5a0c9db7fbfd66919e7da3fea1219467d version 0.3dev
+c35f1a476262b7724ac88f95784b870176f2ac8a initial tests version 3.1.0
+0000000000000000000000000000000000000000 initial tests version 3.1.0
+2cf1bc37723261db618dc1e1bbb23bdccb6fb516 initial tests version 4.0.0
+0000000000000000000000000000000000000000 initial tests version 4.0.0
+626f6c6571037a2d70985feb7d42e0b86c867bc0 version 0.3dev3

File AUTHORS

-German Larrain <gelarrai@uc.cl>
-Miguel Torres-Torriti
+German Larrain-Munoz <glarrain@example.com>
+Miguel Torres-Torriti
+v<version>, <date> -- Initial release.

File INSTALL

-install
-	sudo checkinstall python setup.py install
-run
-	python RagdollPyOdeVtk.py
-remove
-	dpkg -r ragdoll3
-
-
-documentation
-	epydoc generates API documentation for Python modules, based on their docstrings
-	
-	to build it, run the following command from the project main directory
-		epydoc -v --config epydoc.cfg ars/
+==============================
+INSTALL
+==============================
+
+Detailed instructions for installation of ARS and its requirements are available
+in the online documentation [1].
+
+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.
+
+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].
+
+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_debian.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.
+To exit the interpreter, use "quit()" or Ctrl+D.
+
+
+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
+
+
+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.
+Copyright (c) 2012, Germán Larraín Muñoz
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+include *.py *.txt *.rst
+recursive-include demos *.py

File README

-Autonomous Robot Simulator (ARS).
-
-ARS is an open simulation suite for research and development of mobile manipulators. It will encompass a wide range of tools spanning from kinematics and dynamics simulation to robot interfacing and control.
-
-software
-* Physics engine: Open Dynamics Engine (ODE) r1810, via PyODE Python wrapper
-* Programming language: Python 2.6.5
-* 3D Visualization: Visualization Toolkit (VTK) 5.2.1
-* IDE: Eclipse 3.7.1 (Indigo SR 1) with PyDev and MercurialEclipse
-* SCM: Mercurial 1.7.5
-
-documentation:
-* API documentation generation tool: epydoc 3.0.1
-* Automatic graph drawings generation: graphviz 2.20.2
+========================================
+Autonomous Robot Simulator (ARS)
+========================================
+
+ARS is a physically-accurate open-source simulation suite for research and
+development of mobile manipulators and, in general, any multi-body system. It
+is modular, easy to learn and use, and can be a valuable tool in the process
+of robot design, in the development of control and reasoning algorithms, as
+well as in teaching and educational activities.
+
+It will encompass a wide range of tools spanning from kinematics and dynamics
+simulation to robot interfacing and control.
+
+The software is implemented in Python integrating the
+`Open Dynamics Engine (ODE) <https://sourceforge.net/projects/opende/>`_
+and the `Visualization Toolkit (VTK) <http://www.vtk.org/>`_.
+
+Installation and Requirements
+====================
+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
+* VTK (Visualization Toolkit) 5.8 with Python bindings
+* NumPy 1.6
+
+Documentation
+====================
+The documentation is hosted at
+`ReadTheDocs.org <http://ars-project.readthedocs.org>`_
+and it is generated dynamically after each commit to the repository.
+
+License
+==========
+This software is licensed under the OSI-approved "BSD License". To avoid
+confusion with the original BSD license from 1990, the FSF refers to it as
+"Modified BSD License". Other names include "New BSD", "revised BSD", "BSD-3",
+or "3-clause BSD".
+
+See the included LICENSE.txt file.
+
+Tests
+==========
+To run the included test suite (provided you have ``mock`` installed)::
+
+   ~/ars$ python -m unittest discover --verbose
+
+If the output is too verbose for your taste, remove the option ``--verbose``.

File ars/Notes.txt

 Rotation
 	http://en.wikipedia.org/wiki/Rotation_matrix
-	http://en.wikipedia.org/wiki/Rotation_representation_(mathematics)#Conversion_formulae_between_representations
+	http://en.wikipedia.org/wiki/Rotation_formalisms_in_three_dimensions#Conversion_formulae_between_formalisms
 	http://en.wikipedia.org/wiki/Rotation_matrix#Conversions
 	-The inverse of a rotation matrix is its transpose
 	-The determinant of a rotMat (and its transpose) is always 1

File ars/TODO.txt

 TODO
 
-general
--fix the conditions of ifs and loops that use None, ==, "and" and "or" inappropriately
+Packaging and distribution
+-Add to README.txt a "Contributors" section and/or a "Thanks also to" section to list the names of people who’ve helped.
+
+"Correctness"
+-move TIMER_EVENT and KEY_PRESS_EVENT from graphics.__init__ to graphics.adapters
+
+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
+
+General
 -dBodySetFiniteRotationMode
 -dBodySetFiniteRotationAxis. is it necessary to set this if dBodySetFiniteRotationMode = true?
 -rename package "model" to "core"?
+-move tests in utils.mathematical to a test file
 
-visualization
+Visualization
 -create a subclass of vtkInteractorStyleTrackball (http://www.vtk.org/doc/nightly/html/classvtkInteractorStyleTrackball.html)
 -remove the event bindings for this class which interfere with the desired behavior
 	http://vtk.org/doc/nightly/html/classvtkRenderWindowInteractor.html
 -see VTK/Tutorials/InteractorStyleSubclass (http://www.vtk.org/Wiki/VTK/Tutorials/InteractorStyleSubclass) for ideas
--save screenshot: http://www.vtk.org/Wiki/VTK/Examples/Python/Screenshot
 -for a plane use a PlaneSource instead of a slim box
 	http://www.vtk.org/Wiki/VTK/Examples/Python/GeometricObjects/Display/Plane
 -add shadows
 -draw joints: use a point for the anchor, and an arrow for each axis
+-use the position argument to create Axes object
 
-joints
+Joints
 -store joint rotation limits and resistive force factors
 
 GUI
 -Qt integration
 	from PyQt4 import QtCore, QtGui, uic
-	'''
 	# Create a PyQt window using a .ui file generated with Qt Designer ...
 	application = QtGui.QApplication(sys.argv)
 	window = uic.loadUi("gui.ui")
 	
 	window.show()
 	application.exec_()
-	'''
-
-packaging
--if the software is installed using by executing
-		sudo python setup.py install
-	or
-		sudo checkinstall python setup.py install
-	then files will be created in directories like this
-		/usr/local/lib/python2.6/dist-packages/RagdollPyOdeVtk.py
-	and
-		/usr/local/lib/python2.6/dist-packages/model/robot/sensors.py to sensors.pyc
-	which is not desirable because they are not under a single directory named after the project,
-	with an appropriate structure which may also prevent clashes with other projects. 

File ars/__init__.py

+# based on: ``django.__init__`` @ commit 5b644a5
+# see its license in docs/Django BSD-LICENSE.txt
+VERSION = (0, 4, 0, 'alpha', 1)  # i.e. 0.4a1
+
+
+def get_version(*args, **kwargs):
+	# Only import if it's actually called.
+	from .utils.version import get_version
+	return get_version(*args, **kwargs)

File ars/app/__init__.py

+"""Main package of the software.
+It contains the Program class which is the core application controller.
 
-# Created on 2011.10.14
-#
-# @author: german
-
-# TODO: attribute the code sections that were copied from somewhere else
-
-import sys
+"""
 from abc import abstractmethod
 
-import ars.graphics.adapters as gp
-from ars.model.simulator import Simulation
+from .. import exceptions as exc
+from ..graphics import adapters as gp
+from ..lib.pydispatch import dispatcher
+from ..model.simulator import Simulation, signals
 
-class Program():
 
-	WRITE_DATA_FILES = False
+class Program(object):
+
+	"""Main class of ARS.
+
+	To run a custom simulation, create a subclass.
+	It must contain an implementation of the 'create_sim_objects' method
+	which will be called during the simulation creation.
+
+	To use it, only two statements are necessary:
+
+	* create an object of this class
+		>>> sim_program = ProgramSubclass()
+	* call its 'start' method
+		>>> sim_program.start()
+
+	"""
+
 	DEBUG = False
-	
+	PRINT_KEY_INFO = True
+
 	WINDOW_TITLE = "Autonomous Robot Simulator"
-	WINDOW_POSITION_X = 0
-	WINDOW_POSITION_Y = 0
-	WINDOW_SIZE_WIDTH = 1024 #640
-	WINDOW_SIZE_HEIGHT = 768 #480
+	WINDOW_POSITION = (0, 0)
+	WINDOW_SIZE = (1024, 768)  # (width,height)
 	WINDOW_ZOOM = 1.0
-	
+	CAMERA_POSITION = (10, 8, 10)
+
+	BACKGROUND_COLOR = (1, 1, 1)
+
 	FPS = 50
 	STEPS_PER_FRAME = 50
-	
+
+	FLOOR_BOX_SIZE = (10, 0.01, 10)
+
 	def __init__(self):
-		
+		"""Constructor. Defines some attributes and calls some initialization
+		methods to:
+
+		* set the basic mapping of key to action,
+		* create the visualization window according to class constants,
+		* create the simulation.
+
+		"""
 		self.do_create_window = True
-		self.data_files_names = None # TODO
-		self.data_files = None # TODO
-		
+
 		self.key_press_functions = None
 		self.sim = None
-		
+		self._screenshot_recorder = None
+
 		# (key -> action) mapping
 		self.set_key_2_action_mapping()
-		
-		self.gAdapter = gp.VtkAdapter()
-		self.gAdapter.create_window(self.WINDOW_TITLE,
-							 (self.WINDOW_POSITION_X, self.WINDOW_POSITION_Y),
-							 (self.WINDOW_SIZE_WIDTH, self.WINDOW_SIZE_HEIGHT),
-							 zoom = self.WINDOW_ZOOM, background_color=(1,1,1))
-		
+
+		self.gAdapter = gp.VtkAdapter(
+			self.WINDOW_TITLE,
+			self.WINDOW_POSITION,
+			self.WINDOW_SIZE,
+			zoom=self.WINDOW_ZOOM,
+			background_color=self.BACKGROUND_COLOR,
+			cam_position=self.CAMERA_POSITION)
+
 		self.create_simulation()
-	
+
 	def start(self):
-		
-		if self.WRITE_DATA_FILES: self.sim.data_files = self.open_files()
-		
+		"""Starts (indirectly) the simulation handled by this class by starting
+		the visualization window. If it is closed, the simulation ends. It will
+		restart if :attr:`do_create_window` has been previously set to ``True``.
+
+		"""
 		while self.do_create_window:
 			self.do_create_window = False
-			self.gAdapter.start_window(self.sim.on_idle, self.reset_simulation, self.on_action_selection)
-		
-		# after the window is closed
-		if self.WRITE_DATA_FILES: self.close_files(self.sim.data_files)
-	
+			self.gAdapter.start_window(self.sim.on_idle, self.reset_simulation,
+				self.on_action_selection)
+
+	def finalize(self):
+		"""Finalize the program, deleting or releasing all associated resources.
+
+		Currently, the following is done:
+
+		* the graphics adapter is told to
+			:meth:`ars.graphics.Adapter.finalize_window`
+		* all attributes are set to None or False
+
+		A finalized program file cannot be used for further simulations.
+
+		.. note::
+			This method may be called more than once without error.
+
+		"""
+		if self.gAdapter is not None:
+			try:
+				self.gAdapter.finalize_window()
+			except AttributeError:
+				pass
+
+		self.do_create_window = False
+		self.key_press_functions = None
+		self.sim = None
+		self._screenshot_recorder = None
+		self.gAdapter = None
+
 	def reset_simulation(self):
-		print("reset simulation")
+		"""Resets the simulation by resetting the graphics adapter and creating
+		a new simulation.
+
+		"""
+		if self.PRINT_KEY_INFO:
+			print("reset simulation")
 		self.do_create_window = True
 		self.gAdapter.reset()
 		self.create_simulation()
-		
-	def create_simulation(self):
-		
+
+	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.
+
+		"""
 		# set up the simulation parameters
 		self.sim = Simulation(self.FPS, self.STEPS_PER_FRAME)
 		self.sim.add_basic_simulation_objects()
-		self.sim.add_axes()
-		self.sim.add_floor(normal=(0,1,0), box_size=(10,0.01,10), color=(0.7,0.7,0.7))
-		
+
+		if add_axes:
+			self.sim.add_axes()
+		if add_floor:
+			self.sim.add_floor(normal=(0, 1, 0), box_size=self.FLOOR_BOX_SIZE,
+				color=(0.7, 0.7, 0.7))
+
 		self.create_sim_objects()
-		
+
 		# add the graphic objects
-		self.gAdapter.add_objects_list(self.sim.get_actors().values())
+		self.gAdapter.add_objects_list(self.sim.actors.values())
 		self.sim.update_actors()
-		#self.gAdapter.restartWindow()
-#		self.gAdapter.start_window(self.sim.on_idle, self.create_simulation)
 
 	@abstractmethod
 	def create_sim_objects(self):
-		pass
+		"""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)
+
+		"""
+		raise NotImplementedError()
 
 	def set_key_2_action_mapping(self):
-		self.key_press_functions = ActionMap() # TODO: add to constructor = None?
-		self.key_press_functions.add('plus', self.select_next_joint)
-		self.key_press_functions.add('minus', self.select_previous_joint)
+		"""Creates an Action map, assigns it to :attr:`key_press_functions`
+		and then adds some ``(key, function`` tuples.
+
+		"""
+		# TODO: add to constructor ``self.key_press_functions = None``?
+		self.key_press_functions = ActionMap()
 		self.key_press_functions.add('r', self.reset_simulation)
-		self.key_press_functions.add('h', self.add_force)
-		self.key_press_functions.add('n', self.add_torque)
-		self.key_press_functions.add('f', self.inc_joint_vel)
-		self.key_press_functions.add('v', self.dec_joint_vel)
-		self.key_press_functions.add('g', self.inc_joint_pos)
-		self.key_press_functions.add('b', self.dec_joint_pos)
-		# TODO: add 'toggle use of QuickStep function'
 
 	def on_action_selection(self, key):
-		print(key)
-		self.key_press_functions.call(key)
+		"""Method called after an actions is selected by pressing a key."""
+		if self.PRINT_KEY_INFO:
+			print(key)
+		try:
+			if self.key_press_functions.has_key(key):
+				if self.key_press_functions.is_repeat(key):
+					f = self.key_press_functions.get_function(key)
+					self.sim.all_frame_steps_callbacks.append(f)
+				else:
+					self.key_press_functions.call(key)
+			else:
+				if self.PRINT_KEY_INFO:
+					print('unregistered key: %s' % key)
+		except Exception as ex:
+			print(ex)
 
-#===============================================================================
-# KEYPRESS action functions
-#===============================================================================
-	
-	def select_next_joint(self):
-		'''
-		select next joint for future user actions
-		'''
-		print('select_next_joint has not been implemented yet')
-	
-	def select_previous_joint(self):
-		'''
-		select previous joint for future user actions
-		'''
-		print('select_previous_joint has not been implemented yet')
-	
-	def add_force(self):
-		'''
-		add force to an already selected joint
-		'''
-		print('add_force has not been implemented yet')
-	
-	def add_torque(self):
-		'''
-		add torque to an already selected joint
-		'''
-		print('add_torque has not been fully implemented')
-		self.sim.get_joint('r1').add_torque(1000) # FIXME: use member or global variable
-	
-	def inc_joint_vel(self):
-		'''
-		increment the velocity of an already selected joint
-		'''
-		print('inc_joint_vel has not been implemented yet')
-	
-	def dec_joint_vel(self):
-		'''
-		decrement the velocity of an already selected joint
-		'''
-		print('dec_joint_vel has not been implemented yet')
-		
-	def inc_joint_pos(self):
-		'''
-		increment the position of an already selected joint
-		'''
-		print('inc_joint_pos has not been implemented yet')
-	
-	def dec_joint_pos(self):
-		'''
-		decrement the position of an already selected joint
-		'''
-		print('dec_joint_pos has not been implemented yet')
+	#==========================================================================
+	# other
+	#==========================================================================
 
+	def on_pre_step(self):
+		"""This method will be called before each integration step of the simulation.
+		It is meant to be, optionally, implemented by subclasses.
 
-#===============================================================================
-# FILES methods
-#===============================================================================
+		"""
+		raise NotImplementedError()
 
-	def read_filenames(self):
-		# TODO: see Ragdoll2.ars.RagdollPyOdeVtk
-		print('read_filenames has not been implemented')
-	
-	def open_files(self):
-		# TODO: see Ragdoll2.ars.RagdollPyOdeVtk
-		print('open_files has not been implemented')
-	
-	def close_files(self, files):
-		# TODO
-		for _key in files:
-			files[_key].close()
+	def on_pre_frame(self):
+		"""This method will be called before each visualization frame is created.
+		It is meant to be, optionally, implemented by subclasses.
 
+		"""
+		raise NotImplementedError()
 
+	def create_screenshot_recorder(self, base_filename, periodically=False):
+		"""Create a screenshot (of the frames displayed in the graphics window)
+		recorder.
 
-# TODO: see if it is better that this is derived from dictionary or another builtin class
-class ActionMap:
+		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
+		screenshots will be saved in sequence. The time period between each
+		frame is determined according to :attr:`FPS`.
+
+		"""
+		self._screenshot_recorder = gp.ScreenshotRecorder(base_filename,
+			self.gAdapter)
+		if periodically:
+			period = 1.0 / self.FPS
+			self._screenshot_recorder.period = period
+		dispatcher.connect(self.record_frame, signals.SIM_PRE_FRAME)
+
+	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.
+
+		"""
+		if self._screenshot_recorder is None:
+			raise exc.ArsError('Screenshot recorder is not initialized')
+
+		try:
+			time = self.sim.sim_time
+			period = self._screenshot_recorder.period
+
+			if period is None:
+				self._screenshot_recorder.write(self.sim.num_frame)
+			else:
+				self._screenshot_recorder.write(self.sim.num_frame, time)
+		except Exception:
+			raise exc.ArsError('Could not record frame')
+
+
+class ActionMap(object):
 	def __init__(self):
 		self._map = {}
-	
-	def add(self, key, value):
-		self._map[key] = value
-	
+
+	def add(self, key, value, repeat=False):
+		self._map[key] = (value, repeat)
+
 	def has_key(self, key):
-		return self._map.has_key(key)
-	
+		return key in self._map
+
 	def get(self, key, default=None):
 		return self._map.get(key, default)
-	
+
+	def get_function(self, key):
+		return self._map.get(key)[0]
+
 	def call(self, key):
 		try:
-			self._map[key]()
+			self._map[key][0]()
 		except Exception as ex:
 			print(ex)
-	
+
+	def is_repeat(self, key):
+		return self._map.get(key)[1]
+
 	def __str__(self):
 		raise NotImplementedError()
-				
-
-class KeyPressActionMap(ActionMap):
-	'''
-	customize the behavior, knowing which strings mean existing keys or not, plus combinations (e.g. Ctrl+F1)
-	'''
-	pass

File ars/constants.py

+from .utils.mathematical import mult_by_scalar3
+
+#==============================================================================
+# GEOMETRY
+#==============================================================================
+
+X_AXIS = (1,0,0)
+Y_AXIS = (0,1,0)
+Z_AXIS = (0,0,1)
+
+RIGHTWARDS_AXIS = X_AXIS
+UPWARDS_AXIS = Y_AXIS
+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)
+
+# names according to W3C Recommendation - 4.4 Recognized color keyword names
+# http://www.w3.org/TR/SVG/types.html#ColorKeywords
+
+COLOR_BLACK = 		convert_color(0,0,0)
+COLOR_BLUE = 		convert_color(0,0,255)
+COLOR_BROWN = 		convert_color(165,42,42)
+COLOR_CYAN = 		convert_color(0,255,255)
+COLOR_GOLD =		convert_color(255,215,0)
+COLOR_GRAY = 		convert_color(128,128,128)
+COLOR_GREEN = 		convert_color(0,128,0)
+COLOR_LIME = 		convert_color(0,255,0)
+COLOR_LIME_GREEN = 	convert_color(50,205,50)
+COLOR_MAROON = 		convert_color(128,0,0)
+COLOR_MAGENTA = 	convert_color(255,0,255)
+COLOR_NAVY = 		convert_color(0,0,128)
+COLOR_OLIVE = 		convert_color(128,128,0)
+COLOR_ORANGE = 		convert_color(255,165,0)
+COLOR_PINK = 		convert_color(255,192,203)
+COLOR_PURPLE = 		convert_color(128,0,128)
+COLOR_RED = 		convert_color(255,0,0)
+COLOR_SILVER = 		convert_color(192,192,192)
+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)

File ars/exceptions.py

+"""ARS's exceptions class hierarchy.
 
-# Created on 2011.10.31
-# Last modified on 2011.10.31
-#
-# @author: german
+"""
 
-#from __builtin__ import Exception
+###############################################################################
+# doc/python2.7/html/tutorial/errors.html#tut-userexceptions
+# "When creating a module that can raise several distinct errors, a common
+# practice is to create a base class for exceptions defined by that module, and
+# subclass it to create specific exception classes for different error
+# conditions."
+###############################################################################
 
-class PhysicsEngineException(Exception):
-	def __init__(self):
-		print("PhysicsEngineException constructor")
 
-class JointCreationError(PhysicsEngineException):
-	def __init__(self):
-		print("JointCreationError constructor")
+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):
+		PhysicsEngineException.__init__(self, msg)
+		self.type = type

File ars/graphics/__init__.py

-
-# Created on 2011.08.09
-# Last modified on 2011.12.04
-#
-# @author: german
-#
-# TODO: attribute the code sections that were copied from somewhere else
-
 from abc import ABCMeta, abstractmethod
 
-import vtk
-
-from ..utilities import mathematical as mut
-
-TIMER_PERIOD = 50 # milliseconds
+TIMER_PERIOD = 50  # milliseconds
 TIMER_EVENT = 'TimerEvent'
 KEY_PRESS_EVENT = 'KeyPressEvent'
-NULL_QUAT = (0, 0, 1, 0)
-EYE_3X3 = ((1,0,0),(0,1,0),(0,0,1))
+
 
 class Axes:
+
 	__metaclass__ = ABCMeta
-	def __init__(self, position=(0,0,0), cylinder_radius=0.05):
+
+	@abstractmethod
+	def __init__(self, pos=(0, 0, 0), rot=None, cylinder_radius=0.05):
 		self._actor = None
-	
-	def get_actor(self):
+
+	@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 get_actor(self):
+
+	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):
 		return self._actor
-	
+
 	@abstractmethod
 	def get_color(self):
 		return self._color
-	
+
 	@abstractmethod
 	def set_color(self, color):
 		self._color = color
 
+
 class Box(Body):
+
 	__metaclass__ = ABCMeta
-	def __init__(self, size, position, rotation=None):
-		super(Box, self).__init__(position, rotation) # http://docs.python.org/library/functions.html#super
+
+	@abstractmethod
+	def __init__(self, size, pos, rot=None):
+		super(Box, self).__init__(pos, rot)
+
 
 class Cone(Body):
+
 	__metaclass__ = ABCMeta
-	def __init__(self, height, radius, center, rotation=None, resolution = 100):
-		super(Cone, self).__init__(center, rotation)
+
+	@abstractmethod
+	def __init__(self, height, radius, center, rot=None, resolution=100):
+		"""Constructor.
+
+		:param resolution: it is the circumferential number of facets
+		:type resolution: int
+
+		"""
+		super(Cone, self).__init__(center, rot)
+
 
 class Sphere(Body):
+
 	__metaclass__ = ABCMeta
-	def __init__(self, radius, center, rotation=None, phiResolution = 50, thetaResolution = 50):
-		super(Sphere, self).__init__(center, rotation)
+
+	@abstractmethod
+	def __init__(self, radius, center, rot=None, phi_resolution=50,
+                 theta_resolution=50):
+		"""Constructor.
+
+		:param phi_resolution: resolution in the latitude (phi) direction
+		:type phi_resolution: int
+		:param theta_resolution: resolution in the longitude (theta) direction
+		:type theta_resolution: int
+
+		"""
+		super(Sphere, self).__init__(center, rot)
+
 
 class Cylinder(Body):
+
 	__metaclass__ = ABCMeta
-	def __init__(self, length, radius, center, rotation=None, resolution=10):
-		super(Cylinder, self).__init__(center, rotation)
+
+	@abstractmethod
+	def __init__(self, length, radius, center, rot=None, resolution=10):
+		super(Cylinder, self).__init__(center, rot)
+
 
 class Capsule(Body):
+
 	__metaclass__ = ABCMeta
-	def __init__(self, length, radius, center, rotation=None, resolution=10):
-		super(Capsule, self).__init__(center, rotation)
+
+	@abstractmethod
+	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, 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
-	def __init__(self):
+	def __init__(self, *args, **kwargs):
 		self.timer_count = 0
 		self.on_idle_parent_callback = None
 		self.on_reset_parent_callback = None
 		self.on_key_press_parent_callback = None
 		self._window_started = False
-	
-	@abstractmethod
-	def create_window(self, title, position, size, zoom, cam_position, color):
-		pass
 
 	@abstractmethod
 	def add_object(self, object_):
-		pass
+		raise NotImplementedError()
 
 	def add_objects_list(self, objects_list_):
 		for object_ in objects_list_:
 			self.add_object(object_)
-	
+
 	@abstractmethod
-	def start_window(self, on_idle_callback, on_reset_callback, on_key_press_callback):
-		pass
-	
+	def start_window(
+            self, on_idle_callback, on_reset_callback, on_key_press_callback):
+		raise NotImplementedError()
+
 	@abstractmethod
 	def restart_window(self):
-		pass
-	
+		raise NotImplementedError()
+
+	@abstractmethod
+	def finalize_window(self):
+		"""Finalize window and remove/clear associated resources."""
+		raise NotImplementedError()
+
 	@abstractmethod
 	def _timer_callback(self, obj, event):
-		pass
-	
+		raise NotImplementedError()
+
 	@abstractmethod
 	def _key_press_callback(self, obj, event):
-		pass
-	
+		raise NotImplementedError()
+
 	@abstractmethod
 	def reset(self):
-		pass
-	
+		raise NotImplementedError()
+
 	@staticmethod
 	@abstractmethod
-	def _update_body(body, position, rotation):
-		pass
+	def _update_pose(obj, pos, rot):
+		raise NotImplementedError()
 
 
-if __name__ == '__main__':
-	print('this is graphics.py')
+class ScreenshotRecorder:
+
+	__metaclass__ = ABCMeta
+
+	def __init__(self, base_filename):
+		self.base_filename = base_filename
+		self.last_write_time = None
+		self.period = None
+
+	@abstractmethod
+	def write(self, index, time):
+		"""
+		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()

File ars/graphics/adapters.py

-
-# Created on 2011.11.01
-# Last modified on 2011.12.04
-#
-# @author: german
-
 import vtk
 
-#import graphics as gp
-from ars import graphics as gp #TODO: change to import ars.graphics as gp
-from ..utilities import mathematical as mut
+from .. import exceptions as exc
+from .. import graphics as gp
+from ..utils import geometry as gemut
+
 
 class VtkAdapter(gp.Adapter):
-	
-	'''coupled to VTK'''
-	
-	def __init__(self):
+
+	"""Graphics adapter to the Visualization Toolkit (VTK) library"""
+
+	def __init__(
+            self, title, pos=None, size=(1000, 600), zoom=1.0,
+            cam_position=(10, 8, 10), background_color=(0.1, 0.1, 0.4),
+            **kwargs):
+
 		super(VtkAdapter, self).__init__()
-		self.ren = vtk.vtkRenderer()
-		self._title = ''
-		self._size = None
-		self._zoom = None
-		self._cam_position = None
+		self.renderer = vtk.vtkRenderer()
+		self.render_window = None
+		self.interactor = None
 
-	def create_window(self, title, position=None, size=(1000,600), zoom=1.0, cam_position=(10,8,10),
-					background_color=(0.1,0.1,0.4)):
-		
 		self._title = title
 		self._size = size
 		self._zoom = zoom
 		self._cam_position = cam_position
-		
-		self.ren.SetBackground(background_color)
+		self._background_color = background_color
 
 	def add_object(self, object_):
-		self.ren.AddActor(object_.get_actor())
-		
-	def start_window(self, on_idle_callback=None, on_reset_callback=None, on_key_press_callback=None):
-		# TODO: refactor according to restart_window(), reset() and the desired behavior
+		self.renderer.AddActor(object_.actor)
+
+	def remove_object(self, object_):
+		self.renderer.RemoveActor(object_.actor)
+
+	def start_window(self, on_idle_callback=None, on_reset_callback=None,
+                     on_key_press_callback=None):
+		# TODO: refactor according to restart_window(), reset() and the
+		# desired behavior.
 		self.on_idle_parent_callback = on_idle_callback
 		self.on_reset_parent_callback = on_reset_callback
 		self.on_key_press_parent_callback = on_key_press_callback
-		
-		# create a rendering window and RenderWindowInteractor
-		self.renWin = vtk.vtkRenderWindow()
-		self.renWin.AddRenderer(self.ren)
-		self.iren = vtk.vtkRenderWindowInteractor()
-		self.iren.SetInteractorStyle(vtk.vtkInteractorStyleTrackballCamera())
-		self.iren.SetRenderWindow(self.renWin)
-		
+
+		# Create the RenderWindow and the RenderWindowInteractor and
+		# link between them and the Renderer.
+		self.render_window = vtk.vtkRenderWindow()
+		self.interactor = vtk.vtkRenderWindowInteractor()
+		self.render_window.AddRenderer(self.renderer)
+		self.interactor.SetRenderWindow(self.render_window)
+
 		# set properties
-		self.renWin.SetSize(*self._size)
-		self.renWin.SetWindowName(self._title)
-		
-		'''
-		self.ren.ResetCamera()
-		self.ren.GetActiveCamera().SetPosition(self._cam_position)
-		self.ren.GetActiveCamera().Zoom(self._zoom)
-		'''
-		
+		self.renderer.SetBackground(self._background_color)
+		self.render_window.SetSize(*self._size)
+		self.render_window.SetWindowName(self._title)
+		self.interactor.SetInteractorStyle(
+			vtk.vtkInteractorStyleTrackballCamera())
+
+		# create and configure a Camera, and set it as renderer's active one
 		camera = vtk.vtkCamera()
 		camera.SetPosition(self._cam_position)
 		camera.Zoom(self._zoom)
-		self.ren.SetActiveCamera(camera)
-		#self.ren.ResetCameraClippingRange()
-		
-		'''
-		if self._windowStarted == True:
-			#self.iren.ReInitialize()
-			pass
-		else:
-			self.iren.Initialize()
-		'''
-		'''
-		if self.iren.GetInitialized():
-			self.renWin.Finalize()
-			
-		if not self._windowStarted:
-			self.iren.Initialize()
-		'''
-		
-		# self.iren.Initialize() seems to be not necessary
-		
-		self.renWin.Render()
-		
-		print(self.ren.GetActiveCamera().GetPosition())
-		print(self.ren.GetActiveCamera().GetFocalPoint())
-		
-		'''
-		if not self._windowStarted:
-			# events
-			self.iren.AddObserver(gp.TIMER_EVENT, self._timer_callback)
-			timerId = self.iren.CreateRepeatingTimer(gp.TIMER_PERIOD)
-			self.iren.AddObserver(gp.KEY_PRESS_EVENT, self._key_press_callback)
-			
-			self._windowStarted = True
-			self.iren.Start()
-		'''
-		self.iren.AddObserver(gp.TIMER_EVENT, self._timer_callback)
-		timerId = self.iren.CreateRepeatingTimer(gp.TIMER_PERIOD)  #@UnusedVariable
-		self.iren.AddObserver(gp.KEY_PRESS_EVENT, self._key_press_callback)
-		self.iren.Start()
-	
+		self.renderer.SetActiveCamera(camera)
+
+		self.render_window.Render()
+
+		# add observers to the RenderWindowInteractor
+		self.interactor.AddObserver(gp.TIMER_EVENT, self._timer_callback)
+		#noinspection PyUnusedLocal
+		timerId = self.interactor.CreateRepeatingTimer(gp.TIMER_PERIOD)
+		self.interactor.AddObserver(
+			gp.KEY_PRESS_EVENT, self._key_press_callback)
+		self.interactor.Start()
+
 	def restart_window(self):
 		# TODO: code according to start_window(), reset() and the desired behavior
-		#=======================================================================
-		# if self._windowStarted == True:
-		#	self.iren.Initialize()
-		#	self.renWin.Render()
-		#	self.iren.Start() 
-		#=======================================================================
-		pass
+		raise exc.ArsError()
+
+	def finalize_window(self):
+		"""Finalize and delete :attr:`renderer`, :attr:`render_window`
+		and :attr:`interactor`.
+
+		.. seealso::
+			http://stackoverflow.com/questions/15639762/
+			and
+			http://docs.python.org/2/reference/datamodel.html#object.__del__
+
+		"""
+		self.render_window.Finalize()
+		self.interactor.TerminateApp()
+
+		# Instead of `del render_window, interactor` as would be done in a
+		# script, this works too. Clearing `renderer` is not necessary to close
+		# the window, just a good practice.
+		self.renderer = None
+		self.render_window = None
+		self.interactor = None
 
 	@staticmethod
-	def _update_body(body, position, rotation): #orientation):
-		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
-		if not self.on_idle_parent_callback == None:
+		if self.on_idle_parent_callback is not None:
 			self.on_idle_parent_callback()
 		iren = obj
-		iren.GetRenderWindow().Render() # same as self.renWin.Render() ?
-	
+		iren.GetRenderWindow().Render()  # same as self.render_window.Render()?
+
 	def _key_press_callback(self, obj, event):
-		'''
+		"""
 		obj: the vtkRenderWindowInteractor
 		event: "KeyPressEvent"
-		'''
-		
+
+		"""
 		key = obj.GetKeySym().lower()
 		if self.on_key_press_parent_callback:
 			self.on_key_press_parent_callback(key)
 	def reset(self):
 		# remove all actors
 		try:
-			self.ren.RemoveAllViewProps()
-			#self.iren.UnRegister(self.renWin)
-			self.iren.ExitCallback()
-			#self.renWin.Finalize()
+			self.renderer.RemoveAllViewProps()
+			self.interactor.ExitCallback()
 		except AttributeError:
 			pass
 		#self.restartWindow()
-	
-	'''
-	Functions and methods not overriding base class functions and methods
-	'''
-	
-	@staticmethod
-	def _set_object_transform_matrix(obj, transMat):
-		obj.PokeMatrix(transMat)
+
+	#===========================================================================
+	# Functions and methods not overriding base class functions and methods
+	#===========================================================================
 
 	@staticmethod
-	def _create_transform_matrix(position, rotMatrix):
-		'''
-		position: a 3-tuple
-		rotMatrix: a 9-tuple
-		'''
-		t = mut.Transform(position, rotMatrix)
+	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(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`
+
+		"""
 		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
-	
+
+	def __init__(self):
+		self._actor = None
+
 	def get_color(self):
-		self._color = self._actor.GetProperty().GetColor() 
+		"""
+		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()
+			props_3D.InitTraversal()
+			actor_ = props_3D.GetNextProp3D()
+			while actor_ is not None:
+				self._color = actor_.GetProperty().GetColor()
+				actor_ = props_3D.GetNextProp3D()
+		else:
+			self._color = self._actor.GetProperty().GetColor()
 		return self._color
-	
+
 	def set_color(self, color):
-		self._actor.GetProperty().SetColor(color)
+		"""
+		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()
+			props_3D.InitTraversal()
+			actor_ = props_3D.GetNextProp3D()
+			while actor_ is not None:
+				actor_.GetProperty().SetColor(color)
+				actor_ = props_3D.GetNextProp3D()
+		else:
+			self._actor.GetProperty().SetColor(color)
 		self._color = color
 
+
 class Axes(VtkBody, gp.Axes):
-	def __init__(self, position=(0,0,0), cylinder_radius=0.05):
-		super(Axes, self).__init__(position, cylinder_radius)
-		
-		# 2 different methods were used here
-		# see http://stackoverflow.com/questions/7810632/how-to-use-and-set-axes-in-a-3d-scene
+
+	def __init__(self, pos=(0, 0, 0), rot=None, cylinder_radius=0.05):
+		gp.Axes.__init__(self, pos, rot, cylinder_radius)
+
+		# 2 different methods may be used here. See
+		# http://stackoverflow.com/questions/7810632/
 
 		axesActor = vtk.vtkAxesActor()
 		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):
-		super(Box, self).__init__(size, position, rotation) # http://docs.python.org/library/functions.html#super
+
+	def __init__(self, size, pos, rot=None):
+		gp.Box.__init__(self, size, pos, rot)
 
 		box = vtk.vtkCubeSource()
 		box.SetXLength(size[0])
 		box.SetYLength(size[1])
 		box.SetZLength(size[2])
-		
+
 		boxMapper = vtk.vtkPolyDataMapper()
 		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 
+
+		self._actor = boxActor
+
 
 class Cone(VtkBody, gp.Cone):
-	def __init__(self, height, radius, center, rotation=None, resolution = 20):
-		super(Cone, self).__init__(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)
 		cone.SetRadius(radius)
-		cone.SetResolution(resolution) # it is the circumferential number of facets
-		# TODO: cone.SetDirection(*direction) # The vector does not have to be normalized
-		
+		cone.SetResolution(resolution)
+		# TODO: cone.SetDirection(*direction)
+		# The vector does not have to be normalized
+
 		coneMapper = vtk.vtkPolyDataMapper()
 		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):
-	def __init__(self, radius, center, rotation=None, phiResolution = 10, thetaResolution = 10):
-		super(Sphere, self).__init__(radius, center, rotation, phiResolution, thetaResolution)
 
-		'''
-		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.
-		'''
-		sphere = vtk.vtkSphereSource()	
+	"""
+	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, rot=None, phi_resolution=20,
+            theta_resolution=20):
+		gp.Sphere.__init__(
+            self, radius, center, rot, phi_resolution, theta_resolution)
+
+		sphere = vtk.vtkSphereSource()
 		sphere.SetRadius(radius)
-		sphere.SetPhiResolution(phiResolution) # it is the circumferential number of facets
-		sphere.SetThetaResolution(thetaResolution)
-		
+		sphere.SetPhiResolution(phi_resolution)
+		sphere.SetThetaResolution(theta_resolution)
+
 		sphereMapper = vtk.vtkPolyDataMapper()
 		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=10):
-		super(Cylinder, self).__init__(length, radius, center, rotation, resolution)
 
-		'''
-		VTK: The axis of the cylinder is aligned along the global y-axis.
-		'''
+	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()
 		cyl.SetHeight(length)
 		cyl.SetRadius(radius)
-		cyl.SetResolution(resolution) # it is the circumferential number of facets
-		
+		cyl.SetResolution(resolution)
+
 		# set it to be aligned along the global Z-axis, ODE-like
 		userTransform = vtk.vtkTransform()
 		userTransform.RotateX(90.0)
-		#TODO: add argument to select the orientation axis, like cylDirection in Mass.setCylinder()
+		# TODO: add argument to select the orientation axis, like
+		# cylDirection in Mass.setCylinder()
 		transFilter = vtk.vtkTransformPolyDataFilter()
 		transFilter.SetInputConnection(cyl.GetOutputPort())
 		transFilter.SetTransform(userTransform)
-		
+
 		cylMapper = vtk.vtkPolyDataMapper()
 		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=10):
-		super(Capsule, self).__init__(length, radius, center, rotation, resolution)
-		# simplify this construction using those corresponding to Cylinder and Sphere?
+
+	def __init__(self, length, radius, center, rot=None, resolution=20):
+		gp.Capsule.__init__(self, length, radius, center, rot, resolution)
+		# TODO: simplify this construction using those corresponding to
+		# Cylinder and Sphere?
 
 		sphere1 = vtk.vtkSphereSource()
 		sphere1.SetRadius(radius)
 		sphereMapper1.SetInputConnection(sphere1.GetOutputPort())
 		sphereActor1 = vtk.vtkActor()
 		sphereActor1.SetMapper(sphereMapper1)
-		sphereActor1.SetPosition(0, 0, -length/2.0)
-	
+		sphereActor1.SetPosition(0, 0, -length / 2.0)
+
 		sphere2 = vtk.vtkSphereSource()
 		sphere2.SetRadius(radius)
 		sphere2.SetPhiResolution(resolution)
 		sphereMapper2.SetInputConnection(sphere2.GetOutputPort())
 		sphereActor2 = vtk.vtkActor()
 		sphereActor2.SetMapper(sphereMapper2)
-		sphereActor2.SetPosition(0, 0, length/2.0)
-	
+		sphereActor2.SetPosition(0, 0, length / 2.0)
+
 		# set it to be aligned along the global Z-axis, ODE-like
 		cylinder = vtk.vtkCylinderSource()
 		cylinder.SetRadius(radius)
 		cylinder.SetHeight(length)
 		cylinder.SetResolution(resolution)
-		
+
 		userTransform = vtk.vtkTransform()
 		userTransform.RotateX(90.0)
-		#TODO: add argument to select the orientation axis, like cylDirection in Mass.setCylinder()
+		# TODO: add argument to select the orientation axis, like
+		# cylDirection in Mass.setCylinder()
 		transFilter = vtk.vtkTransformPolyDataFilter()
 		transFilter.SetInputConnection(cylinder.GetOutputPort())
 		transFilter.SetTransform(userTransform)
-		
+
 		cylinderMapper = vtk.vtkPolyDataMapper()
 		cylinderMapper.SetInputConnection(transFilter.GetOutputPort())
 		cylinderActor = vtk.vtkActor()
 		cylinderActor.SetMapper(cylinderMapper)
-	
+
 		assembly = vtk.vtkAssembly()
 		assembly.AddPart(cylinderActor)
 		assembly.AddPart(sphereActor1)
 		assembly.AddPart(sphereActor2)
-		
-		VtkAdapter._update_body(assembly, center, rotation)
+
+		VtkAdapter._update_pose(assembly, center, rot)
 		self._actor = assembly
 
-def _run_tests():
-	gAdapter = VtkAdapter()
-	gAdapter.create_window("test")
-	
-	axes1 = Axes()
-	axes2 = Axes(cylinder_radius=0.02)
-	box = Box(size=(0.5,1.0,2.0), position=(2,2,2))
-	cone = Cone(1.0, 0.2, center=(1,1,1))
-	sphere = Sphere(0.5, center=(-2,-2,-2))
-	cyl = Cylinder(length=2, radius=0.25, center=(1,-1,1)) #, orientation=(mut.pi/3,mut.pi/3,mut.pi/3))
-	caps = Capsule(length=2, radius=0.25, center=(-1,-1,-1)) 
-	
-	gAdapter.add_object(axes1)
-	gAdapter.add_object(axes2)
-	gAdapter.add_object(box)
-	gAdapter.add_object(cone)
-	gAdapter.add_object(sphere)
-	gAdapter.add_object(cyl)
-	gAdapter.add_object(caps)
-	
-	# initialized as identity matrix
-	vtk_matrix1 = vtk.vtkMatrix4x4()
-	vtk_matrix2 = vtk.vtkMatrix4x4()
-	vtk_matrix3 = vtk.vtkMatrix4x4()
-	#_values = gut.nestedIterable2tuple(iterable_)
-	#vtk_matrix.DeepCopy(_values)
 
-	# translation
-	vtk_matrix1.Identity()
-	vtk_matrix1.SetElement(0, 3, 1)
-	vtk_matrix1.SetElement(1, 3, 0)
-	vtk_matrix1.SetElement(2, 3, 4)
-	
-	# axes permutation
-	vtk_matrix2.Zero(); vtk_matrix2.SetElement(3, 3, 1) # don't forget to set the lower left corner value!
-	vtk_matrix2.SetElement(0, 2, 1)
-	vtk_matrix2.SetElement(1, 0, 1)
-	vtk_matrix2.SetElement(2, 1, 1)
-	
-	# corresponds to a rotation -74 degrees aprox. around the axis 1/3*(-1,2,2)
-	vtk_matrix3.SetElement(0, 0, 0.36)
-	vtk_matrix3.SetElement(0, 1, 0.48)
-	vtk_matrix3.SetElement(0, 2, -0.8)
-	vtk_matrix3.SetElement(1, 0, -0.8)
-	vtk_matrix3.SetElement(1, 1, 0.60)
-	vtk_matrix3.SetElement(1, 2, 0)
-	vtk_matrix3.SetElement(2, 0, 0.48)
-	vtk_matrix3.SetElement(2, 1, 0.64)
-	vtk_matrix3.SetElement(2, 2, 0.60)
-	
-	#box.bodyObject.SetUserMatrix(vtk_matrix) # concatenates the transform
-	sphere.get_actor().PokeMatrix(vtk_matrix1) # sets the final transform
-	cone.get_actor().PokeMatrix(vtk_matrix2)
-	axes1.get_actor().PokeMatrix(vtk_matrix3)
-	caps.get_actor().PokeMatrix(vtk_matrix3)
-	
-	gAdapter.start_window()
+class Trimesh(VtkBody, gp.Trimesh):
 
-if __name__ == '__main__':
-	print('this is adapters.py')
-	_run_tests()
+	def __init__(self, vertices, faces, pos, rot=None):
+		gp.Trimesh.__init__(self, vertices, faces, pos, rot)
+
+		# create points
+		points = vtk.vtkPoints()
+		triangles = vtk.vtkCellArray()
+		triangle_list = []
+
+		for face in faces:
+			# get the 3 points of each face
+			p_id = points.InsertNextPoint(*vertices[face[0]])
+			points.InsertNextPoint(*vertices[face[1]])
+			points.InsertNextPoint(*vertices[face[2]])
+
+			# the triangle is defined by 3 points
+			triangle = vtk.vtkTriangle()
+			triangle.GetPointIds().SetId(0, p_id)       # point 0
+			triangle.GetPointIds().SetId(1, p_id + 1)   # point 1
+			triangle.GetPointIds().SetId(2, p_id + 2)   # point 2
+			triangle_list.append(triangle)
+
+		# insert each triangle into the Vtk data structure
+		for triangle in triangle_list:
+			triangles.InsertNextCell(triangle)
+
+		# polydata object: represents a geometric structure consisting of
+		# vertices, lines, polygons, and/or triangle strips
+		trianglePolyData = vtk.vtkPolyData()
+		trianglePolyData.SetPoints(points)
+		trianglePolyData.SetPolys(triangles)
+
+		# mapper
+		mapper = vtk.vtkPolyDataMapper()
+		mapper.SetInput(trianglePolyData)
+
+		# actor: represents an object (geometry & properties) in a rendered scene
+		triangles_actor = vtk.vtkActor()
+		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):
+		self.base_filename = base_filename
+		self.gAdapter = graphics_adapter
+		self.last_write_time = None
+		self.period = None
+
+	def write(self, index=1, time=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 render_window and create
+		# image_getter every time) was needed because we used
+		# image_getter.SetInput instead of SetInputConnection.
+		render_window = self.gAdapter.render_window
+		image_getter = vtk.vtkWindowToImageFilter()
+		image_getter.SetInput(render_window)
+		image_getter.Update()
+
+		writer = vtk.vtkPNGWriter()
+		writer.SetFileName('%s%d.png' % (self.base_filename, index))
+		writer.SetInputConnection(image_getter.GetOutputPort())
+		writer.Write()
+
+		if time is not None:
+			self.last_write_time = time

File ars/gui/__init__.py

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