Skip to content

Commit 9eff3b1

Browse files
Merge pull request #712 from prabhuramachandran/disable-update
ENH: Feature to disable automatic updates.
2 parents 1a94288 + 6e4d242 commit 9eff3b1

3 files changed

Lines changed: 112 additions & 39 deletions

File tree

tvtk/api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
# The external API for tvtk.
55

6-
76
# The version of TVTK that is installed
87
from tvtk.version import version, version as __version__
98

@@ -15,3 +14,5 @@
1514

1615
# Some miscellaneous functionality.
1716
from tvtk.misc import write_data
17+
18+
from tvtk.tvtk_base import global_disable_update

tvtk/tests/test_tvtk_base.py

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
"""
66
# Author: Prabhu Ramachandran
7-
# Copyright (c) 2004-2015, Enthought, Inc.
7+
# Copyright (c) 2004-2018, Enthought, Inc.
88
# License: BSD Style.
99

1010
import unittest
@@ -23,33 +23,44 @@
2323
# testing.
2424
class Prop(tvtk_base.TVTKBase):
2525
def __init__(self, obj=None, update=1, **traits):
26-
tvtk_base.TVTKBase.__init__(self, vtk.vtkProperty, obj, update, **traits)
26+
tvtk_base.TVTKBase.__init__(
27+
self, vtk.vtkProperty, obj, update, **traits
28+
)
2729

2830
edge_visibility = tvtk_base.false_bool_trait
31+
2932
def _edge_visibility_changed(self, old_val, new_val):
3033
self._do_change(self._vtk_obj.SetEdgeVisibility, self.edge_visibility_)
3134

32-
representation = traits.Trait('surface',
33-
tvtk_base.TraitRevPrefixMap({'points': 0, 'wireframe': 1, 'surface': 2}))
35+
representation = traits.Trait(
36+
'surface',
37+
tvtk_base.TraitRevPrefixMap(
38+
{'points': 0, 'wireframe': 1, 'surface': 2})
39+
)
40+
3441
def _representation_changed(self, old_val, new_val):
3542
self._do_change(self._vtk_obj.SetRepresentation, self.representation_)
3643

3744
opacity = traits.Trait(1.0, traits.Range(0.0, 1.0))
45+
3846
def _opacity_changed(self, old_val, new_val):
3947
self._do_change(self._vtk_obj.SetOpacity,
4048
self.opacity)
4149

4250
specular_color = tvtk_base.vtk_color_trait((1.0, 1.0, 1.0))
51+
4352
def _specular_color_changed(self, old_val, new_val):
4453
self._do_change(self._vtk_obj.SetSpecularColor,
4554
self.specular_color, 1)
4655

4756
diffuse_color = tvtk_base.vtk_color_trait((1.0, 1.0, 1.0))
57+
4858
def _diffuse_color_changed(self, old_val, new_val):
4959
self._do_change(self._vtk_obj.SetDiffuseColor,
5060
self.diffuse_color, 1)
5161

5262
color = tvtk_base.vtk_color_trait((1.0, 1.0, 1.0))
63+
5364
def _color_changed(self, old_val, new_val):
5465
self._do_change(self._vtk_obj.SetColor,
5566
self.color)
@@ -62,7 +73,6 @@ def _color_changed(self, old_val, new_val):
6273
('representation', 'GetRepresentation'))
6374

6475

65-
6676
class TestTVTKBase(unittest.TestCase):
6777
def test_tvtk_name(self):
6878
"""Test VTK to TVTK class name conversion."""
@@ -77,9 +87,9 @@ def test_tvtk_name(self):
7787
num = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
7888
'Six', 'Seven', 'Eight', 'Nine']
7989
for i in range(10):
80-
vn = 'vtk%dA'%i
90+
vn = 'vtk%dA' % i
8191
tn = get_tvtk_name(vn)
82-
self.assertEqual(tn, '%sA'%num[i])
92+
self.assertEqual(tn, '%sA' % num[i])
8393

8494
def test_camel2enthought(self):
8595
"""Test CamelCase to Enthought style name conversion."""
@@ -103,16 +113,31 @@ def test_do_change(self):
103113
p.edge_visibility = not p.edge_visibility
104114
p.representation = 'p'
105115
p.opacity = 0.5
106-
p.color = (0,1,0)
107-
p.diffuse_color = (1,1,1)
108-
p.specular_color = (1,1,0)
116+
p.color = (0, 1, 0)
117+
p.diffuse_color = (1, 1, 1)
118+
p.specular_color = (1, 1, 0)
109119
for t, g in p._updateable_traits_:
110120
val = getattr(p._vtk_obj, g)()
111121
if t == 'representation':
112122
self.assertEqual(val, getattr(p, t + '_'))
113123
else:
114124
self.assertEqual(val, getattr(p, t))
115125

126+
def test_wrap_call_is_graceful_on_failure(self):
127+
# Given
128+
p = Prop()
129+
130+
# When
131+
try:
132+
# Make a mistake
133+
p._wrap_call(p._vtk_obj.SetLineWidth, 'a')
134+
except TypeError:
135+
pass
136+
137+
# Then
138+
# The _in_set should be reset to zero.
139+
self.assertEqual(p._in_set, 0)
140+
116141
def test_auto_update(self):
117142
"""Test trait updation when the VTK object changes."""
118143
p = Prop()
@@ -176,9 +201,9 @@ def test_pickle(self):
176201
p.edge_visibility = 1
177202
p.representation = 'p'
178203
p.opacity = 0.5
179-
p.color = (0,1,0)
180-
p.diffuse_color = (0,1,1)
181-
p.specular_color = (1,1,0)
204+
p.color = (0, 1, 0)
205+
p.diffuse_color = (0, 1, 1)
206+
p.specular_color = (1, 1, 0)
182207

183208
s = pickle.dumps(p)
184209
del p
@@ -218,9 +243,9 @@ def test_rev_prefix_map(self):
218243
p.representation = 'points'
219244
p.representation = 2
220245
self.assertEqual(p.representation, 'surface')
221-
self.assertRaises(traits.TraitError, setattr , p,
246+
self.assertRaises(traits.TraitError, setattr, p,
222247
'representation', 'points1')
223-
self.assertRaises(traits.TraitError, setattr , p,
248+
self.assertRaises(traits.TraitError, setattr, p,
224249
'representation', 'POINTS')
225250

226251
def test_deref_vtk(self):
@@ -242,6 +267,25 @@ def test_obj_del(self):
242267
del p
243268
self.assertEqual(ref(), None)
244269

270+
def test_global_disable_update(self):
271+
# Given
272+
p = Prop()
273+
vp = tvtk_base.deref_vtk(p)
274+
275+
# When
276+
with tvtk_base.global_disable_update():
277+
vp.SetOpacity(0.5)
278+
vp.Modified()
279+
280+
# Then
281+
self.assertEqual(p.opacity, 1.0)
282+
283+
# When
284+
vp.SetOpacity(0.4)
285+
286+
# Then
287+
self.assertEqual(p.opacity, 0.4)
288+
245289
def test_strict_traits(self):
246290
"""Test if TVTK objects use strict traits."""
247291
p = Prop()
@@ -251,7 +295,7 @@ def test_strict_traits(self):
251295

252296
def test_init_traits(self):
253297
"""Test if the objects traits can be set in __init__."""
254-
p = Prop(opacity=0.1, color=(1,0,0), representation='p')
298+
p = Prop(opacity=0.1, color=(1, 0, 0), representation='p')
255299
self.assertEqual(p.opacity, 0.1)
256300
self.assertEqual(p.color, (1.0, 0.0, 0.0))
257301
self.assertEqual(p.representation, 'points')
@@ -272,7 +316,7 @@ def test_zz_object_cache(self):
272316
self.assertEqual(p, tvtk_base._object_cache.get(addr))
273317

274318
del p
275-
gc.collect() # Force collection.
319+
gc.collect() # Force collection.
276320
self.assertEqual(l1, len(tvtk_base._object_cache))
277321
self.assertEqual(addr in tvtk_base._object_cache, False)
278322

tvtk/tvtk_base.py

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
"""
44
# Author: Prabhu Ramachandran <prabhu_r@users.sf.net>
5-
# Copyright (c) 2004-2015, Enthought, Inc.
5+
# Copyright (c) 2004-2018, Enthought, Inc.
66
# License: BSD Style.
77

