1. German Larrain
  2. ars

Commits

German Larrain  committed 20eadfe Merge

Merge with dev

  • Participants
  • Parent commits 3c20548, 60c96b5
  • Branches default

Comments (0)

Files changed (89)

File CHANGES.txt

View file
  • Ignore whitespace
-v<version>, <date> -- Initial release.
+Changelog
+=========
+
+0.4a1 / 2013-04-13
+-------------------
+
+  * Blah
+
+0.3a1 / 2012-10-17
+-------------------
+
+  * Buh
+  * Burrito
+
+..
+   v0.4, 2013.04.13 -- First "real" release.
+
+..
+   v0.3a1, 2012.10.17 -- Initial release.

File README.rst

View file
  • Ignore whitespace
-========================================
-Autonomous Robot Simulator (ARS)
-========================================
+ARS: Autonomous Robot Simulator
+===============================
+
+
+.. image:: https://pypip.in/d/ARS/badge.png
+   :target: https://crate.io/packages/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
 `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/>`_.
 
 * VTK (Visualization Toolkit) 5.8 with Python bindings
 * NumPy 1.6
 
+Provided ODE and VTK are already installed, execute this to get ARS up and running:
+
+.. code-block:: bash
+
+   $ pip install ARS
+
+
 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",
 
 See the included LICENSE.txt file.
 
+
 Tests
-==========
-To run the included test suite (provided you have ``mock`` installed)::
+-----
 
-   ~/ars$ python -m unittest discover --verbose
+To run the included test suite you need more packages (``tox`` and ``mock``):
 
-If the output is too verbose for your taste, remove the option ``--verbose``.
+.. code-block:: bash
+
+   ~/ars$ pip install -r requirements_test.txt
+   ~/ars$ tox

File TODO.txt

View file
  • Ignore whitespace
