Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
ENH: Feature to disable automatic updates.
This can be very handy at times when the automatic updates can trigger
several changes that are not desirable.  For example on certain VTK
calls, internal ModifiedEvents may be fired which will automatically
call the update_traits method which can be wired to other events
triggering problems.  In these cases one can use the
`global_disable_update` function to temporarily disable updates.

Also fix some PEP8 errors and a few places where the state of the object
could be incorrectly set due to exceptions beinf raised.
  • Loading branch information
prabhuramachandran committed Sep 8, 2018
commit 6e4d2425d2534d40702ecda623ad0e102f67a948
3 changes: 2 additions & 1 deletion tvtk/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

# The external API for tvtk.


# The version of TVTK that is installed
from tvtk.version import version, version as __version__

Expand All @@ -15,3 +14,5 @@

# Some miscellaneous functionality.
from tvtk.misc import write_data

from tvtk.tvtk_base import global_disable_update
78 changes: 61 additions & 17 deletions tvtk/tests/test_tvtk_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

"""
# Author: Prabhu Ramachandran
# Copyright (c) 2004-2015, Enthought, Inc.
# Copyright (c) 2004-2018, Enthought, Inc.
# License: BSD Style.

import unittest
Expand All @@ -23,33 +23,44 @@
# testing.
class Prop(tvtk_base.TVTKBase):
def __init__(self, obj=None, update=1, **traits):
tvtk_base.TVTKBase.__init__(self, vtk.vtkProperty, obj, update, **traits)
tvtk_base.TVTKBase.__init__(
self, vtk.vtkProperty, obj, update, **traits
)

edge_visibility = tvtk_base.false_bool_trait

def _edge_visibility_changed(self, old_val, new_val):
self._do_change(self._vtk_obj.SetEdgeVisibility, self.edge_visibility_)

representation = traits.Trait('surface',
tvtk_base.TraitRevPrefixMap({'points': 0, 'wireframe': 1, 'surface': 2}))
representation = traits.Trait(
'surface',
tvtk_base.TraitRevPrefixMap(
{'points': 0, 'wireframe': 1, 'surface': 2})
)

def _representation_changed(self, old_val, new_val):
self._do_change(self._vtk_obj.SetRepresentation, self.representation_)

opacity = traits.Trait(1.0, traits.Range(0.0, 1.0))

def _opacity_changed(self, old_val, new_val):
self._do_change(self._vtk_obj.SetOpacity,
self.opacity)

specular_color = tvtk_base.vtk_color_trait((1.0, 1.0, 1.0))

def _specular_color_changed(self, old_val, new_val):
self._do_change(self._vtk_obj.SetSpecularColor,
self.specular_color, 1)

diffuse_color = tvtk_base.vtk_color_trait((1.0, 1.0, 1.0))

def _diffuse_color_changed(self, old_val, new_val):
self._do_change(self._vtk_obj.SetDiffuseColor,
self.diffuse_color, 1)

color = tvtk_base.vtk_color_trait((1.0, 1.0, 1.0))

def _color_changed(self, old_val, new_val):
self._do_change(self._vtk_obj.SetColor,
self.color)
Expand All @@ -62,7 +73,6 @@ def _color_changed(self, old_val, new_val):
('representation', 'GetRepresentation'))



class TestTVTKBase(unittest.TestCase):
def test_tvtk_name(self):
"""Test VTK to TVTK class name conversion."""
Expand All @@ -77,9 +87,9 @@ def test_tvtk_name(self):
num = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five',
'Six', 'Seven', 'Eight', 'Nine']
for i in range(10):
vn = 'vtk%dA'%i
vn = 'vtk%dA' % i
tn = get_tvtk_name(vn)
self.assertEqual(tn, '%sA'%num[i])
self.assertEqual(tn, '%sA' % num[i])

def test_camel2enthought(self):
"""Test CamelCase to Enthought style name conversion."""
Expand All @@ -103,16 +113,31 @@ def test_do_change(self):
p.edge_visibility = not p.edge_visibility
p.representation = 'p'
p.opacity = 0.5
p.color = (0,1,0)
p.diffuse_color = (1,1,1)
p.specular_color = (1,1,0)
p.color = (0, 1, 0)
p.diffuse_color = (1, 1, 1)
p.specular_color = (1, 1, 0)
for t, g in p._updateable_traits_:
val = getattr(p._vtk_obj, g)()
if t == 'representation':
self.assertEqual(val, getattr(p, t + '_'))
else:
self.assertEqual(val, getattr(p, t))

def test_wrap_call_is_graceful_on_failure(self):
# Given
p = Prop()

# When
try:
# Make a mistake
p._wrap_call(p._vtk_obj.SetLineWidth, 'a')
except TypeError:
pass

# Then
# The _in_set should be reset to zero.
self.assertEqual(p._in_set, 0)

def test_auto_update(self):
"""Test trait updation when the VTK object changes."""
p = Prop()
Expand Down Expand Up @@ -176,9 +201,9 @@ def test_pickle(self):
p.edge_visibility = 1
p.representation = 'p'
p.opacity = 0.5
p.color = (0,1,0)
p.diffuse_color = (0,1,1)
p.specular_color = (1,1,0)
p.color = (0, 1, 0)
p.diffuse_color = (0, 1, 1)
p.specular_color = (1, 1, 0)

s = pickle.dumps(p)
del p
Expand Down Expand Up @@ -218,9 +243,9 @@ def test_rev_prefix_map(self):
p.representation = 'points'
p.representation = 2
self.assertEqual(p.representation, 'surface')
self.assertRaises(traits.TraitError, setattr , p,
self.assertRaises(traits.TraitError, setattr, p,
'representation', 'points1')
self.assertRaises(traits.TraitError, setattr , p,
self.assertRaises(traits.TraitError, setattr, p,
'representation', 'POINTS')

def test_deref_vtk(self):
Expand All @@ -242,6 +267,25 @@ def test_obj_del(self):
del p
self.assertEqual(ref(), None)

def test_global_disable_update(self):
# Given
p = Prop()
vp = tvtk_base.deref_vtk(p)

# When
with tvtk_base.global_disable_update():
vp.SetOpacity(0.5)
vp.Modified()

# Then
self.assertEqual(p.opacity, 1.0)

# When
vp.SetOpacity(0.4)

# Then
self.assertEqual(p.opacity, 0.4)

def test_strict_traits(self):
"""Test if TVTK objects use strict traits."""
p = Prop()
Expand All @@ -251,7 +295,7 @@ def test_strict_traits(self):

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

del p
gc.collect() # Force collection.
gc.collect() # Force collection.
self.assertEqual(l1, len(tvtk_base._object_cache))
self.assertEqual(addr in tvtk_base._object_cache, False)

Expand Down
70 changes: 49 additions & 21 deletions tvtk/tvtk_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

"""
# Author: Prabhu Ramachandran <prabhu_r@users.sf.net>
# Copyright (c) 2004-2015, Enthought, Inc.
# Copyright (c) 2004-2018, Enthought, Inc.
# License: BSD Style.