88
from __future__ import print_function
@@ -11,6 +11,7 @@
1111
import weakref
1212
import os
1313
import logging
14+
from contextlib import contextmanager
1415

1516
import vtk
1617

@@ -25,10 +26,12 @@ def BooleanEditor(*args, **kw):
2526
from traitsui.api import BooleanEditor as Editor
2627
return Editor(*args, **kw)
2728

29+
2830
def RGBColorEditor(*args, **kw):
2931
from traitsui.api import RGBColorEditor as Editor
3032
return Editor(*args, **kw)
3133

34+
3235
def FileEditor(*args, **kw):
3336
from traitsui.api import FileEditor as Editor
3437
return Editor(*args, **kw)
@@ -42,6 +45,7 @@ class TVTKObjectCache(weakref.WeakValueDictionary):
4245
def __init__(self, *args, **kw):
4346
self._observer_data = {}
4447
weakref.WeakValueDictionary.__init__(self, *args, **kw)
48+
4549
def remove(wr, selfref=weakref.ref(self)):
4650
self = selfref()
4751
if self is not None:
@@ -139,7 +143,7 @@ def get_tvtk_object_from_cache(vtk_obj):
139143
{'true': 1, 't': 1, 'yes': 1,
140144
'y': 1, 'on': 1, 1: 1, 'false': 0,
141145
'f': 0, 'no': 0, 'n': 0,
142-
'off': 0, 0: 0, -1:0},
146+
'off': 0, 0: 0, -1: 0},
143147
editor=BooleanEditor)
144148

