I found a very strange bug in the 3D visualization library mayavi which I did not initially understand. I have tracked it down to a problem with wxpython.
In my use case, I create a Mayavi window inside a wx GUI (which happens to be created by traitsui but my example as follows bypasses the traitsui component of the problem). Then, using pyface.GUI.invoke_later() to immediately invoke some GUI events following the construction of the GUI and invocation of the event loop, I save some screen captures from mayavi, which under normal conditions resizes the window. However, in this use case, the window did not resize properly, and I have tracked the bug down to the call to SetSize( ) on the wxVTKRenderWindowInteractor which extends wx.GLCanvas.
Here is a minimal snippet which reliably reproduces the problem:
from traits.api import *
from traitsui.api import *
from mayavi import mlab
from mayavi.core.ui.api import MayaviScene, SceneEditor, MlabSceneModel
from pyface.api import GUI
import numpy as np
class MayaviGUIApp(HasTraits):
scene = Instance(MlabSceneModel,())
shell = Dict
@on_trait_change('scene.activated')
def setup(self):
x,y,z = np.random.random((3,10))
mlab.points3d(x,y,z,figure=self.scene.mayavi_scene)
traits_view = View(
Item(name='scene',
editor=SceneEditor(scene_class=MayaviScene),
height=500, width=500, show_label=False, resizable=True),
Item(name='shell',
editor=ShellEditor(),
height=300, width=500, show_label=False, resizable=True))
def invoke_script(scene=None):
# pass some script to this function and then execute it
#_vtk_control is a wxVTKRenderWindowInteractor which is a subclass of wx.GLCanvas
scene.scene.scene_editor._vtk_control.SetSize([375,375])
mlab.savefig('test_invokelater_1.png', figure=scene, magnification=2)
scene.scene.scene_editor._vtk_control.SetSize([500,468]) #set back to the original size
#this is equivalent to mlab.savefig('test_invokelater_1.png', size=(750,750), figure=scene), except the window
#resizing that is done in several levels of library calls eventually culminating in a wx canvas is managed manually.
#now if we try it again, it works this time.
scene.scene.scene_editor._vtk_control.SetSize([375,375])
mlab.savefig('test_invokelater_2.png', figure=scene, magnification=2)
scene.scene.scene_editor._vtk_control.SetSize([500,468]) #set back to the original size
mga = MayaviGUIApp()
gui = GUI() #from pyface import GUI
gui.invoke_later(lambda:invoke_script(scene=mga.scene.mayavi_scene))
invoke_later is just a toolkit-independent wrapper for wx.CallAfter
mga.configure_traits()
configure_traits sets up the GUI and then starts the event loop
This snippet reliably produces two images. Instead of producing them both at the correct resolution 750x750 (375 * 2), the first one is saved with the resolution 1000x936 (500x468 * 2), while the second one is saved with the correct size. This is because in some way the SetSize fails for some reason.
I apologize if this is not a very good place to ask about this, but I decided to search after I tracked this to the wx layer because I do not have very much experience with wxpython. I don’t expect people to dig through the mayavi/tvtk/pyface codebases, but if anyone can help diagnose why SetSize might fail proximally inside wx.CallAfter I would greatly appreciate it.
thanks,
R