from __future__ import print_function
Expand All @@ -11,6 +11,7 @@
import weakref
import os
import logging
from contextlib import contextmanager

import vtk

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


def RGBColorEditor(*args, **kw):
from traitsui.api import RGBColorEditor as Editor
return Editor(*args, **kw)


def FileEditor(*args, **kw):
from traitsui.api import FileEditor as Editor
return Editor(*args, **kw)
Expand All @@ -42,6 +45,7 @@ class TVTKObjectCache(weakref.WeakValueDictionary):
def __init__(self, *args, **kw):
self._observer_data = {}
weakref.WeakValueDictionary.__init__(self, *args, **kw)

def remove(wr, selfref=weakref.ref(self)):
self = selfref()
if self is not None:
Expand Down Expand Up @@ -139,7 +143,7 @@ def get_tvtk_object_from_cache(vtk_obj):
{'true': 1, 't': 1, 'yes': 1,
'y': 1, 'on': 1, 1: 1, 'false': 0,
'f': 0, 'no': 0, 'n': 0,
'off': 0, 0: 0, -1:0},
'off': 0, 0: 0, -1: 0},
editor=BooleanEditor)

false_bool_trait = traits.Trait('false', true_bool_trait)
Expand Down Expand Up @@ -174,21 +178,21 @@ def validate(self, object, name, value):
try:
if value in self._rmap:
value = self._rmap[value]
if not value in self._map:
if value not in self._map:
match = None
n = len( value )
n = len(value)
for key in self.map.keys():
if value == key[:n]:
if match is not None:
match = None
break
match = None
break
match = key
if match is None:
self.error( object, name, value )
self._map[ value ] = match
return self._map[ value ]
self.error(object, name, value)
self._map[value] = match
return self._map[value]
except:
self.error( object, name, value )
self.error(object, name, value)

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


