Search code examples
pythonwxpythonvispy

How to dynamically add an object to a scene in vispy canvas embedded in wxpython


I want to add an arrow (some object) upon the press of a button. The code is a modified version of the example given in the vispy repository.

import wx
from vispy import scene
from vispy.scene.visuals import Arrow

import numpy as np

class Canvas(scene.SceneCanvas):
    def __init__(self, *a, **k):
        sizes = k.pop("sizes", (300, 300))  # Default value is (300, 300)
        scene.SceneCanvas.__init__(self, *a, **k, size=sizes)
        view = self.central_widget.add_view()
        view.bgcolor = 'snow'
        view.camera = scene.TurntableCamera(up='+y', azimuth=100, elevation=15, fov=60)
        axis = scene.visuals.XYZAxis(parent=view.scene)
        
        arrow1 = np.array([(0, 0, 0, 1, 1, 0)])  # Arrow direction, position
        arr = Arrow(pos=np.array([(0, 0, 0), (1, 1, 0)]), color='teal', method='gl', width=5., arrows=arrow1,
                    arrow_type="angle_30", arrow_size=5.0, arrow_color='teal', antialias=True, parent=view.scene)
        
        self.show()

class mainFrame ( wx.Frame ):

    def __init__( self, parent ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = "Add Object", pos = wx.DefaultPosition, size = wx.Size( 905,569 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

        self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )

        bSizer1 = wx.BoxSizer( wx.VERTICAL )

        self.panel_vispy = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizer1.Add( self.panel_vispy, 9, wx.EXPAND |wx.ALL, 5 )

        self.panel_Buttons = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizer2 = wx.BoxSizer( wx.HORIZONTAL )


        bSizer2.Add( ( 0, 0), 1, wx.EXPAND, 5 )

        self.button_AddArrow = wx.Button( self.panel_Buttons, wx.ID_ANY, u"Add Arrow", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer2.Add( self.button_AddArrow, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 )


        bSizer2.Add( ( 0, 0), 1, wx.EXPAND, 5 )


        self.panel_Buttons.SetSizer( bSizer2 )
        self.panel_Buttons.Layout()
        bSizer2.Fit( self.panel_Buttons )
        bSizer1.Add( self.panel_Buttons, 1, wx.EXPAND |wx.ALL, 5 )


        self.SetSizer( bSizer1 )
        self.Layout()

        self.Centre( wx.BOTH )
        
        # Connect Events
        self.button_AddArrow.Bind(wx.EVT_BUTTON, self.button_AddArrow_OnClick)
        
        self.panel_vispy.canvas = Canvas(app="wx", parent=self, sizes=self.panel_vispy.GetSize())
    
    def button_AddArrow_OnClick(self, event):
        print("Adding arrow")
        arrow2 = np.array([(0, 0, 0, 1, 1, 0)])  # Arrow direction, position
        arr = Arrow(pos=np.array([(0, 0, 0), (1, 1, 0)]), color='red', method='gl', width=5., arrows=arrow2,
                    arrow_type="angle_30", arrow_size=5.0, arrow_color='red', antialias=True)#, parent=view.scene) -Unable to access view from the Canvas class


if __name__ == "__main__":
    app = wx.App(False)
    
    GUI = mainFrame(None)
    
    GUI.Show(True)
    
    app.MainLoop()

I have created the Canvas as given in the example and tried to instantiate it from the mainFrame. All the objects that are given in the __init__ of the Canvas class are created. If I want to add any new object after the GUI is created with the press of a button, I am unable to access the view in the Canvas class from the mainFrame. Or is there any other way to achieve this.

Update: Here is the code after following the suggestions given in the comments:

import wx
from vispy import scene, gloo
from vispy.scene.visuals import Arrow

import numpy as np

class Canvas(scene.SceneCanvas):
    def __init__(self, *a, **k):
        sizes = k.pop("sizes", (300, 300))  # Default value is (300, 300)
        scene.SceneCanvas.__init__(self, *a, **k, size=sizes)
        self.unfreeze()
        self.view = self.central_widget.add_view()
        self.view.bgcolor = 'snow'
        self.view.camera = scene.TurntableCamera(up='+y', azimuth=100, elevation=15, fov=60)
        axis = scene.visuals.XYZAxis(parent=self.view.scene)
        
        arrow1 = np.array([(0, 0, 0, 1, 1, 0)])  # Arrow direction, position
        arr = Arrow(pos=np.array([(0, 0, 0), (1, 1, 0)]), color='teal', method='gl', width=5., arrows=arrow1,
                    arrow_type="angle_30", arrow_size=5.0, arrow_color='teal', antialias=True, parent=self.view.scene)
        
        self.show()
    
class mainFrame ( wx.Frame ):

    def __init__( self, parent ):
        wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = "Add Object", pos = wx.DefaultPosition, size = wx.Size( 905,569 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )

        self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )

        bSizer1 = wx.BoxSizer( wx.VERTICAL )

        self.panel_vispy = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizer1.Add( self.panel_vispy, 9, wx.EXPAND |wx.ALL, 5 )

        self.panel_Buttons = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
        bSizer2 = wx.BoxSizer( wx.HORIZONTAL )


        bSizer2.Add( ( 0, 0), 1, wx.EXPAND, 5 )

        self.button_AddArrow = wx.Button( self.panel_Buttons, wx.ID_ANY, u"Add Arrow", wx.DefaultPosition, wx.DefaultSize, 0 )
        bSizer2.Add( self.button_AddArrow, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 )


        bSizer2.Add( ( 0, 0), 1, wx.EXPAND, 5 )


        self.panel_Buttons.SetSizer( bSizer2 )
        self.panel_Buttons.Layout()
        bSizer2.Fit( self.panel_Buttons )
        bSizer1.Add( self.panel_Buttons, 1, wx.EXPAND |wx.ALL, 5 )


        self.SetSizer( bSizer1 )
        self.Layout()

        self.Centre( wx.BOTH )
        
        # Connect Events
        self.button_AddArrow.Bind(wx.EVT_BUTTON, self.button_AddArrow_OnClick)
        
        self.panel_vispy.canvas = Canvas(app="wx", parent=self, sizes=self.panel_vispy.GetSize())
    
    def button_AddArrow_OnClick(self, event):
        print("Adding arrow")
        arrow2 = np.array([(0, 0, 0, 1, 1, 0)])  # Arrow direction, position
        arr = Arrow(pos=np.array([(0, 0, 0), (1, 1, 0)]), color='teal', method='gl', width=5., arrows=arrow2,
                    arrow_type="angle_30", arrow_size=5.0, arrow_color='teal', antialias=True, parent=self.panel_vispy.canvas.view.scene)
        self.panel_vispy.canvas.update() # Canvas not getting updated


if __name__ == "__main__":
    app = wx.App(False)
    
    GUI = mainFrame(None)
    
    GUI.Show(True)
    
    app.MainLoop()

The code now runs without any error which means the arrow is probably added but the scene is not updated.


Solution

  • Following the suggestions of @djhoese and @RolfofSaxony, I have updated the code. The code now successfully adds an arrow upon clicking the button.

    import wx
    from vispy import scene, gloo
    from vispy.scene.visuals import Arrow
    
    import numpy as np
    
    class Canvas(scene.SceneCanvas):
        def __init__(self, *a, **k):
            sizes = k.pop("sizes", (300, 300))  # Default value is (300, 300)
            scene.SceneCanvas.__init__(self, *a, **k, size=sizes)
            self.unfreeze()
            self.view = self.central_widget.add_view()
            self.view.bgcolor = 'snow'
            self.view.camera = scene.TurntableCamera(up='+y', azimuth=100, elevation=15, fov=60)
            axis = scene.visuals.XYZAxis(parent=self.view.scene)
            
            arrow1 = np.array([(0, 0, 0, 1, 1, 0)])  # Arrow direction, position
            arr = Arrow(pos=np.array([(0, 0, 0), (1, 1, 0)]), color='red', method='gl', width=5., arrows=arrow1,
                        arrow_type="angle_30", arrow_size=5.0, arrow_color='blue', antialias=True, parent=self.view.scene)
            
            self.show()
            
    class mainFrame ( wx.Frame ):
    
        def __init__( self, parent ):
            wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = "Add Object", pos = wx.DefaultPosition, size = wx.Size( 905,569 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
    
            self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
    
            bSizer1 = wx.BoxSizer( wx.VERTICAL )
    
            self.panel_vispy = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
            bSizer1.Add( self.panel_vispy, 9, wx.EXPAND |wx.ALL, 5 )
    
            self.panel_Buttons = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
            bSizer2 = wx.BoxSizer( wx.HORIZONTAL )
    
    
            bSizer2.Add( ( 0, 0), 1, wx.EXPAND, 5 )
    
            self.button_AddArrow = wx.Button( self.panel_Buttons, wx.ID_ANY, u"Add Arrow", wx.DefaultPosition, wx.DefaultSize, 0 )
            bSizer2.Add( self.button_AddArrow, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 )
    
    
            bSizer2.Add( ( 0, 0), 1, wx.EXPAND, 5 )
    
    
            self.panel_Buttons.SetSizer( bSizer2 )
            self.panel_Buttons.Layout()
            bSizer2.Fit( self.panel_Buttons )
            bSizer1.Add( self.panel_Buttons, 1, wx.EXPAND |wx.ALL, 5 )
    
    
            self.SetSizer( bSizer1 )
            self.Layout()
    
            self.Centre( wx.BOTH )
            
            # Connect Events
            self.button_AddArrow.Bind(wx.EVT_BUTTON, self.button_AddArrow_OnClick)
            
            self.panel_vispy.canvas = Canvas(app="wx", parent=self, sizes=self.panel_vispy.GetSize())
        
        def button_AddArrow_OnClick(self, event):
            print("Adding arrow")
            arrow2 = np.array([(0, 0, 0, -1, -0.5, 1)])  # Arrow direction, position
            arr = Arrow(pos=np.array([(0, 0, 0), (-1, -0.5, 1)]), color='green', method='gl', width=5., arrows=arrow2,
                        arrow_type="angle_30", arrow_size=5.0, arrow_color='blue', antialias=True, parent=self.panel_vispy.canvas.view.scene)
    
    
    if __name__ == "__main__":
        app = wx.App(False)
        
        GUI = mainFrame(None)
        
        GUI.Show(True)
        
        app.MainLoop()