+TODO
+
+Packaging and distribution
+-Fix MANIFEST.in according to output of tool "check-manifest"
+-Add to README.txt a "Contributors" section and/or a "Thanks also to" section to list the names of people who’ve helped.
+
+"Correctness"
+-Simulation has nothing to do with "frames". So, replace attributes FPS and _STEPS_PF with 'time_step'.
+-what is 'time' for in ScreenshotRecorder.write?
+-replace access to world._inner_object with its property (world.inner_object)
+-rename Simulation.get_joint as get_sim_joint
+-IROS/example4_sinusoidal_terrain: extract the *_heightfield funtions
+
+Code structure (modules and packages)
+-convert sensors module into a package
+-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()"
+-rename package "model" to "core"?
+
+
+Conventions and consistency:
+-rename demos modules in CamelCase
+-IROS/example4_sinusoidal_terrain: rename 'get_arm_compensation' to 'calc_arm_compensation'
+-rename '_inner_object'/'inner_object' to '_inner_obj'/'inner_obj'
+
+
+Python "idioms":
+-verify if it's pythonic to have explicit default values (tuples) instead of `None`
+and setting the value inside the function, as when using mutable objects (it's a must in this case).
+For example, see graphics Engine constructor.
+
+
+ODE
+-dBodySetFiniteRotationMode
+-dBodySetFiniteRotationAxis. is it necessary to set this if dBodySetFiniteRotationMode = true?
+-wrap code in factory functions with a try/except, like joint constructors.
+
+
+Tests
+-split 'tests' and what are examples of how to use external libs
+-move tests in utils.mathematical to a test file
+-extract functions from trimesh_collision: '*_heightfield', 'get_faces', 'swap_faces_indices'
+
+Documentation
+-document that 'Queue' is a wrapper of 'deque', one of the high performance
+container datatypes in built-in 'collections' module
+
+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
+-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
+-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")
+	vertex_slider = window.findChild(QtGui.QSlider, "vertexCount")
+	edge_slider = window.findChild(QtGui.QSlider, "edgeCount")
+	
+	window.show()
+	application.exec_()
+
+-simple, useful example: https://code.google.com/p/pychrysanthemum/

File ars/TODO.txt

  • Ignore whitespace
-TODO
-
-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
--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
--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
--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")
-	vertex_slider = window.findChild(QtGui.QSlider, "vertexCount")
-	edge_slider = window.findChild(QtGui.QSlider, "edgeCount")
-	
-	window.show()
-	application.exec_()

File ars/__init__.py

View file
  • Ignore whitespace
+"""ARS is a physically-accurate open-source simulation suite for research and
+development of mobile manipulators and, in general, any multi-body system.
+
+"""
 # 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
+
+# for a "real" release, replace 'alpha' with 'final'
+VERSION = (0, 5, 0, 'alpha', 1)  # i.e. 0.5a1
 
 
 def get_version(*args, **kwargs):

File ars/app/__init__.py

View file
  • Ignore whitespace
 
 """
 from abc import abstractmethod
+import logging
 
 from .. import exceptions as exc
-from ..graphics import adapters as gp
+from ..graphics import vtk_adapter as gp
 from ..lib.pydispatch import dispatcher
 from ..model.simulator import Simulation, signals
 
 
+logger = logging.getLogger(__name__)
+
+
 class Program(object):
 
 	"""Main class of ARS.
 
 	"""
 
-	DEBUG = False
-	PRINT_KEY_INFO = True
-
 	WINDOW_TITLE = "Autonomous Robot Simulator"
 	WINDOW_POSITION = (0, 0)
 	WINDOW_SIZE = (1024, 768)  # (width,height)
 		# (key -> action) mapping
 		self.set_key_2_action_mapping()
 
-		self.gAdapter = gp.VtkAdapter(
+		self.gAdapter = gp.Engine(
 			self.WINDOW_TITLE,
 			self.WINDOW_POSITION,
 			self.WINDOW_SIZE,
 
 		Currently, the following is done:
 
-		* the graphics adapter is told to
-			:meth:`ars.graphics.Adapter.finalize_window`
+		* the graphics engine is told to
+			:meth:`ars.graphics.base.Engine.finalize_window`
 		* all attributes are set to None or False
 
 		A finalized program file cannot be used for further simulations.
 		a new simulation.
 
 		"""
-		if self.PRINT_KEY_INFO:
-			print("reset simulation")
+		logger.info("reset simulation")
+
 		self.do_create_window = True
 		self.gAdapter.reset()
 		self.create_simulation()
 		"""
 		# set up the simulation parameters
 		self.sim = Simulation(self.FPS, self.STEPS_PER_FRAME)
+		self.sim.graph_adapter = gp
 		self.sim.add_basic_simulation_objects()
 
 		if add_axes:
 		>>> self.sim.add_sphere(0.5, (1,10,1), density=1)
 
 		"""
-		raise NotImplementedError()
+		pass
 
 	def set_key_2_action_mapping(self):
 		"""Creates an Action map, assigns it to :attr:`key_press_functions`
 
 	def on_action_selection(self, key):
 		"""Method called after an actions is selected by pressing a key."""
-		if self.PRINT_KEY_INFO:
-			print(key)
+		logger.info("key: %s" % key)
+
 		try:
 			if self.key_press_functions.has_key(key):
 				if self.key_press_functions.is_repeat(key):
 				else:
 					self.key_press_functions.call(key)
 			else:
-				if self.PRINT_KEY_INFO:
-					print('unregistered key: %s' % key)
-		except Exception as ex:
-			print(ex)
+				logger.info("unregistered key: %s" % key)
+		except Exception:
+			logger.exception("")
 
 	#==========================================================================
 	# other
 	def call(self, key):
 		try:
 			self._map[key][0]()
-		except Exception as ex:
-			print(ex)
+		except Exception:
+			logger.exception("")
 
 	def is_repeat(self, key):
 		return self._map.get(key)[1]

File ars/constants.py

View file
  • Ignore whitespace
+from math import pi
+
 from .utils.mathematical import mult_by_scalar3
 
 #==============================================================================
 # GEOMETRY
 #==============================================================================
 
-X_AXIS = (1,0,0)
-Y_AXIS = (0,1,0)
-Z_AXIS = (0,0,1)
+X_AXIS = (1.0, 0.0, 0.0)
+X_AXIS_NEG = (-1.0, 0.0, 0.0)
+Y_AXIS = (0.0, 1.0, 0.0)
+Y_AXIS_NEG = (0.0, -1.0, 0.0)
+Z_AXIS = (0.0, 0.0, 1.0)
+Z_AXIS_NEG = (0.0, 0.0, -1.0)
 
-RIGHTWARDS_AXIS = X_AXIS
-UPWARDS_AXIS = Y_AXIS
-OUTWARDS_AXIS = Z_AXIS
-
+RIGHT_AXIS = X_AXIS
+LEFT_AXIS = X_AXIS_NEG
+UP_AXIS = Y_AXIS
+DOWN_AXIS = Y_AXIS_NEG
+OUT_AXIS = Z_AXIS  # out of the screen
+IN_AXIS = Z_AXIS_NEG  # into the screen
 
 #==============================================================================
 # MATH & ALGEBRA
 
 EYE_3X3 = ((1,0,0),(0,1,0),(0,0,1))
 
+#==============================================================================
+# PHYSICS
+#==============================================================================
+
+# "typical" constant of gravity acceleration
+G_NORM = 9.81
+G_VECTOR = mult_by_scalar3(DOWN_AXIS, G_NORM)
 
 #==============================================================================
 # COLORS

File ars/exceptions.py

View file
  • Ignore whitespace
 	"""
 
 	def __init__(self, joint, msg=None):
-		PhysicsEngineException.__init__(self, msg)
+		super(JointError, self).__init__(msg)
 		self.joint = joint
 
 
 
 	"""
 
-	def __init__(self, type, msg=None):
-		PhysicsEngineException.__init__(self, msg)
-		self.type = type
+	def __init__(self, type_, msg=None):
+		super(PhysicsObjectCreationError, self).__init__(msg)
+		self.type = type_

File ars/graphics/__init__.py

View file
  • Ignore whitespace
-from abc import ABCMeta, abstractmethod
-
-TIMER_PERIOD = 50  # milliseconds
-TIMER_EVENT = 'TimerEvent'
-KEY_PRESS_EVENT = 'KeyPressEvent'
-
-
-class Axes:
-
-	__metaclass__ = ABCMeta
-
-	@abstractmethod
-	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, rot):
-		self._position = center
-		self._rotation = rot
-		self._color = None
-		self._actor = None
-
-	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
-
-	@abstractmethod
-	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, 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
-
-	@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
-
-	@abstractmethod
-	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, 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, *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 add_object(self, object_):
-		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):
-		raise NotImplementedError()
-
-	@abstractmethod
-	def restart_window(self):
-		raise NotImplementedError()
-
-	@abstractmethod
-	def finalize_window(self):
-		"""Finalize window and remove/clear associated resources."""
-		raise NotImplementedError()
-
-	@abstractmethod
-	def _timer_callback(self, obj, event):
-		raise NotImplementedError()
-
-	@abstractmethod
-	def _key_press_callback(self, obj, event):
-		raise NotImplementedError()
-
-	@abstractmethod
-	def reset(self):
-		raise NotImplementedError()
-
-	@staticmethod
-	@abstractmethod
-	def _update_pose(obj, pos, rot):
-		raise NotImplementedError()
-
-
-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

  • Ignore whitespace
-import vtk
-
-from .. import exceptions as exc
-from .. import graphics as gp
-from ..utils import geometry as gemut
-
-
-class VtkAdapter(gp.Adapter):
-
-	"""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.renderer = vtk.vtkRenderer()
-		self.render_window = None
-		self.interactor = None
-
-		self._title = title
-		self._size = size
-		self._zoom = zoom
-		self._cam_position = cam_position
-		self._background_color = background_color
-
-	def add_object(self, object_):
-		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 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.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.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
-		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_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 self.on_idle_parent_callback is not None:
-			self.on_idle_parent_callback()
-		iren = obj
-		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.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, 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(trans.get_long_tuple())
-
-		return vtk_matrix
-
-
-class VtkBody:
-
-	adapter = VtkAdapter
-
-	def __init__(self):
-		self._actor = None
-
-	def get_color(self):
-		"""
-		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):
-		"""
-		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, 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, 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_pose(boxActor, pos, rot)
-		boxActor.SetMapper(boxMapper)
-
-		self._actor = boxActor
-
-
-class Cone(VtkBody, gp.Cone):
-
-	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)
-		# TODO: cone.SetDirection(*direction)
-		# The vector does not have to be normalized
-
-		coneMapper = vtk.vtkPolyDataMapper()
-		coneMapper.SetInputConnection(cone.GetOutputPort())
-		coneActor = vtk.vtkActor()
-
-		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, 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(phi_resolution)
-		sphere.SetThetaResolution(theta_resolution)
-
-		sphereMapper = vtk.vtkPolyDataMapper()
-		sphereMapper.SetInputConnection(sphere.GetOutputPort())
-		sphereActor = vtk.vtkActor()
-
-		VtkAdapter._update_pose(sphereActor, center, rot)
-		sphereActor.SetMapper(sphereMapper)
-
-		self._actor = sphereActor
-
-
-class Cylinder(VtkBody, gp.Cylinder):
-
-	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)
-
-		# 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()
-		transFilter = vtk.vtkTransformPolyDataFilter()
-		transFilter.SetInputConnection(cyl.GetOutputPort())
-		transFilter.SetTransform(userTransform)
-
-		cylMapper = vtk.vtkPolyDataMapper()
-		cylMapper.SetInputConnection(transFilter.GetOutputPort())
-		cylActor = vtk.vtkActor()
-
-		VtkAdapter._update_pose(cylActor, center, rot)
-		cylActor.SetMapper(cylMapper)
-
-		self._actor = cylActor
-
-
-class Capsule(VtkBody, gp.Capsule):
-
-	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)
-		sphere1.SetPhiResolution(resolution)
-		sphere1.SetThetaResolution(resolution)
-		sphereMapper1 = vtk.vtkPolyDataMapper()
-		sphereMapper1.SetInputConnection(sphere1.GetOutputPort())
-		sphereActor1 = vtk.vtkActor()
-		sphereActor1.SetMapper(sphereMapper1)
-		sphereActor1.SetPosition(0, 0, -length / 2.0)
-
-		sphere2 = vtk.vtkSphereSource()
-		sphere2.SetRadius(radius)
-		sphere2.SetPhiResolution(resolution)
-		sphere2.SetThetaResolution(resolution)
-		sphereMapper2 = vtk.vtkPolyDataMapper()
-		sphereMapper2.SetInputConnection(sphere2.GetOutputPort())
-		sphereActor2 = vtk.vtkActor()
-		sphereActor2.SetMapper(sphereMapper2)
-		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()
-		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_pose(assembly, center, rot)
-		self._actor = assembly
-
-
-class Trimesh(VtkBody, gp.Trimesh):
-
-	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/graphics/base.py

