ars / ars / graphics / adapters.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
import vtk

import ars.exceptions as exc
import ars.graphics as gp
import ars.utils.geometry as gemut


class VtkAdapter(gp.Adapter):

	"""Graphics adapter to the Visualization Toolkit (VTK) library"""

	def __init__(self):
		super(VtkAdapter, self).__init__()
		self.ren = vtk.vtkRenderer()
		self._title = ''
		self._size = None
		self._zoom = None
		self._cam_position = None

	def create_window(self, title, pos=None, size=(1000,600), zoom=1.0, cam_position=(10,8,10),
					background_color=(0.1,0.1,0.4)):

		self._title = title
		self._size = size
		self._zoom = zoom
		self._cam_position = cam_position

		self.ren.SetBackground(background_color)

	def add_object(self, object_):
		self.ren.AddActor(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)

		# set properties
		self.renWin.SetSize(*self._size)
		self.renWin.SetWindowName(self._title)

		camera = vtk.vtkCamera()
		camera.SetPosition(self._cam_position)
		camera.Zoom(self._zoom)
		self.ren.SetActiveCamera(camera)
		self.renWin.Render()

		self.iren.AddObserver(gp.TIMER_EVENT, self._timer_callback)
		#noinspection PyUnusedLocal
		timerId = self.iren.CreateRepeatingTimer(gp.TIMER_PERIOD)  #@UnusedVariable
		self.iren.AddObserver(gp.KEY_PRESS_EVENT, self._key_press_callback)
		self.iren.Start()

	def restart_window(self):
		# TODO: code according to start_window(), reset() and the desired behavior
		raise exc.ArsError()

	@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.renWin.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.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) # it is the circumferential number of facets
		# 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, phiResolution = 20, thetaResolution = 20):
		gp.Sphere.__init__(self, radius, center, rot, phiResolution, thetaResolution)

		sphere = vtk.vtkSphereSource()
		sphere.SetRadius(radius)
		sphere.SetPhiResolution(phiResolution) # it is the circumferential number of facets
		sphere.SetThetaResolution(thetaResolution)

		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) # it is the circumferential number of facets

		# 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)
		# 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 renWin and create image_getter every time)
		# was needed because we used image_getter.SetInput instead of SetInputConnection
		render_window = self.gAdapter.renWin
		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
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.