145149
false_bool_trait = traits.Trait('false', true_bool_trait)
@@ -174,21 +178,21 @@ def validate(self, object, name, value):
174178
try:
175179
if value in self._rmap:
176180
value = self._rmap[value]
177-
if not value in self._map:
181+
if value not in self._map:
178182
match = None
179-
n = len( value )
183+
n = len(value)
180184
for key in self.map.keys():
181185
if value == key[:n]:
182186
if match is not None:
183-
match = None
184-
break
187+
match = None
188+
break
185189
match = key
186190
if match is None:
187-
self.error( object, name, value )
188-
self._map[ value ] = match
189-
return self._map[ value ]
191+
self.error(object, name, value)
192+
self._map[value] = match
193+
return self._map[value]
190194
except:
191-
self.error( object, name, value )
195+
self.error(object, name, value)
192196

193197
def info(self):
194198
keys = [repr(x) for x in self._rmap.keys()]
@@ -235,6 +239,28 @@ def vtk_color_trait(default, **metadata):
235239
vtk_property_delegate = traits.Delegate('property', modify=True)
236240

237241

242+
_DISABLE_UPDATE = False
243+
244+
245+
@contextmanager
246+
def global_disable_update():
247+
'''Disable updating any traits automatically due to changes in VTK.
248+
249+
Specifically, do not auto-update *any* objects due to the firing of a
250+
ModifiedEvent from within VTK. This basically can call `update_traits` on
251+
the object which can in turn fire off other callbacks.
252+
253+
Use this sparingly when you REALLY need to disable updates.
254+
255+
'''
256+
global _DISABLE_UPDATE
257+
try:
258+
_DISABLE_UPDATE = True
259+
yield
260+
finally:
261+
_DISABLE_UPDATE = False
262+
263+
238264
######################################################################
239265
# Utility functions.
240266
######################################################################
@@ -282,7 +308,8 @@ class TVTKBase(traits.HasStrictTraits):
282308
# Stores the names of the traits that need to be updated.
283309
_updateable_traits_ = traits.Tuple
284310

285-
# List of trait names that are to be included in the full traits view of this object.
311+
# List of trait names that are to be included in the full traits view of
312+
# this object.
286313
_full_traitnames_list_ = traits.List
287314

288315
#################################################################
@@ -326,7 +353,7 @@ def __init__(self, klass, obj=None, update=True, **traits):
326353
else:
327354
self._vtk_obj = klass()
328355

329-
# print "INIT", self.__class__.__name__, repr(self._vtk_obj)
356+
# print("INIT", self.__class__.__name__)#, repr(self._vtk_obj))
330357

331358
# Call the Super class to update the traits.
332359
# Inhibit any updates at this point since we update in the end
@@ -394,8 +421,7 @@ def __str__(self):
394421
#################################################################
395422
# `HasTraits` interface.
396423
#################################################################
397-
398-
def class_trait_view_elements ( cls ):
424+
def class_trait_view_elements(cls):
399425
""" Returns the ViewElements object associated with the class.
400426
401427
The returned object can be used to access all the view elements
@@ -429,12 +455,12 @@ def class_trait_view_elements ( cls ):
429455
)
430456
for name in names:
431457
if name in result:
432-
view_elements.content[ name ] = result[name]
458+
view_elements.content[name] = result[name]
433459
except Exception:
434460
pass
435461
return view_elements
436462

437-
class_trait_view_elements = classmethod( class_trait_view_elements )
463+
class_trait_view_elements = classmethod(class_trait_view_elements)
438464

439465
#################################################################
440466
# `TVTKBase` interface.
@@ -464,7 +490,7 @@ def update_traits(self, obj=None, event=None):
464490
the VTK observer callback functions.
465491
466492
"""
467-
if self._in_set:
493+
if self._in_set or _DISABLE_UPDATE:
468494
return
469495
if not hasattr(self, '_updateable_traits_'):
470496
return
@@ -538,11 +564,11 @@ def _do_change(self, method, val, force_update=False):
538564
method(*val)
539565
else:
540566
raise
541-
self._in_set -= 1
567+
finally:
568+
self._in_set -= 1
542569
if force_update or self._wrapped_mtime(vtk_obj) > mtime:
543570
self.update_traits()
544571

545-
546572
def _wrap_call(self, vtk_method, *args):
547573
"""This method allows us to safely call a VTK method without
548574
calling `update_traits` during the call. This method is
@@ -565,8 +591,10 @@ def _wrap_call(self, vtk_method, *args):
565591
vtk_obj = self._vtk_obj
566592
self._in_set += 1
567593
mtime = self._wrapped_mtime(vtk_obj) + 1
568-
ret = vtk_method(*args)
569-
self._in_set -= 1
594+
try:
595+
ret = vtk_method(*args)
596+
finally:
597+
self._in_set -= 1
570598
if self._wrapped_mtime(vtk_obj) > mtime:
571599
self.update_traits()
572600
return ret

0 commit comments

Comments
 (0)