View file
  • Ignore whitespace
+from abc import ABCMeta, abstractmethod
+
+
+class Entity(object):
+
+	"""Renderable and movable object.
+
+	It has position and orientation. The underlying object is :attr:`actor`,
+	which connects to the real entity handled by the graphics library in use.
+
+	"""
+
+	__metaclass__ = ABCMeta
+
+	adapter = None
+
+	@abstractmethod
+	def __init__(self, pos, rot):
+		self._position = pos
+		self._rotation = rot
+		self._actor = None
+
+	def set_pose(self, pos, rot):
+		self._position = pos
+		self._rotation = rot
+		self.adapter._update_pose(self._actor, pos, rot)
+
+	@property
+	def actor(self):
+		return self._actor
+
+
+class Axes(Entity):
+
+	__metaclass__ = ABCMeta
+
+	@abstractmethod
+	def __init__(self, pos=(0, 0, 0), rot=None, cylinder_radius=0.05):
+		super(Axes, self).__init__(pos, rot)
+
+
+class Body(Entity):
+
+	"""Entity representing a defined body with a given color."""
+
+	__metaclass__ = ABCMeta
+
+	@abstractmethod
+	def __init__(self, center, rot):  # TODO: rename 'center' to 'pos'
+		super(Body, self).__init__(center, rot)
+		self._color = None
+
+	@abstractmethod
+	def get_color(self):
+		return self._color
+
+	@abstractmethod
+	def set_color(self, color):
+		self._color = color
+
+
+class Box(Body):
+
+	__metaclass__ = ABCMeta
+
+	@abstractmethod
+	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, 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
+
+	@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
+
+	@abstractmethod
+	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, 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 Engine(object):
+
+	"""
+	Abstract class. Not coupled (at all) with VTK or any other graphics library
+
+	"""
+
+	__metaclass__ = ABCMeta
+
+	@abstractmethod
+	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 add_object(self, obj):
+		"""Add ``obj`` to the visualization controlled by this adapter.
+
+		:param obj:
+		:type obj: :class:`Body`
+
+		"""
+		pass
+
+	@abstractmethod
+	def remove_object(self, obj):
+		"""Remove ``obj`` from the visualization controlled by this adapter.
+
+		:param obj:
+		:type obj: :class:`Body`
+
+		"""
+		pass
+
+	def add_objects_list(self, obj_list):
+		for obj in obj_list:
+			self.add_object(obj)
+
+	@abstractmethod
+	def start_window(
+            self, on_idle_callback, on_reset_callback, on_key_press_callback):
+		pass
+
+	@abstractmethod
+	def restart_window(self):
+		pass
+
+	@abstractmethod
+	def finalize_window(self):
+		"""Finalize window and remove/clear associated resources."""
+		pass
+
+	@abstractmethod
+	def _timer_callback(self, obj, event):
+		pass
+
+	@abstractmethod
+	def _key_press_callback(self, obj, event):
+		pass
+
+	@abstractmethod
+	def reset(self):
+		pass
+
+	@classmethod
+	def _update_pose(cls, obj, pos, rot):
+		# Raising an exception efectively makes this definition be that of
+		# an abstract method (i.e. calling it directly raises an exception),
+		# except that it not requires the subclass to implement it if it is
+		# not used. We would like to use @classmethod AND @abstractmethod,
+		# but until Python 3.3 that doesn't work correctly.
+		# http://docs.python.org/3/library/abc.html
+		raise NotImplementedError()
+
+
+class ScreenshotRecorder(object):
+
+	__metaclass__ = ABCMeta
+
+	file_extension = None  # e.g. 'png'
+
+	def __init__(self, base_filename):
+		self.base_filename = base_filename
+		self.last_write_time = None
+		self.period = None
+
+	@abstractmethod
+	def write(self, index, time):
+		"""Write render-window's currently displayed image to a file.
+
+		The image format (thus the file extension too) to use must be defined
+		by the implementation.
+
+		Image's filename is determined by :meth:`calc_filename`.
+
+		:param index: image's index to use for filename calculation
+		:type index: int
+		:param time:
+		:type time:
+
+		"""
+		pass
+
+	def calc_filename(self, index=1):
+		"""Calculate a filename using ``index`` for a new image.
+
+		:param index: image's index to use for filename calculation
+		:type index: int
+		:return: image's filename
+		:rtype: str
+
+		"""
+		return '%s%d.%s' % (self.base_filename, index, self.file_extension)

