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
|
"""Test subclassing support in VTK-Python
VTK classes can be subclassed in Python. There are
some caveats, such as:
- protected items are inaccessible to the python class
- virtual method calls from C++ are not propagated to python
To be tested:
- make sure that subclassing works
- make sure that unbound superclass methods can be called
Created on Sept 26, 2010 by David Gobbi
"""
import sys
from vtkmodules.vtkCommonCore import (
vtkCollection,
vtkDataArray,
vtkIntArray,
vtkObject,
vtkObjectBase,
vtkPoints,
)
from vtkmodules.vtkCommonDataModel import vtkImageData
from vtkmodules.vtkFiltersSources import vtkSphereSource
from vtkmodules.vtkImagingSources import vtkImageGridSource
from vtkmodules.test import Testing
class vtkCustomObject(vtkObject):
def __init__(self, extra=None):
"""Initialize all attributes."""
if extra is None:
extra = vtkObject()
self._ExtraObject = extra
def GetClassName(self):
"""Get the class name."""
return self.__class__.__name__
def GetExtraObject(self):
"""Getter method."""
return self._ExtraObject
def SetExtraObject(self, o):
"""Setter method."""
# make sure it is "None" or a vtkobject instance
if o == None or isinstance(o, vtkObjectBase):
self._ExtraObject = o
self.Modified()
else:
raise TypeError("requires None or a vtkobject")
def GetMTime(self):
"""Override a method (only works when called from Python)"""
t = vtkObject.GetMTime(self)
if self._ExtraObject:
t = max(t, self._ExtraObject.GetMTime())
return t
class vtkPointsCustom(vtkPoints):
def __init__(self):
self.some_attribute = "custom"
class vtkImageDataWarning(vtkImageData):
def __init__(self, spacing=(1.0, 1.0, 1.0)):
# this sets the spacing when the constructor is called without any
# arguments, which generates a warning in testOverrideWarning (see
# the test code below for an explanation)
self.SetSpacing(spacing)
class TestSubclass(Testing.vtkTest):
def testSubclassInstantiate(self):
"""Instantiate a python vtkObject subclass"""
o = vtkCustomObject()
self.assertEqual(o.GetClassName(), "vtkCustomObject")
def testConstructorArgs(self):
"""Test the use of constructor arguments."""
extra = vtkObject()
o = vtkCustomObject(extra)
self.assertEqual(o.GetClassName(), "vtkCustomObject")
self.assertEqual(id(o.GetExtraObject()), id(extra))
def testCallUnboundMethods(self):
"""Test calling an unbound method in an overridden method"""
o = vtkCustomObject()
a = vtkIntArray()
o.SetExtraObject(a)
a.Modified()
# GetMTime should return a's mtime
self.assertEqual(o.GetMTime(), a.GetMTime())
# calling the vtkObject mtime should give a lower MTime
self.assertNotEqual(o.GetMTime(), vtkObject.GetMTime(o))
# another couple quick unbound method check
vtkDataArray.InsertNextTuple1(a, 2)
self.assertEqual(a.GetTuple1(0), 2)
def testPythonRTTI(self):
"""Test the python isinstance and issubclass methods """
o = vtkCustomObject()
d = vtkIntArray()
self.assertEqual(True, isinstance(o, vtkObjectBase))
self.assertEqual(True, isinstance(d, vtkObjectBase))
self.assertEqual(True, isinstance(o, vtkCustomObject))
self.assertEqual(False, isinstance(d, vtkCustomObject))
self.assertEqual(False, isinstance(o, vtkDataArray))
self.assertEqual(True, issubclass(vtkCustomObject, vtkObject))
self.assertEqual(False, issubclass(vtkObject, vtkCustomObject))
self.assertEqual(False, issubclass(vtkCustomObject, vtkDataArray))
def testSubclassGhost(self):
"""Make sure ghosting of the class works"""
o = vtkCustomObject()
c = vtkCollection()
c.AddItem(o)
i = id(o)
del o
o = vtkObject()
o = c.GetItemAsObject(0)
# make sure the id has changed, but class the same
self.assertEqual(o.__class__, vtkCustomObject)
self.assertNotEqual(i, id(o))
def testOverride(self):
"""Make sure that overwriting with a subclass works"""
self.assertFalse(isinstance(vtkPoints(), vtkPointsCustom))
# check that object has the correct class
vtkPoints.override(vtkPointsCustom)
self.assertTrue(isinstance(vtkPoints(), vtkPointsCustom))
# check object created deep in c++
source = vtkSphereSource()
source.Update()
points = source.GetOutput().GetPoints()
self.assertTrue(isinstance(points, vtkPointsCustom))
# check that __init__ is called
self.assertEqual(points.some_attribute, "custom")
# check that overrides can be removed
vtkPoints.override(None)
self.assertTrue(vtkPoints().__class__ == vtkPoints)
def testOverrideWarning(self):
"""Check if a late call to __init__() modifies the C++ object"""
# check that the object has the correct class
vtkImageData.override(vtkImageDataWarning)
self.assertTrue(isinstance(vtkImageData(), vtkImageDataWarning))
# check object created deep in c++
source = vtkImageGridSource()
source.SetDataExtent(0, 255, 0, 255, 0, 0)
source.SetDataSpacing(0.1, 0.1, 1.0)
# calling Update() instantiates the data object in C++
source.Update()
# calling GetOutput() instantiates the data object in Python,
# and reports RuntimeWarning because our override modifies the data
with self.assertWarns(RuntimeWarning):
data = source.GetOutput()
# the custom __init__() method modified the spacing to be (1.0,1.0,1.0),
# the purpose of the RuntimeWarning is to let the user know that an odd
# modification like this has occurred
self.assertEqual(data.GetSpacing(), (1.0, 1.0, 1.0))
if __name__ == "__main__":
Testing.main([(TestSubclass, 'test')])
|