_DISABLE_UPDATE = False


@contextmanager
def global_disable_update():
'''Disable updating any traits automatically due to changes in VTK.

Specifically, do not auto-update *any* objects due to the firing of a
ModifiedEvent from within VTK. This basically can call `update_traits` on
the object which can in turn fire off other callbacks.

Use this sparingly when you REALLY need to disable updates.

'''
global _DISABLE_UPDATE
try:
_DISABLE_UPDATE = True
yield
finally:
_DISABLE_UPDATE = False


######################################################################
# Utility functions.
######################################################################
Expand Down Expand Up @@ -282,7 +308,8 @@ class TVTKBase(traits.HasStrictTraits):
# Stores the names of the traits that need to be updated.
_updateable_traits_ = traits.Tuple

# List of trait names that are to be included in the full traits view of this object.
# List of trait names that are to be included in the full traits view of
# this object.
_full_traitnames_list_ = traits.List

#################################################################
Expand Down Expand Up @@ -326,7 +353,7 @@ def __init__(self, klass, obj=None, update=True, **traits):
else:
self._vtk_obj = klass()

# print "INIT", self.__class__.__name__, repr(self._vtk_obj)
# print("INIT", self.__class__.__name__)#, repr(self._vtk_obj))

# Call the Super class to update the traits.
# Inhibit any updates at this point since we update in the end
Expand Down Expand Up @@ -394,8 +421,7 @@ def __str__(self):
#################################################################
# `HasTraits` interface.
#################################################################

def class_trait_view_elements ( cls ):
def class_trait_view_elements(cls):
""" Returns the ViewElements object associated with the class.

The returned object can be used to access all the view elements
Expand Down Expand Up @@ -429,12 +455,12 @@ def class_trait_view_elements ( cls ):
)
for name in names:
if name in result:
view_elements.content[ name ] = result[name]
view_elements.content[name] = result[name]
except Exception:
pass
return view_elements

class_trait_view_elements = classmethod( class_trait_view_elements )
class_trait_view_elements = classmethod(class_trait_view_elements)

#################################################################
# `TVTKBase` interface.
Expand Down Expand Up @@ -464,7 +490,7 @@ def update_traits(self, obj=None, event=None):
the VTK observer callback functions.

"""
if self._in_set:
if self._in_set or _DISABLE_UPDATE:
return
if not hasattr(self, '_updateable_traits_'):
return
Expand Down Expand Up @@ -538,11 +564,11 @@ def _do_change(self, method, val, force_update=False):
method(*val)
else:
raise
self._in_set -= 1
finally:
self._in_set -= 1
if force_update or self._wrapped_mtime(vtk_obj) > mtime:
self.update_traits()


def _wrap_call(self, vtk_method, *args):
"""This method allows us to safely call a VTK method without
calling `update_traits` during the call. This method is
Expand All @@ -565,8 +591,10 @@ def _wrap_call(self, vtk_method, *args):
vtk_obj = self._vtk_obj
self._in_set += 1
mtime = self._wrapped_mtime(vtk_obj) + 1
ret = vtk_method(*args)
self._in_set -= 1
try:
ret = vtk_method(*args)
finally:
self._in_set -= 1
if self._wrapped_mtime(vtk_obj) > mtime:
self.update_traits()
return ret
Expand Down