File ars/graphics/vtk_adapter.py

View file
  • Ignore whitespace
+import vtk
+
+from .. import exceptions as exc
+from ..utils import geometry as gemut
+from . import base
+
+
+TIMER_PERIOD = 50  # milliseconds
+TIMER_EVENT = 'TimerEvent'
+KEY_PRESS_EVENT = 'KeyPressEvent'
+
+
+class Engine(base.Engine):
+
+	"""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(Engine, self).__init__()
+		self.renderer = vtk.vtkRenderer()
+		self.render_window = None
+		self.interactor = None
+
+		self._title = title
+		self._size = size
+		self._zoom = zoom
+		self._cam_position = cam_position
+		self._background_color = background_color
+
+	def add_object(self, obj):
+		self.renderer.AddActor(obj.actor)
+
+	def remove_object(self, obj):
+		self.renderer.RemoveActor(obj.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 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.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.renderer.SetActiveCamera(camera)
+
+		self.render_window.Render()
+
+		# add observers to the RenderWindowInteractor
+		self.interactor.AddObserver(TIMER_EVENT, self._timer_callback)
+		#noinspection PyUnusedLocal
+		timerId = self.interactor.CreateRepeatingTimer(TIMER_PERIOD)
+		self.interactor.AddObserver(
+			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
+		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
+
+	@classmethod
+	def _update_pose(cls, obj, pos, rot):
+		trans = gemut.Transform(pos, rot)
+		vtk_tm = cls._create_transform_matrix(trans)
+		cls._set_object_transform_matrix(obj, vtk_tm)
+
+	def _timer_callback(self, obj, event):
+		self.timer_count += 1
+		if self.on_idle_parent_callback is not None:
+			self.on_idle_parent_callback()
+		iren = obj
+		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.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, 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(trans.get_long_tuple())
+
+		return vtk_matrix
+
+
+class Entity(object):
+
+	adapter = Engine
+
+	def __init__(self, *args, **kwargs):
+		self._actor = None
+
+
+class Body(Entity):
+
+	def get_color(self):
+		"""
+		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):
+		"""
+		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(Entity, base.Axes):
+
+	def __init__(self, pos=(0, 0, 0), rot=None, cylinder_radius=0.05):
+		base.Axes.__init__(self, pos, rot, cylinder_radius)
+
+		# 2 different methods may be used here. See
+		# http://stackoverflow.com/questions/7810632/
+
+		self._actor = vtk.vtkAxesActor()
+		self._actor.AxisLabelsOn()
+		self._actor.SetShaftTypeToCylinder()
+		self._actor.SetCylinderRadius(cylinder_radius)
+		self.set_pose(pos, rot)
+
+
+class Box(Body, base.Box):
+
+	def __init__(self, size, pos, rot=None):
+		base.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())
+		self._actor = vtk.vtkActor()
+
+		self.set_pose(pos, rot)
+		self._actor.SetMapper(boxMapper)  # TODO: does the order matter?
+
+
+class Cone(Body, base.Cone):
+
+	def __init__(self, height, radius, center, rot=None, resolution=20):
+		base.Cone.__init__(self, height, radius, center, rot, resolution)
+
+		cone = vtk.vtkConeSource()
+		cone.SetHeight(height)
+		cone.SetRadius(radius)
+		cone.SetResolution(resolution)
+		# TODO: cone.SetDirection(*direction)
+		# The vector does not have to be normalized
+
+		coneMapper = vtk.vtkPolyDataMapper()
+		coneMapper.SetInputConnection(cone.GetOutputPort())
+		self._actor = vtk.vtkActor()
+
+		self.set_pose(center, rot)
+		self._actor.SetMapper(coneMapper)  # TODO: does the order matter?
+
+
+class Sphere(Body, base.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, rot=None, phi_resolution=20,
+            theta_resolution=20):
+		base.Sphere.__init__(
+            self, radius, center, rot, phi_resolution, theta_resolution)
+
+		sphere = vtk.vtkSphereSource()
+		sphere.SetRadius(radius)
+		sphere.SetPhiResolution(phi_resolution)
+		sphere.SetThetaResolution(theta_resolution)
+
+		sphereMapper = vtk.vtkPolyDataMapper()
+		sphereMapper.SetInputConnection(sphere.GetOutputPort())
+		self._actor = vtk.vtkActor()
+
+		self.set_pose(center, rot)
+		self._actor.SetMapper(sphereMapper)  # TODO: does the order matter?
+
+
+class Cylinder(Body, base.Cylinder):
+
+	def __init__(self, length, radius, center, rot=None, resolution=20):
+		base.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)
+
+		# 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()
+		transFilter = vtk.vtkTransformPolyDataFilter()
+		transFilter.SetInputConnection(cyl.GetOutputPort())
+		transFilter.SetTransform(userTransform)
+
+		cylMapper = vtk.vtkPolyDataMapper()
+		cylMapper.SetInputConnection(transFilter.GetOutputPort())
+		self._actor = vtk.vtkActor()
+
+		self.set_pose(center, rot)
+		self._actor.SetMapper(cylMapper)  # TODO: does the order matter?
+
+
+class Capsule(Body, base.Capsule):
+
+	def __init__(self, length, radius, center, rot=None, resolution=20):
+		base.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)
+		sphere1.SetPhiResolution(resolution)
+		sphere1.SetThetaResolution(resolution)
+		sphereMapper1 = vtk.vtkPolyDataMapper()
+		sphereMapper1.SetInputConnection(sphere1.GetOutputPort())
+		sphereActor1 = vtk.vtkActor()
+		sphereActor1.SetMapper(sphereMapper1)
+		sphereActor1.SetPosition(0, 0, -length / 2.0)
+
+		sphere2 = vtk.vtkSphereSource()
+		sphere2.SetRadius(radius)
+		sphere2.SetPhiResolution(resolution)
+		sphere2.SetThetaResolution(resolution)
+		sphereMapper2 = vtk.vtkPolyDataMapper()
+		sphereMapper2.SetInputConnection(sphere2.GetOutputPort())
+		sphereActor2 = vtk.vtkActor()
+		sphereActor2.SetMapper(sphereMapper2)
+		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()
+		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)
+		self._actor = assembly
+
+		self.set_pose(center, rot)
+
+
+class Trimesh(Body, base.Trimesh):
+
+	def __init__(self, vertices, faces, pos, rot=None):
+		base.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
+		self._actor = vtk.vtkActor()
+		self.set_pose(pos, rot)
+		self._actor.SetMapper(mapper)  # TODO: does the order matter?
+
+
+class ScreenshotRecorder(base.ScreenshotRecorder):
+
+	"""
+	Based on an official example script, very simple:
+	http://www.vtk.org/Wiki/VTK/Examples/Python/Screenshot
+
+	"""
+
+	file_extension = 'png'
+
+	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):
+		"""
+
+		.. note::
+		   Image files format is PNG, and extension is ``.png``.
+
+		"""
+		# 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(self.calc_filename(index))
+		writer.SetInputConnection(image_getter.GetOutputPort())
+		writer.Write()
+
+		if time is not None:
+			self.last_write_time = time

File ars/model/collision/adapters.py

  • Ignore whitespace
-from abc import ABCMeta
-
-import ode
-
-from . import base
-
-
-class OdeEngine(base.Engine):
-	"""Adapter to the ODE collision engine"""
-
-#	def __init__(self):
-#		pass
-
-	#==========================================================================
-	# Functions and methods not overriding base class functions and methods
-	#==========================================================================
-
-	@staticmethod
-	def near_callback(args, geom1, geom2):
-		"""Callback function for the collide() method (in ODE). This function
-		checks if the given geoms do collide and creates contact joints (c_joint)
-		if they do, except if they are connected."""
-
-		# Contact joint parameters:
-		# 	-bounce: default=0.2
-		# 	-mu: default=500.
-		# 		very slippery 0-5, normal 50-500, very sticky 5000
-		c_joint_bounce = 0.2
-		c_joint_mu = 500
-
-		world = args.world._inner_object
-		contact_group = args.contact_group
-		ray_geom = None
-		other_geom = None
-
-		if args.ignore_connected and OdeEngine.are_geoms_connected(geom1,
-			geom2):
-			return
-
-		#======================================================================
-		# Ray's special case
-		#======================================================================
-		if OdeEngine.is_ray(geom1):
-			if OdeEngine.is_ray(geom2):
-				print('Weird, ODE says two rays may collide. '
-					  'That case is not handled.')
-				return
-			else:
-				ray_geom = geom1
-				other_geom = geom2
-
-		elif OdeEngine.is_ray(geom2):
-			ray_geom = geom2
-			other_geom = geom1
-
-		#======================================================================
-		# create contact joints
-		#======================================================================
-		# check if the objects collide
-		contacts = ode.collide(geom1, geom2)
-		for c in contacts:
-
-			if ray_geom is not None:
-				OdeEngine.handle_ray_collision(ray_geom, other_geom, c)
-			else:  # we create a ContactJoint only if both geoms are not rays
-				# set contact parameters
-				c.setBounce(c_joint_bounce)
-				c.setMu(c_joint_mu)
-				# create contact joints
-				j = ode.ContactJoint(world, contact_group, c)
-				j.attach(geom1.getBody(), geom2.getBody())
-
-	@staticmethod
-	def are_geoms_connected(geom1, geom2):
-		"""Are `geom1` and `geom2` connected (through the bodies they are
-		attached to)?
-		"""
-		return ode.areConnected(geom1.getBody(), geom2.getBody())
-
-	@staticmethod
-	def is_ray(geom):
-		"""Is `geom` a ray?"""
-		return isinstance(geom, ode.GeomRay)
-
-	@staticmethod
-	def handle_ray_collision(ray, other_geom, contact):
-		# pos: intersection position
-		# depth: distance
-		(pos, normal, depth, geom1, geom2) = contact.getContactGeomParams()
-		ray_contact = base.RayContactData(
-			ray, other_geom, pos, normal, depth)
-		try:
-			ray.outer_object.set_last_contact(ray_contact)
-		except AttributeError:
-			print("`ray` has no attribute `outer_object` ")
-		except Exception:
-			print("Ray's encapsulating object's last_contact attribute could not be set")
-
-
-class Space(base.Space):
-	def __init__(self):
-		# ode.SimpleSpace() is the same as ode.Space(space_type=0)
-		self._inner_object = ode.SimpleSpace()
-
-	def collide(self, args, callback=None):
-		if callback is None:
-			self._inner_object.collide(args, OdeEngine.near_callback)
-		else:
-			self._inner_object.collide(args, callback)
-
-
-class OdeGeom(base.Geom):
-
-	def attach_body(self, body):
-		super(OdeGeom, self).attach_body(body)
-		self._inner_object.setBody(body.inner_object)
-
-	#==========================================================================
-	# Getters and setters
-	#==========================================================================
-
-	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)
-
-
-class Ray(OdeGeom, base.Ray):
-
-	def __init__(self, space, length):
-		OdeGeom.__init__(self)
-		base.Ray.__init__(self, space, length)
-		self._inner_object = ode.GeomRay(space.inner_object, length)
-		self._inner_object.outer_object = self  # the encapsulating object
-
-	def get_length(self):
-		return self._inner_object.getLength()
-
-	def set_length(self, length):
-		self._inner_object.setLength(length)
-
-
-class Trimesh(OdeGeom, base.Trimesh):
-
-	def __init__(self, space, vertices, faces, center):
-		OdeGeom.__init__(self)
-		base.Trimesh.__init__(self, space, vertices, faces, center)
-
-		tm_data = ode.TriMeshData()
-		tm_data.build(vertices, faces)
-		self._inner_object = ode.GeomTriMesh(tm_data, space.inner_object)
-		self._inner_object.setPosition(center)
-
-
-class OdeBasicShape(OdeGeom):
-	__metaclass__ = ABCMeta
-
-
-class Box(OdeBasicShape, base.Box):
-	"""Box shape, aligned along the X, Y and Z axii by default"""
-
-	def __init__(self, space, size):
-		OdeBasicShape.__init__(self)
-		base.Box.__init__(self, space, size)
-
-		self._inner_object = ode.GeomBox(space.inner_object, lengths=size)
-
-
-class Sphere(OdeBasicShape, base.Sphere):
-	"""Spherical shape"""
-
-	def __init__(self, space, radius):
-		OdeBasicShape.__init__(self)
-		base.Sphere.__init__(self, space, radius)
-
-		self._inner_object = ode.GeomSphere(space.inner_object, radius)
-
-
-class Capsule(OdeBasicShape, base.Capsule):
-	"""Capsule shape, aligned along the Z-axis by default"""
-
-	def __init__(self, space, length, radius):
-		# GeomCCylinder: Capped Cylinder
-		OdeBasicShape.__init__(self)
-		base.Capsule.__init__(self, space, length, radius)
-
-		self._inner_object = ode.GeomCCylinder(
-			space.inner_object, radius, length)
-
-
-class Cylinder(OdeBasicShape, base.Cylinder):
-	"""Cylinder shape, aligned along the Z-axis by default"""
-