Index: simulationview/setup.py
===================================================================
--- simulationview/setup.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
+++ simulationview/setup.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
@@ -0,0 +1,28 @@
+"""
+     Installation script for SANS gui tools
+"""
+import os
+# Then build and install the modules
+from distutils.core import setup
+setup(
+    name="simulationview",
+    version = "0.1.1",
+    description = "Python module for SANS SimView",
+    author = "University of Tennessee",
+    #author_email = "",
+    url = "http://danse.chem.utk.edu",
+    
+    # Place this module under the sans package
+    #ext_package = "sans",
+    
+    # Use the pure python modules
+    package_dir = {"sans":".",
+                   "sans.perspectives":os.path.join("src", "sans",
+                                          "perspectives"),
+                   "sans.perspectives.simulation":os.path.join("src", "sans",
+                                          "perspectives",
+                                          "simulation")},
+    
+    packages = ["sans", "sans.perspectives", "sans.perspectives.simulation"]
+    )
+        
Index: simulationview/src/sans/perspectives/simulation/ShapeAdapter.py
===================================================================
--- simulationview/src/sans/perspectives/simulation/ShapeAdapter.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
+++ simulationview/src/sans/perspectives/simulation/ShapeAdapter.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
@@ -0,0 +1,133 @@
+"""
+This software was developed by the University of Tennessee as part of the
+Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+project funded by the US National Science Foundation. 
+
+See the license text in license.txt
+
+copyright 2009, University of Tennessee
+"""
+import sans.realspace.VolumeCanvas as VolumeCanvas
+
+class ShapeVisitor:
+    """
+        The shape visitor process a GUI object representing 
+        a shape and returns a shape description that can be
+        processed by the VolumeCanvas.
+    """
+    
+    def __init__(self):
+        """
+            Initialize visitor
+        """
+        ## Place holder for the simulation canvas
+        self.sim_canvas = None
+    
+    def fromCylinder(self, cyl):
+        """
+            Create a simulation cylinder descriptor (computation) 
+            from the information produced by the GUI
+            @param cyl: Cylinder object from the GUI canvas 
+            @return: VolumeCanvas.CylinderDescriptor object
+        """
+        from sans.realspace.VolumeCanvas import CylinderDescriptor
+        desc = CylinderDescriptor()
+         
+        desc.params["center"] = [cyl.x, cyl.y, cyl.z]
+        # Orientation are angular offsets in degrees with respect to X, Y, Z
+        desc.params["orientation"] = [cyl.theta_x, cyl.theta_y, cyl.theta_z]
+        desc.params["order"] = cyl.params["order"]
+        
+        
+        # Length of the cylinder
+        desc.params["length"] = cyl.params["length"]
+        # Radius of the cylinder
+        desc.params["radius"] = cyl.params["radius"]
+        # Constrast parameter
+        desc.params["contrast"] = cyl.params["contrast"]
+        
+        return desc
+    
+    def fromSphere(self, sph):
+        """
+            Create a simulation sphere descriptor (computation) 
+            from the information produced by the GUI
+            @param sph: Sphere object from the GUI canvas 
+            @return: VolumeCanvas.SphereDescriptor object
+        """
+        from sans.realspace.VolumeCanvas import SphereDescriptor
+        desc = SphereDescriptor()
+         
+        desc.params["center"] = [sph.x, sph.y, sph.z]
+        desc.params["order"] = sph.params["order"]
+        # Radius of the sphere
+        desc.params["radius"] = sph.params["radius"]
+        # Constrast parameter
+        desc.params["contrast"] = sph.params["contrast"]
+        
+        return desc
+    
+    def update_sphere(self, sph):
+        """
+            Update a shape description in the simulation canvas (computation)
+            according to the new parameters of the GUI canvas.
+            @param sph: Sphere object from the GUI canvas 
+        """
+        # Check class
+        if self.sim_canvas.shapes[sph.name].__class__.__name__=="SphereDescriptor":
+            desc = self.sim_canvas.shapes[sph.name]
+            
+            desc.params["center"] = [sph.x, sph.y, sph.z]
+            desc.params["order"] = sph.params["order"]
+            # Radius of the sphere
+            desc.params["radius"] = sph.params["radius"]
+            # Constrast parameter
+            desc.params["contrast"] = sph.params["contrast"]
+            
+            self.sim_canvas._model_changed()
+        else:
+            raise ValueError, "SimShapeVisitor: Wrong class for visited object"
+        
+        
+        
+    def update_cylinder(self, cyl):
+        """
+            Update a shape description in the simulation canvas (computation)
+            according to the new parameters of the GUI canvas.
+            @param cyl: Cylinder object from the GUI canvas 
+        """
+        # Check class
+        if self.sim_canvas.shapes[cyl.name].__class__.__name__=="CylinderDescriptor":
+            desc = self.sim_canvas.shapes[cyl.name]
+            
+            desc.params["center"] = [cyl.x, cyl.y, cyl.z]
+            # Orientation are angular offsets in degrees with respect to X, Y, Z
+            desc.params["orientation"] = [cyl.theta_x, cyl.theta_y, cyl.theta_z]
+            desc.params["order"] = cyl.params["order"]
+            
+            # Length of the cylinder
+            desc.params["length"] = cyl.params["length"]
+            # Radius of the cylinder
+            desc.params["radius"] = cyl.params["radius"]
+            # Constrast parameter
+            desc.params["contrast"] = cyl.params["contrast"]
+            
+            self.sim_canvas._model_changed()
+        else:
+            raise ValueError, "SimShapeVisitor: Wrong class for visited object"
+        
+    
+    def update(self, volCanvas, shape):
+        """
+            Update the shape descriptor in the VolumeCanvas
+            corresponding to the given 'shape' parameter
+            
+            @param volCanvas: VolumeCanvas object
+            @param shape: SimCanvas.BaseShape object
+        """
+        if shape.name in volCanvas.getShapeList():
+            self.sim_canvas = volCanvas
+            shape.accept_update(self)
+        else:
+            raise ValueError, "ShapeAdapter: Shape [%s] not in list" % shape.name
+
Index: simulationview/src/sans/perspectives/simulation/ShapeParameters.py
===================================================================
--- simulationview/src/sans/perspectives/simulation/ShapeParameters.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
+++ simulationview/src/sans/perspectives/simulation/ShapeParameters.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
@@ -0,0 +1,489 @@
+"""
+This software was developed by the University of Tennessee as part of the
+Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+project funded by the US National Science Foundation. 
+
+See the license text in license.txt
+
+copyright 2009, University of Tennessee
+"""
+import wx
+import sys
+import wx.lib.newevent
+from copy import deepcopy
+import SimCanvas
+
+(CreateShapeEvent, EVT_ADD_SHAPE) = wx.lib.newevent.NewEvent()
+(EditShapeEvent, EVT_EDIT_SHAPE)  = wx.lib.newevent.NewEvent()
+(DelShapeEvent, EVT_DEL_SHAPE)    = wx.lib.newevent.NewEvent()
+(QRangeEvent, EVT_Q_RANGE)        = wx.lib.newevent.NewEvent() 
+(PtDensityEvent, EVT_PT_DENSITY)  = wx.lib.newevent.NewEvent() 
+
+class ShapeParameterPanel(wx.Panel):
+    #TODO: show units
+    #TODO: order parameters properly
+    CENTER_PANE = True
+    
+    def __init__(self, parent, q_min=0.001, q_max=0.5, q_npts=10, pt_density=0.1, *args, **kwargs):
+        wx.Panel.__init__(self, parent, *args, **kwargs)
+        
+        self.window_name = "ShapeParams"
+        self.window_caption = "Shape parameter"
+        
+      
+        self.params = {}
+        self.parent = parent
+        self.type = None
+        self.listeners = []
+        self.parameters = []
+        self.bck = wx.GridBagSizer(5,5)
+        self.SetSizer(self.bck)
+        
+        # position and orientation ctrl
+        self.xctrl = None
+        self.yctrl = None
+        self.zctrl = None
+        self.actrl = None
+        self.bctrl = None
+        self.cctrl = None
+               
+        # Sizer to hold the shape parameters
+        self.shape_sizer = wx.GridBagSizer(5,5)
+        
+        ny = 0       
+        title = wx.StaticText(self, -1, "[Temporary form]", style=wx.ALIGN_LEFT)
+        self.bck.Add(title, (ny,0), (1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
+        
+        # Shape list
+        shape_text = wx.StaticText(self, -1, "Geometric shape", style=wx.ALIGN_LEFT)
+        self.shape_list = SimCanvas.getShapes()
+        value_list = []
+        for item in self.shape_list:
+            value_list.append(item['name'])
+            
+        self.model_combo = wx.ComboBox(self, -1, value=value_list[0], choices=value_list, style=wx.CB_READONLY)
+        self.model_combo.SetToolTip(wx.ToolTip("Select a geometric shape from the drop-down list"))
+        
+        ny+=1
+        self.bck.Add(shape_text, (ny,0), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
+        self.bck.Add(self.model_combo, (ny,1), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
+        wx.EVT_COMBOBOX(self.model_combo,-1, self._on_select_shape)
+   
+        # Placeholder for parameter form
+        ny+=1
+        self.bck.Add(self.shape_sizer, (ny,0), (1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=0)   
+        
+        # Point density control
+        point_density_text = wx.StaticText(self, -1, "Point density", style=wx.ALIGN_LEFT)
+        ny+=1
+        self.bck.Add(point_density_text, (ny,0), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        self.point_density_ctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+        self.point_density_ctrl.SetValue(str(pt_density))
+        self.point_density_ctrl.SetToolTip(wx.ToolTip("Enter the number of real-space points per Angstrom cube"))
+        self.point_density_ctrl.Bind(wx.EVT_TEXT_ENTER, self._on_density_changed)
+        self.point_density_ctrl.Bind(wx.EVT_KILL_FOCUS, self._on_density_changed)
+        self.bck.Add(self.point_density_ctrl, (ny,1), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        
+        # Q range
+        q_min_text = wx.StaticText(self, -1, "Q min", style=wx.ALIGN_LEFT)
+        ny+=1
+        self.bck.Add(q_min_text, (ny,0), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        self.q_min_ctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+        self.q_min_ctrl.SetValue(str(q_min))
+        self.q_min_ctrl.SetToolTip(wx.ToolTip("Enter the minimum Q value to be simulated"))
+        self.q_min_ctrl.Bind(wx.EVT_TEXT_ENTER, self._on_q_range_changed)
+        self.q_min_ctrl.Bind(wx.EVT_KILL_FOCUS, self._on_q_range_changed)
+        self.bck.Add(self.q_min_ctrl, (ny,1), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        
+        q_max_text = wx.StaticText(self, -1, "Q max", style=wx.ALIGN_LEFT)
+        self.bck.Add(q_max_text, (ny,2), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        self.q_max_ctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+        self.q_max_ctrl.SetValue(str(q_max))
+        self.q_min_ctrl.SetToolTip(wx.ToolTip("Enter the maximum Q value to be simulated"))
+        self.q_max_ctrl.Bind(wx.EVT_TEXT_ENTER, self._on_q_range_changed)
+        self.q_max_ctrl.Bind(wx.EVT_KILL_FOCUS, self._on_q_range_changed)
+        self.bck.Add(self.q_max_ctrl, (ny,3), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        
+        q_npts_text = wx.StaticText(self, -1, "No. of Q pts", style=wx.ALIGN_LEFT)
+        self.bck.Add(q_npts_text, (ny,4), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        self.q_npts_ctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+        self.q_npts_ctrl.SetValue(str(q_npts))
+        self.q_min_ctrl.SetToolTip(wx.ToolTip("Enter the number of Q points to be simulated"))
+        self.q_npts_ctrl.Bind(wx.EVT_TEXT_ENTER, self._on_q_range_changed)
+        self.q_npts_ctrl.Bind(wx.EVT_KILL_FOCUS, self._on_q_range_changed)
+        self.bck.Add(self.q_npts_ctrl, (ny,5), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        
+        # Shape List
+        # Internal counter of shapes in the listbox
+        self.counter = 0
+        # Buffer filled flag
+        self.buffer_filled = False
+        # Save current flag
+        self.current_saved = False       
+         
+        id = wx.NewId()
+        shape_listbox_text = wx.StaticText(self, -1, "List of shapes on 3D canvas", style=wx.ALIGN_LEFT)
+        self.shape_listbox = wx.ListBox(self, id, wx.DefaultPosition, (295, 200), 
+                                   [], wx.LB_SINGLE | wx.LB_HSCROLL)
+        
+        # Listen to history events
+        self.parent.Bind(EVT_ADD_SHAPE, self._on_add_shape_to_listbox)
+        self.shape_listbox.Bind(wx.EVT_LISTBOX, self._on_select_from_listbox, id=id)
+        self.shape_listbox.Bind(wx.EVT_CONTEXT_MENU, self._on_listbox_context_menu, id=id)
+        
+        ny+=1
+        self.bck.Add(shape_listbox_text, (ny,0), (1,2), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        ny+=1
+        self.bck.Add(self.shape_listbox, (ny,0), (1,5), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+        
+        self.current_shape = None
+        
+        # Default shape
+        if type(self.shape_list)==list and len(self.shape_list)>0:
+            shape = SimCanvas.getShapeClassByName(self.shape_list[0]['name'])()
+            self.editShape(shape)                        
+
+    def _on_select_shape(self, event):
+        shape = SimCanvas.getShapeClassByName(self.shape_list[event.GetEventObject().GetSelection()]['name'])()
+        self.editShape(shape)    
+
+    def _on_density_changed(self, event):
+        """
+            Process event that might mean a change of the simulation point density
+        """
+        npts  = self._readCtrlFloat(self.point_density_ctrl)
+        if npts is not None:
+            event = PtDensityEvent(npts=npts)
+            wx.PostEvent(self.parent, event)
+            
+    def _on_q_range_changed(self, event):
+        """
+            Process event that might mean a change in Q range
+            @param event: EVT_Q_RANGE event
+        """
+        q_min = self._readCtrlFloat(self.q_min_ctrl)
+        q_max = self._readCtrlFloat(self.q_max_ctrl)
+        npts  = self._readCtrlInt(self.q_npts_ctrl)
+        if q_min is not None or q_max is not None or npts is not None:
+            event = QRangeEvent(q_min=q_min, q_max=q_max, npts=npts)
+            wx.PostEvent(self.parent, event)
+            
+    def _onEditShape(self, evt):
+        """
+            Process an EVT_EDIT_SHAPE event 
+            @param evt: EVT_EDIT_SHAPE object
+        """
+        evt.Skip()
+        self.editShape(evt.shape, False)
+        
+    def editShape(self, shape=None, new=True):
+        """
+            Rebuild the panel
+        """
+        self.current_shape = shape
+        self.shape_sizer.Clear(True)  
+        #self.type = type  
+        
+        if shape==None:
+            title = wx.StaticText(self, -1, "Use menu to add a shapes", style=wx.ALIGN_LEFT)
+            self.shape_sizer.Add(title, (0,0), (1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
+
+        else:
+            if new==False:
+                title = wx.StaticText(self, -1, "Edit shape parameters", style=wx.ALIGN_LEFT)
+            else:
+                title = wx.StaticText(self, -1, "Create shape from parameters", style=wx.ALIGN_LEFT)
+                
+            self.shape_sizer.Add(title, (0,0), (1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border=15)
+            
+            n = 1
+            self.parameters = []
+            keys = shape.params.keys()
+            keys.sort()
+            
+            for item in keys:
+                if item in ['contrast', 'order']:
+                    continue
+                n += 1
+                text = wx.StaticText(self, -1, item, style=wx.ALIGN_LEFT)
+                self.shape_sizer.Add(text, (n-1,0), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+                ctl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+                
+                
+                ctl.SetValue(str(shape.params[item]))
+                self.Bind(wx.EVT_TEXT_ENTER, self.onTextEnter)
+                ctl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
+                self.parameters.append([item, ctl])
+                self.shape_sizer.Add(ctl, (n-1,1), flag=wx.TOP|wx.BOTTOM, border = 0)
+                
+                # Units
+                units = wx.StaticText(self, -1, shape.details[item], style=wx.ALIGN_LEFT)
+                self.shape_sizer.Add(units, (n-1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+
+            for item in ['contrast', 'order']:
+                n += 1
+                text = wx.StaticText(self, -1, item, style=wx.ALIGN_LEFT)
+                self.shape_sizer.Add(text, (n-1,0), flag = wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+                ctl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+                
+                
+                ctl.SetValue(str(shape.params[item]))
+                self.Bind(wx.EVT_TEXT_ENTER, self.onTextEnter)
+                ctl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
+                self.parameters.append([item, ctl])
+                self.shape_sizer.Add(ctl, (n-1,1), flag=wx.TOP|wx.BOTTOM, border = 0)
+                
+                # Units
+                units = wx.StaticText(self, -1, shape.details[item], style=wx.ALIGN_LEFT)
+                self.shape_sizer.Add(units, (n-1,2), flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL, border = 15)
+                
+                
+            # Add position
+            n += 1
+            
+            pos_sizer = wx.GridBagSizer(0,0)
+            
+            text = wx.StaticText(self, -1, 'x', style=wx.ALIGN_LEFT)
+            pos_sizer.Add(text, (0,0), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            text = wx.StaticText(self, -1, 'y', style=wx.ALIGN_LEFT)
+            pos_sizer.Add(text, (0,1), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            text = wx.StaticText(self, -1, 'z', style=wx.ALIGN_LEFT)
+            pos_sizer.Add(text, (0,2), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            
+            self.xctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+            self.xctrl.SetValue(str(shape.x))
+            self.xctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
+            pos_sizer.Add(self.xctrl, (1,0), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            self.yctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+            self.yctrl.SetValue(str(shape.y))
+            self.yctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
+            pos_sizer.Add(self.yctrl, (1,1), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            self.zctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+            self.zctrl.SetValue(str(shape.z))
+            self.zctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
+            pos_sizer.Add(self.zctrl, (1,2), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            
+            self.shape_sizer.Add(pos_sizer, (n-1, 0), (1,3), flag=wx.LEFT|wx.ALIGN_CENTER)
+            
+            # Add orientation
+            n += 1
+            
+            pos_sizer = wx.GridBagSizer(0,0)
+            
+            text = wx.StaticText(self, -1, 'theta_x', style=wx.ALIGN_LEFT)
+            pos_sizer.Add(text, (0,0), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            text = wx.StaticText(self, -1, 'theta_y', style=wx.ALIGN_LEFT)
+            pos_sizer.Add(text, (0,1), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            text = wx.StaticText(self, -1, 'theta_z', style=wx.ALIGN_LEFT)
+            pos_sizer.Add(text, (0,2), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            
+            self.actrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+            self.actrl.SetValue(str(shape.theta_x))
+            self.actrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
+            pos_sizer.Add(self.actrl, (1,0), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            self.bctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+            self.bctrl.SetValue(str(shape.theta_y))
+            self.bctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
+            pos_sizer.Add(self.bctrl, (1,1), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            self.cctrl = wx.TextCtrl(self, -1, size=(40,20), style=wx.TE_PROCESS_ENTER)
+            self.cctrl.SetValue(str(shape.theta_z))
+            self.cctrl.Bind(wx.EVT_KILL_FOCUS, self.onTextEnter)
+            pos_sizer.Add(self.cctrl, (1,2), (1,1), flag = wx.LEFT|wx.ALIGN_CENTER, border = 15)
+            
+            self.shape_sizer.Add(pos_sizer, (n-1, 0), (1,3), flag=wx.LEFT|wx.ALIGN_CENTER)
+            
+            
+            # Add a button to create the new shape on the canvas
+            n += 1
+            if new==True:
+                create = wx.Button(self, 1,"Create")
+                self.shape_sizer.Add(create, (n-1,1), (1,2), flag=wx.ALIGN_RIGHT|wx.ALL, border = 15)
+                self.Bind(wx.EVT_BUTTON, self._onCreate, id = 1)        
+            else:
+                create = wx.Button(self, 1,"Apply")
+                self.shape_sizer.Add(create, (n-1,1), (1,2), flag=wx.ALIGN_RIGHT|wx.ALL, border = 15)
+                self.Bind(wx.EVT_BUTTON, self._onEdited, id = 1)        
+                
+
+
+        self.shape_sizer.Layout()
+        self.shape_sizer.Fit(self)
+        try:
+            self.parent.GetSizer().Layout()
+        except:
+            print "TODO: move the Layout call of editShape up to the caller"
+
+    def _readCtrlFloat(self, ctrl):
+        """
+            Parses a TextCtrl for a float value.
+            Returns None is the value hasn't changed or the value is not a float.
+            The control background turns pink if the value is not a float.
+            
+            @param ctrl: TextCtrl object
+        """
+        if ctrl.IsModified():
+            ctrl.SetModified(False)
+            str_value = ctrl.GetValue().lstrip().rstrip()
+            try:
+                flt_value = float(str_value)
+                ctrl.SetBackgroundColour(
+                        wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
+                ctrl.Refresh()
+                return flt_value
+            except:
+                ctrl.SetBackgroundColour("pink")
+                ctrl.Refresh()
+        return None
+
+    def _readCtrlInt(self, ctrl):
+        """
+            Parses a TextCtrl for a float value.
+            Returns None is the value hasn't changed or the value is not a float.
+            The control background turns pink if the value is not a float.
+            
+            @param ctrl: TextCtrl object
+        """
+        if ctrl.IsModified():
+            ctrl.SetModified(False)
+            str_value = ctrl.GetValue().lstrip().rstrip()
+            try:
+                int_value = int(str_value)
+                ctrl.SetBackgroundColour(
+                        wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
+                ctrl.Refresh()
+                return int_value
+            except:
+                ctrl.SetBackgroundColour("pink")
+                ctrl.Refresh()
+        return None
+
+    def _parseCtrl(self):
+        try:
+            # Position
+            tmp = self._readCtrlFloat(self.xctrl)
+            if not tmp==None:
+                self.current_shape.x = tmp
+                
+            tmp = self._readCtrlFloat(self.yctrl)
+            if not tmp==None:
+                self.current_shape.y = tmp
+                
+            tmp = self._readCtrlFloat(self.zctrl)
+            if not tmp==None:
+                self.current_shape.z = tmp
+                
+            # Orientation
+            tmp = self._readCtrlFloat(self.actrl)
+            if not tmp==None:
+                self.current_shape.theta_x = tmp
+                
+            tmp = self._readCtrlFloat(self.bctrl)
+            if not tmp==None:
+                self.current_shape.theta_y = tmp
+                
+            tmp = self._readCtrlFloat(self.cctrl)
+            if not tmp==None:
+                self.current_shape.theta_z = tmp
+                
+            # Parameters
+            for item in self.parameters:
+                tmp = self._readCtrlFloat(item[1])
+                if not tmp==None:
+                    self.current_shape.params[item[0]] = tmp 
+        except:
+            print "Could not create"
+            print sys.exc_value
+                
+    def _onCreate(self, evt):
+        """
+            Create a new shape with the parameters given by the user
+        """
+        self._parseCtrl()
+        event = CreateShapeEvent(shape=self.current_shape, new=True)
+        wx.PostEvent(self.parent, event)
+        self.editShape(self.current_shape, False)
+            
+        
+    def _onEdited(self, evt):
+        self._parseCtrl()
+        event = CreateShapeEvent(shape=self.current_shape, new=False)
+        wx.PostEvent(self.parent, event)
+        self.editShape(self.current_shape, False)
+
+    def onTextEnter(self, evt): 
+        """
+            Read text field to check values
+        """ 
+        self._readCtrlFloat(self.xctrl)
+        self._readCtrlFloat(self.yctrl)
+        self._readCtrlFloat(self.zctrl)
+        self._readCtrlFloat(self.actrl)
+        self._readCtrlFloat(self.bctrl)
+        self._readCtrlFloat(self.cctrl)
+        for item in self.parameters:
+            self._readCtrlFloat(item[1])
+ 
+    #-- Methods to support list of shapes --
+    def _on_add_shape_to_listbox(self, event):
+        """
+            Process a new shape            
+            @param event: EVT_ADD_SHAPE event
+        """
+        event.Skip()
+        if event.new:
+            self.counter += 1
+            self.shape_listbox.Insert("%d: %s" % (self.counter, event.shape.name), 
+                                 self.shape_listbox.GetCount(), event.shape)
+
+    def _on_select_from_listbox(self, event):
+        """
+            Process item selection events
+        """
+        index = event.GetSelection()
+        view_name = self.shape_listbox.GetString(index)
+        self.editShape(shape=event.GetClientData(), new=False)
+        # The following event is bound to the SimPanel to highlight the shape in blue
+        #TODO: how come it doesn't work? 
+        wx.PostEvent(self.parent, EditShapeEvent(shape=event.GetClientData()))
+        #TODO: select the shape from the drop down
+        
+    def _on_listbox_context_menu(self, event):
+        """
+            Popup context menu event
+        """
+        # Create context menu
+        popupmenu = wx.Menu()
+        
+        # Create an item to delete the selected shape from the canvas
+        id = wx.NewId()
+        popupmenu.Append(id, "&Remove Selected")
+        wx.EVT_MENU(self, id, self._on_remove_shape_from_listbox)
+        
+        # Create an item to rename a shape to a more user friendly name
+        #id = wx.NewId()
+        #popupmenu.Append(102, "&Rename Selected")
+        #wx.EVT_MENU(self, 102, self.onRename)
+
+        pos = event.GetPosition()
+        pos = self.ScreenToClient(pos)
+        self.PopupMenu(popupmenu, pos)        
+        
+    def _on_remove_shape_from_listbox(self, ev):
+        """
+            Remove an item
+        """
+        indices = self.shape_listbox.GetSelections()
+        if len(indices)>0:
+            name =  self.shape_listbox.GetClientData(indices[0]).name
+            self.shape_listbox.Delete(indices[0])
+            wx.PostEvent(self.parent, DelShapeEvent(id = name))
+        
+    def _on_rename_shape_from_listbox(self, ev):
+        """
+            Rename an item
+        """
+        indices = self.shape_listbox.GetSelections()
+        if len(indices)>0:
+            print "NOT YET IMPLMENTED"
+            print "renaming", self.shape_listbox.GetString(indices[0])
+                
Index: simulationview/src/sans/perspectives/simulation/SimCanvas.py
===================================================================
--- simulationview/src/sans/perspectives/simulation/SimCanvas.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
+++ simulationview/src/sans/perspectives/simulation/SimCanvas.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
@@ -0,0 +1,660 @@
+"""
+This software was developed by the University of Tennessee as part of the
+Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+project funded by the US National Science Foundation. 
+
+See the license text in license.txt
+
+copyright 2009, University of Tennessee
+"""
+import wx
+import math
+
+try:
+    import OpenGL
+except:
+    import sys, os
+    sys.path.insert(1,os.path.dirname(sys.executable))
+
+import OpenGL.GL
+try:
+    from wx import glcanvas
+    haveGLCanvas = True
+except ImportError:
+    haveGLCanvas = False
+try:
+    # The Python OpenGL package can be found at
+    # http://PyOpenGL.sourceforge.net/
+    from OpenGL.GL import *
+    from OpenGL.GLU import *
+    from OpenGL.GLUT import *
+    haveOpenGL = True
+except ImportError:
+    haveOpenGL = False
+    
+# Color set
+DEFAULT_COLOR = [1.0, 1.0, 0.0, .2]    
+COLOR_RED     = [1.0, 0.0, 0.0, .2]
+COLOR_GREEN   = [0.0, 1.0, 0.0, .2]
+COLOR_BLUE    = [0.0, 0.0, 1.0, .2]
+COLOR_YELLOW  = [1.0, 1.0, 0.0, .2]
+COLOR_HIGHLIGHT = [.2, .2, .8, .5]
+
+# List of shapes
+SHAPE_LIST = []
+
+import ShapeParameters
+
+class SimPanel(wx.Panel):
+    """
+        3D viewer to support real-space simulation. 
+    """
+    window_name = "3dview"
+    window_caption = "3D viewer"
+    
+    def __init__(self, parent, id = -1, plots = None, standalone=False, **kwargs):
+        wx.Panel.__init__(self, parent, id = id, **kwargs)
+        self.parent = parent
+    
+        #Add a sizer
+        mainSizer = wx.BoxSizer(wx.VERTICAL)
+        sliderSizer = wx.BoxSizer(wx.HORIZONTAL)
+    
+        self.canvas    = CanvasBase(self) 
+        self.canvas.SetMinSize((200,2100))
+
+        #Add the model to it's sizer
+        mainSizer.Add(self.canvas, 1, wx.EXPAND)
+
+        #Add the Subsizers to mainsizer
+        mainSizer.Add(sliderSizer, 0, wx.EXPAND)
+
+        self.SetSizer(mainSizer)
+        self.Bind(wx.EVT_CONTEXT_MENU, self._on_context_menu)
+        
+ 
+    def _on_context_menu(self, event):
+        """
+            Default context menu for a plot panel
+        """
+        # Slicer plot popup menu
+        id = wx.NewId()
+        slicerpop = wx.Menu()
+        slicerpop.Append(id, '&Reset 3D View')
+        wx.EVT_MENU(self, id, self.canvas.resetView)
+       
+        pos = event.GetPosition()
+        pos = self.ScreenToClient(pos)
+        self.PopupMenu(slicerpop, pos)        
+    
+class CanvasBase(glcanvas.GLCanvas):
+    """
+        3D canvas to display OpenGL 3D shapes
+    """
+    window_name = "Simulation"
+    window_caption = "Simulation"
+    
+    def __init__(self, parent):
+        glcanvas.GLCanvas.__init__(self, parent, -1)
+        self.init = False
+        # initial mouse position
+        self.lastx = self.x = 30
+        self.lasty = self.y = 30
+        self.tr_lastx = self.tr_x = 30
+        self.tr_lasty = self.tr_y = 30
+        self.size = None
+        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
+        self.Bind(wx.EVT_SIZE, self.OnSize)
+        self.Bind(wx.EVT_PAINT, self.OnPaint)
+        self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
+        self.Bind(wx.EVT_RIGHT_DOWN, self.OnMouseDown)
+        self.Bind(wx.EVT_LEFT_UP, self.OnMouseUp)
+        self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
+        self.Bind(wx.EVT_MOTION, self.OnMouseMotion)
+        self.Bind(wx.EVT_MOUSEWHEEL, self._onMouseWheel)
+        
+        self.initialized = False
+        self.shapes = []
+        self.parent = parent
+        
+        # Reference vectors
+        self.x_vec = [1,0,0]
+        self.y_vec = [0,1,0]
+        
+        self.mouse_down = False
+        self.scale = 1.0
+        self.zoom  = 1.0
+        self.translation = [0,0,0]
+        
+        # Bind to Edit events
+        self.parent.Bind(ShapeParameters.EVT_EDIT_SHAPE, self._onEditShape)
+        
+    def resetView(self, evt=None):
+        """
+            Resets zooming, translation and rotation.
+        """
+        self.zoom = 1.0
+        self.scale = 1.0
+        self.x_vec = [1,0,0]
+        self.y_vec = [0,1,0]
+        glLoadIdentity()
+        self.Refresh()
+                
+    def _onEditShape(self, evt):
+        evt.Skip()
+        evt.shape.highlight(True)
+        for item in self.shapes:
+            if not item == evt.shape:
+                item.highlight(False)
+        self.Refresh(False)
+
+    def OnEraseBackground(self, event):
+        pass # Do nothing, to avoid flashing on MSW.
+
+    def OnSize(self, event):
+        size = self.size = self.GetClientSize()
+        if self.GetContext():
+            glViewport(0, 0, size.width, size.height)
+        event.Skip()
+
+    def OnPaint(self, event):     
+        size = self.GetClientSize()
+        side = size.width
+        if size.height<size.width:
+            side = size.height
+        
+        if self.GetContext():
+            glViewport(0, 0, side, side)
+            self.SetMinSize((side,side))
+            
+        dc = wx.PaintDC(self)
+        self.SetCurrent()
+        if not self.init:
+            self.InitGL()
+            self.init = True
+        self.OnDraw()
+        event.Skip()        
+        
+    def _onMouseWheel(self, evt):
+        # Initialize mouse position so we don't have unwanted rotation
+        self.x, self.y = self.lastx, self.lasty = evt.GetPosition()
+        self.tr_x, self.tr_y = self.tr_lastx, self.tr_lasty = evt.GetPosition()
+        
+        scale = 1.15
+        if evt.GetWheelRotation()<0:
+            scale = 1.0/scale
+             
+        self.zoom *= scale             
+             
+        glScale(scale, scale, scale)
+        self.Refresh(False)
+        
+    def OnMouseDown(self, evt):
+        self.SetFocus()
+        self.CaptureMouse()
+        self.x, self.y = self.lastx, self.lasty = evt.GetPosition()
+        self.tr_x, self.tr_y = self.tr_lastx, self.tr_lasty = self.x, self.y
+        self.mouse_down = True
+
+    def OnMouseUp(self, evt):
+        self.ReleaseMouse()
+        self.mouse_down = False
+
+    def OnRightUp(self, evt):
+        self.OnMouseUp(evt)
+        if self.x==self.tr_lastx and self.y==self.tr_lasty:
+            self._on_context_menu(evt)
+
+    def _on_context_menu(self, event):
+        """
+            Default context menu for a plot panel
+        """
+        id = wx.NewId()
+        slicerpop = wx.Menu()
+        slicerpop.Append(id, '&Reset 3D View')
+        wx.EVT_MENU(self, id, self.resetView)
+       
+        pos = event.GetPosition()
+        #pos = self.ScreenToClient(pos)
+        self.PopupMenu(slicerpop, pos)        
+
+    def OnMouseMotion(self, evt):
+        if evt.Dragging() and evt.LeftIsDown():
+            self.tr_lastx, self.tr_lasty = self.tr_x, self.tr_y
+            x, y = evt.GetPosition()
+            
+            # Min distance to do anything
+            if math.fabs(self.lastx-x)>10 or math.fabs(self.lasty-y)>10:
+                
+                self.lastx, self.lasty = self.x, self.y
+        
+                
+                if math.fabs(self.lastx-x)>math.fabs(self.lasty-y):
+                    self.x = x
+                else:
+                    self.y = y
+                
+                #self.x, self.y = evt.GetPosition()
+                self.Refresh(False)
+                
+        elif evt.Dragging() and evt.RightIsDown():
+            self.lastx, self.lasty = self.x, self.y
+            self.tr_lastx, self.tr_lasty = self.tr_x, self.tr_y
+            self.tr_x, self.tr_y = evt.GetPosition()
+            self.Refresh(False)
+            
+    def InitGL( self ):      
+        glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA)
+        #glShadeModel(GL_FLAT);
+        
+        glMatrixMode(GL_PROJECTION)
+        
+        glLight(GL_LIGHT0, GL_AMBIENT, [.2, .2, .2, 0])
+        
+        # Object color
+        #glLight(GL_LIGHT0, GL_DIFFUSE, COLOR_BLUE)
+        
+        glLight(GL_LIGHT0, GL_POSITION, [1.0, 1.0, -1.0, 0.0])
+        
+        
+        glLightModelfv(GL_LIGHT_MODEL_AMBIENT, [1, 1, 1, 0])
+        glEnable(GL_LIGHTING)
+        glEnable(GL_LIGHT0)
+        glEnable(GL_BLEND)
+        glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+        
+        glEnable ( GL_ALPHA_TEST ) ;
+        glAlphaFunc ( GL_GREATER, 0 ) ;
+        glPixelStorei(GL_PACK_ALIGNMENT, 1)
+        glPixelStorei(GL_UNPACK_ALIGNMENT, 1)
+        
+        glEnable(GL_NORMALIZE)
+        glDepthFunc(GL_LESS)
+        glEnable(GL_DEPTH_TEST)
+        glDepthMask(GL_TRUE);
+        glClearColor (1.0, 1.0, 1.0, 0.0);
+        #glClear (GL_COLOR_BUFFER_BIT);
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+        
+        
+        gluPerspective(0, 1.0, 0.1, 60.0);
+
+        glMatrixMode(GL_MODELVIEW)
+
+    def getMaxSize(self):
+        max_size = 0.0
+        # Ready to draw shapes
+        for item in self.shapes:
+            item.draw()
+            l = item.get_length()
+            if l>max_size:
+                max_size = l
+        return max_size
+
+    def OnDraw(self):
+        # clear color and depth buffers
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+        # use a fresh transformation matrix
+        
+        # get the max object size to re-scale
+        max_size = 1.0
+        # Ready to draw shapes
+        for item in self.shapes:
+            item.draw()
+            l = item.get_length()
+            if l>max_size:
+                max_size = l
+        
+        max_size *= 1.05
+        scale = self.scale/max_size
+        glScale(scale, scale, scale)
+        self.scale = max_size
+        
+        self.drawAxes()
+        
+        if self.mouse_down:
+            if math.fabs((self.x - self.lastx))>math.fabs((self.y - self.lasty)):
+                angle = 10.0*(self.x - self.lastx) / math.fabs(self.x - self.lastx) 
+                glRotate(angle, self.y_vec[0], self.y_vec[1], self.y_vec[2]);
+                self._rot_y(angle)
+            elif math.fabs(self.y - self.lasty)>0:
+                angle = 10.0*(self.y - self.lasty) / math.fabs(self.y - self.lasty) 
+                glRotate(angle, self.x_vec[0], self.x_vec[1], self.x_vec[2]);
+                self._rot_x(angle)
+
+            self.lasty = self.y
+            self.lastx = self.x
+        
+            w,h = self.GetVirtualSizeTuple()
+        
+            # Translate in the x-y plane
+            vx = self.x_vec[0] * 2.0*float(self.tr_x - self.tr_lastx)/w \
+               + self.y_vec[0] * 2.0*float(self.tr_lasty - self.tr_y)/h
+            vy = self.x_vec[1] * 2.0*float(self.tr_x - self.tr_lastx)/w \
+               + self.y_vec[1] * 2.0*float(self.tr_lasty - self.tr_y)/h
+            vz = self.x_vec[2] * 2.0*float(self.tr_x - self.tr_lastx)/w \
+               + self.y_vec[2] * 2.0*float(self.tr_lasty - self.tr_y)/h
+            
+            glTranslate(self.scale*vx/self.zoom, self.scale*vy/self.zoom, self.scale*vz/self.zoom)    
+                
+        # push into visible buffer
+        self.SwapBuffers()
+        
+    def _matrix_mult(self, v, axis, angle):
+        c = math.cos(angle)
+        s = math.sin(angle) 
+        x = axis[0]
+        y = axis[1]
+        z = axis[2]
+        vx = v[0]*(x*x*(1-c)+c)   + v[1]*(x*y*(1-c)-z*s) + v[2]*(x*z*(1-c)+y*s)
+        vy = v[0]*(y*x*(1-c)+z*s) + v[1]*(y*y*(1-c)+c)   + v[2]*(y*z*(1-c)-x*s)
+        vz = v[0]*(x*z*(1-c)-y*s) + v[1]*(y*z*(1-c)+x*s) + v[2]*(z*z*(1-c)+c)
+        return [vx, vy, vz]
+    
+    def _rot_y(self, theta):
+        """
+            Rotate the view by theta around the y-axis
+        """
+        angle = theta/180.0*math.pi 
+        axis = self.y_vec
+        self.x_vec = self._matrix_mult(self.x_vec, self.y_vec, -angle)
+        
+    def _rot_x(self, theta):
+        """
+            Rotate the view by theta around the x-axis
+        """
+        angle = theta/180.0*math.pi
+        self.y_vec = self._matrix_mult(self.y_vec, self.x_vec, -angle)
+        
+        
+    def addShape(self, shape, name=None):
+        """
+            Add a shape to the list of displayed shapes
+            @param shape: BaseShape object
+            @param name: name given to the shape
+        """
+        if not name==None:
+            shape.name = name
+        shape.params['order']=len(self.shapes)
+        self.shapes.append(shape)
+        self.Refresh(False)
+
+    def delShape(self, name):
+        """
+            Delete a shape by name
+            @param name: name of the shape to be deleted
+        """
+        for i in range(len(self.shapes)):
+            if self.shapes[i].name == name:
+                del self.shapes[i]
+                break
+        self.Refresh(False)
+
+    def getMaxVolume(self):
+        """
+            Returns the maximum volume of the combination of all shapes.
+            The maximum volume is the simple sum of the volumes of all the shapes.
+            @return: sum of the volumes of all the shapes [float]
+        """
+        sum = 0
+        for item in self.shapes:
+            sum += item.get_volume()
+        return sum
+
+    def drawAxes(self):
+        """ 
+            Draw 3D axes
+        """
+        pos = self.scale * 0.7
+       
+        # Z-axis is red
+        zaxis= Arrow(x=pos, y=-pos, z=0, r_cyl=self.scale*0.005, r_cone=self.scale*0.01, 
+                     l_cyl=self.scale*0.1, l_cone=self.scale*0.05)
+        zaxis.color = COLOR_RED
+        zaxis.draw()
+        
+        # Y-axis is yellow
+        yaxis= Arrow(x=pos, y=-pos, z=0, r_cyl=self.scale*0.005, r_cone=self.scale*0.01, 
+                     l_cyl=self.scale*0.1, l_cone=self.scale*0.05)
+        yaxis.color = COLOR_YELLOW
+        yaxis.rotate(-90,0,0)
+        yaxis.draw()
+        
+        # X-axis is green
+        xaxis= Arrow(x=pos, y=-pos, z=0, r_cyl=self.scale*0.005, r_cone=self.scale*0.01, 
+                     l_cyl=self.scale*0.1, l_cone=self.scale*0.05)
+        xaxis.color = COLOR_GREEN
+        xaxis.rotate(0,-90,0)
+        xaxis.draw()
+        
+        glLight(GL_LIGHT0, GL_DIFFUSE, DEFAULT_COLOR)
+
+
+class BaseShape:
+    """
+        Basic shape functionality
+    """
+    def __init__(self, x=0, y=0, z=0):
+        self.name = ''
+        ## Position
+        self.x = x
+        self.y = y
+        self.z = z
+        ## Orientation
+        self.theta_x = 0
+        self.theta_y = 0
+        self.theta_z = 0
+        
+        # Params
+        self.params = {}
+        self.params['contrast']  = 1.0
+        self.params['order']     = 0
+        self.details = {}
+        self.details['contrast'] = 'A-2'
+        self.details['order']    = ' '
+        
+        self.highlighted = False
+        self.color = DEFAULT_COLOR
+    
+    def get_volume(self):
+        return 0
+    
+    def get_length(self):
+        return 1.0
+    
+    def highlight(self, value=False):
+        self.highlighted = value
+        
+    def rotate(self, alpha, beta, gamma):
+        """ 
+            Set the rotation angles of the shape
+            @param alpha: angle around the x-axis [degrees]
+            @param beta: angle around the y-axis [degrees]
+            @param gamma: angle around the z-axis [degrees]
+        """
+        self.theta_x = alpha
+        self.theta_y = beta
+        self.theta_z = gamma
+        
+    def _rotate(self):
+        """
+            Perform the OpenGL rotation
+            
+            Note that the rotation order is reversed.
+            We do Y, X, Z do be compatible with simulation...
+        """
+        
+        glRotated(self.theta_z, 0, 0, 1)
+        glRotated(self.theta_x, 1, 0, 0)
+        glRotated(self.theta_y, 0, 1, 0)
+        
+    def _pre_draw(self):
+        if self.highlighted:
+            glLight(GL_LIGHT0, GL_DIFFUSE, COLOR_HIGHLIGHT)
+        else:
+            glLight(GL_LIGHT0, GL_DIFFUSE, self.color)
+
+
+class Arrow(BaseShape):
+    """
+        Arrow shape used to show the three axes
+    """
+    def __init__(self, x=0, y=0, z=0, r_cyl=0.01, r_cone=0.02, l_cyl=0.3, l_cone=0.1):
+        BaseShape.__init__(self, x, y, z)
+        self.r_cyl = r_cyl
+        self.r_cone = r_cone
+        self.l_cyl = l_cyl
+        self.l_cone = l_cone
+           
+    def draw(self):
+        self._pre_draw()
+        glPushMatrix()
+        glTranslate(self.x, self.y, self.z)
+        
+        # Perform rotation
+        glRotate(self.theta_x, 1, 0, 0)
+        glRotate(self.theta_y, 0, 1, 0)
+        glRotate(self.theta_z, 0, 0, 1)
+
+        # Draw axis cylinder
+        qobj = gluNewQuadric();
+        gluCylinder(qobj, self.r_cyl, self.r_cyl, self.l_cyl, 15, 5);
+                
+        glTranslate(0, 0, self.z+self.l_cyl)
+        
+        # Draw cone of the arrow
+        glutSolidCone(self.r_cone, self.l_cone, 30, 5)
+        
+        # Translate back to original position
+        glTranslate(-self.x, -self.y, -self.z)
+        glPopMatrix()
+
+class Cone(BaseShape):
+    """
+        Conical shape
+    """
+    def __init__(self, x=0, y=0, z=0, radius=0.5, height=1):
+        BaseShape.__init__(self, x, y, z)
+        self.radius = radius
+        self.height = height
+        
+    def draw(self):
+        glPushMatrix()
+        glTranslate(self.x, self.y, self.z)
+        glutSolidCone(self.radius, self.height, 30, 5)
+        self._rotate()
+        glTranslate(-self.x, -self.y, -self.z)
+        glPopMatrix()
+
+class Sphere(BaseShape):
+    """
+        Spherical shape
+    """
+    def __init__(self, x=0, y=0, z=0, radius=10.0):
+        BaseShape.__init__(self, x, y, z)
+        self.name = 'sphere'
+        self.params['radius'] = radius
+        self.details['radius'] = 'A'
+        
+    def get_volume(self):
+        return 4.0/3.0*math.pi*self.params['radius']*self.params['radius']*self.params['radius']
+        
+    def get_length(self):
+        return 2.0*self.params['radius']
+    
+    def draw(self):
+        self._pre_draw()
+        glPushMatrix()
+        glTranslate(self.x, self.y, self.z)
+        #glutSolidSphere(self.params['radius'], 30, 30)
+        qobj = gluNewQuadric();
+        #gluQuadricDrawStyle(qobj,GLU_SILHOUETTE)
+        gluSphere(qobj,self.params['radius'], 30, 30)
+        glTranslate(-self.x, -self.y, -self.z)
+        glPopMatrix()
+        glLight(GL_LIGHT0, GL_DIFFUSE, DEFAULT_COLOR)
+
+    def accept(self, visitor):
+        return visitor.fromSphere(self)
+
+    def accept_update(self, visitor):
+        return visitor.update_sphere(self)
+
+class Cylinder(BaseShape):
+    """
+        Cylinder shape, by default the cylinder is oriented along
+        the z-axis. 
+        
+        The reference point of the cylinder is the center of the
+        bottom circle.
+    """
+    def __init__(self, x=0, y=0, z=0, radius=10.0, length=100.0):
+        BaseShape.__init__(self, x, y, z)
+        self.name = 'cylinder'
+        self.params['radius'] = radius
+        self.params['length'] = length
+        self.details['radius'] = 'A'
+        self.details['length'] = 'A'
+        
+    def get_volume(self):
+        return math.pi*self.params['radius']*self.params['radius']*self.params['length']
+        
+    def get_length(self):
+        if self.params['length']>2.0*self.params['radius']:
+            return self.params['length']
+        else:
+            return 2.0*self.params['radius']
+        
+    def draw(self):
+        self._pre_draw()
+        glPushMatrix()
+        
+        glTranslate(self.x, self.y, self.z)
+        self._rotate() 
+        qobj = gluNewQuadric();
+        # gluCylinder(qobj, r_base, r_top, L, div around z, div along z)
+        gluCylinder(qobj, self.params['radius'], self.params['radius'], self.params['length'], 15, 5);
+
+        glTranslate(-self.x, -self.y, -self.z)
+        glPopMatrix()
+        glLight(GL_LIGHT0, GL_DIFFUSE, DEFAULT_COLOR)
+        
+    def accept(self, visitor):
+        return visitor.fromCylinder(self)
+        
+    def accept_update(self, visitor):
+        return visitor.update_cylinder(self)
+        
+# Fill the shape list
+SHAPE_LIST.append(dict(name='Sphere',   id=wx.NewId(), cl=Sphere))
+SHAPE_LIST.append(dict(name='Cylinder', id=wx.NewId(), cl=Cylinder))
+        
+def getShapes():
+    """
+        Returns a list of all available shapes
+    """
+    return SHAPE_LIST
+    
+def getShapeClass(id):
+    """
+        Returns a child class of BaseShape corresponding
+        to the supplied shape ID.
+        @param id: shape ID number
+    """
+    def f(s): return s['id']==id
+    if SHAPE_LIST is not None:
+        shape = filter(f, SHAPE_LIST)
+        return shape[0]['cl']
+    return None
+    
+def getShapeClassByName(name):
+    """
+        Returns a child class of BaseShape corresponding
+        to the supplied shape name.
+        @param name: shape name
+    """
+    def f(s): return s['name']==name
+    if SHAPE_LIST is not None:
+        shape = filter(f, SHAPE_LIST)
+        return shape[0]['cl']
+    return None
Index: simulationview/src/sans/perspectives/simulation/__init__.py
===================================================================
--- simulationview/src/sans/perspectives/simulation/__init__.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
+++ simulationview/src/sans/perspectives/simulation/__init__.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
@@ -0,0 +1,2 @@
+PLUGIN_ID = "Simulation plug-in 1.0"
+from simulation import *
Index: simulationview/src/sans/perspectives/simulation/requirements.txt
===================================================================
--- simulationview/src/sans/perspectives/simulation/requirements.txt	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
+++ simulationview/src/sans/perspectives/simulation/requirements.txt	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
@@ -0,0 +1,18 @@
+UI requirements
+- Allow user to modify q range from main panel [DONE]
+- Move list of shapes from top menu to central simulation panel
+- Popup P(r) plot and update for each simulation [DONE]
+- Allow user to choose whether he wants the simulation to be run automatically with each parameter change
+  or whether he wants to hit 'simulate' himself when he's done.
+- A shape should be highlighted when clicked
+
+
+Refactor:
+- simulation._refresh_3D_viewer should not access canvas directly
+- simulation._simulate_Iq: the getMaxVolume should be a call to the VolumeCanvas object.
+        # Since the VolumeCanvas doesn't currently have that functionality, and
+        # since the simulation panel holds the list of graphical representations
+        # for the shapes, we will take the information from there until VolumeCanvas 
+        # is updated.
+- In simulation.py: avoid interfacing directly with the canvas member of the plotPanel.
+- using time.time in CalcThread might not work on OS X 
Index: simulationview/src/sans/perspectives/simulation/simulation.py
===================================================================
--- simulationview/src/sans/perspectives/simulation/simulation.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
+++ simulationview/src/sans/perspectives/simulation/simulation.py	(revision b7b9c161f7b120968cc9b000ba7fa504b2b72216)
@@ -0,0 +1,327 @@
+"""
+This software was developed by the University of Tennessee as part of the
+Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+project funded by the US National Science Foundation. 
+
+See the license text in license.txt
+
+copyright 2009, University of Tennessee
+"""
+import wx
+import sys
+import os
+import numpy
+import time
+from copy import deepcopy
+import logging
+
+# Application imports
+import SimCanvas
+import ShapeParameters
+import ShapeAdapter
+from sans.guiframe.dataFitting import Data1D
+# Real-space simulation import
+import sans.realspace.VolumeCanvas as VolumeCanvas
+
+from data_util.calcthread import CalcThread
+from sans.guicomm.events import NewPlotEvent, StatusEvent    
+
+class Calc1D(CalcThread):
+    """
+        Thread object to simulate I(q)
+    """
+    
+    def __init__(self, x, model,
+                 completefn = None,
+                 updatefn   = None,
+                 yieldtime  = 0.01,
+                 worktime   = 0.01
+                 ):
+        CalcThread.__init__(self,completefn,
+                 updatefn,
+                 yieldtime,
+                 worktime)
+        self.x = x
+        self.model = model
+        self.starttime = 0
+        
+    def compute(self):
+        x = self.x
+        output = numpy.zeros(len(x))
+        error = numpy.zeros(len(x))
+        
+        self.starttime = time.time()
+        
+        for i_x in range(len(self.x)):
+            # Check whether we need to bail out
+            self.isquit()
+            self.update(output=output, error=error)
+            
+            value, err = self.model.getIqError(float(self.x[i_x]))
+            output[i_x] = value
+            error[i_x] = err
+            
+        elapsed = time.time()-self.starttime
+        self.complete(output=output, error=error, elapsed=elapsed)
+
+## Default minimum q-value for simulation output    
+DEFAULT_Q_MIN = 0.01
+## Default maximum q-value for simulation output
+DEFAULT_Q_MAX = 0.4
+## Default number of q point for simulation output
+DEFAULT_Q_NPTS = 10
+## Default number of real-space points per Angstrom cube
+DEFAULT_PT_DENSITY = 0.1
+   
+class Plugin:
+    """
+        Real-space simulation plug-in for guiframe
+    """
+    ## Minimum q-value for simulation output    
+    q_min = DEFAULT_Q_MIN
+    ## Maximum q-value for simulation output
+    q_max = DEFAULT_Q_MAX
+    ## Number of q point for simulation output
+    q_npts = DEFAULT_Q_NPTS    
+    
+    def __init__(self):
+        ## Plug-in name
+        self.sub_menu = "Simulation"
+        ## Reference to the parent window
+        self.parent = None
+        ## List of panels for the simulation perspective (names)
+        self.perspective = []
+        # Default save location
+        self._default_save_location = os.getcwd()
+        # Log startup
+        logging.info("Simulation plug-in started")
+        
+    def get_panels(self, parent):
+        """
+            Create and return a list of panel objects
+        """
+        self.parent = parent
+        
+        # 3D viewer
+        self.plotPanel  = SimCanvas.SimPanel(self.parent, -1, style=wx.RAISED_BORDER)
+
+        # Central simulation panel
+        self.paramPanel = ShapeParameters.ShapeParameterPanel(self.parent, 
+                                                              q_min = self.q_min,
+                                                              q_max = self.q_max,
+                                                              q_npts = self.q_npts,
+                                                              pt_density = DEFAULT_PT_DENSITY,
+                                                              style=wx.RAISED_BORDER)
+        
+        # Simulation
+        self.volCanvas = VolumeCanvas.VolumeCanvas()
+        self.volCanvas.setParam('lores_density', DEFAULT_PT_DENSITY)
+        self.adapter = ShapeAdapter.ShapeVisitor()
+        self._data_1D = None
+        self.calc_thread_1D = None
+        self.speedCheck = False
+        self.speed = 3.0e-7
+        
+        # Q-values for plotting simulated I(Q)
+        step = (self.q_max-self.q_min)/(self.q_npts-1)
+        self.x = numpy.arange(self.q_min, self.q_max+step*0.01, step)        
+        
+        # Set the list of panels that are part of the simulation perspective
+        self.perspective = []
+        self.perspective.append(self.plotPanel.window_name)
+        self.perspective.append(self.paramPanel.window_name)
+        
+        # Bind state events
+        self.parent.Bind(ShapeParameters.EVT_ADD_SHAPE, self._onAddShape)
+        self.parent.Bind(ShapeParameters.EVT_DEL_SHAPE, self._onDelShape)
+        self.parent.Bind(ShapeParameters.EVT_Q_RANGE, self._on_q_range_changed)
+        self.parent.Bind(ShapeParameters.EVT_PT_DENSITY, self._on_pt_density_changed)
+
+        return [self.plotPanel, self.paramPanel]
+
+    def _onAddShape(self, evt):
+        """
+            Process a user event to add a newly created 
+            or modified shape to the canvas
+        """
+        evt.Skip()
+        
+        # Give the new shape to the canvas
+        if evt.new:
+            shape = evt.shape.accept(self.adapter)
+            id = self.volCanvas.addObject(shape)
+            self.plotPanel.canvas.addShape(evt.shape, id)
+        else:
+            self.adapter.update(self.volCanvas, evt.shape)
+        
+        # Compute the simulated I(Q)
+        self._simulate_Iq()
+        
+        # Refresh the 3D viewer
+        self._refresh_3D_viewer()
+        
+    def _onDelShape(self, evt):
+        """
+            Remove a shape from the simulation
+        """
+        # Notify the simulation canvas
+        self.volCanvas.delete(evt.id)
+        # Notify the UI canvas
+        self.plotPanel.canvas.delShape(evt.id)
+        
+        # Compute the simulated I(Q)
+        self._simulate_Iq()
+        
+        # Refresh the 3D viewer
+        self._refresh_3D_viewer()   
+        
+    def _on_q_range_changed(self, evt):
+        """
+            Modify the Q range of the simulation output
+        """
+        if evt.q_min is not None:
+            self.q_min = evt.q_min
+        if evt.q_max is not None:
+            self.q_max = evt.q_max
+        if evt.npts is not None:
+            self.q_npts = evt.npts
+        
+        # Q-values for plotting simulated I(Q)
+        step = (self.q_max-self.q_min)/(self.q_npts-1)
+        self.x = numpy.arange(self.q_min, self.q_max+step*0.01, step)    
+         
+        # Compute the simulated I(Q)
+        self._simulate_Iq()
+        
+    def _on_pt_density_changed(self, evt):
+        """
+            Modify the Q range of the simulation output
+        """
+        if evt.npts is not None:
+            self.volCanvas.setParam('lores_density', evt.npts)
+         
+        # Compute the simulated I(Q)
+        self._simulate_Iq()
+        
+    def _simulate_Iq(self):
+        """
+            Simulate I(q) using the current VolumeCanvas object. 
+        """
+        # Check that the VolumeCanvas object exists
+        if not isinstance(self.volCanvas, VolumeCanvas.VolumeCanvas):
+            return
+        
+        # If a computation thread is running, stop it
+        if self.calc_thread_1D != None and self.calc_thread_1D.isrunning():
+            self.calc_thread_1D.stop()
+            
+        # Create a computation thread
+        self.calc_thread_1D = Calc1D(self.x, self.volCanvas, 
+                            completefn=self._simulation_completed_1D,
+                            updatefn=None)
+        self.calc_thread_1D.queue()
+        self.calc_thread_1D.ready(2.5)
+    
+        # Evaluate maximum number of points on the canvas and the 
+        # maximum computation time
+        
+        # TODO: the getMaxVolume should be a call to the VolumeCanvas object.
+        # Since the VolumeCanvas doesn't currently have that functionality, and
+        # since the simulation panel holds the list of graphical representations
+        # for the shapes, we will take the information from there until VolumeCanvas 
+        # is updated.
+        npts = self.plotPanel.canvas.getMaxVolume() * self.volCanvas.params['lores_density'] 
+        
+        est = self.speed * npts * npts
+        self.parent.SetStatusText("Calculation started: this might take a moment... [up to %d secs, %g points]" % (int(est), int(npts)))
+
+  
+    def _simulation_completed_1D(self, output, elapsed, error=None):
+        """
+            Called by the computation thread when the simulation is complete.
+            This method processes the simulation output, plots it, and updates
+            the simulation time estimate.
+            
+            @param output: simulated distribution I(q)
+            @param elapsed: simulation time, in seconds
+            @param error: standard deviation on the I(q) points
+        """
+        # Create the plotting event to pop up the I(Q) plot.
+        new_plot = Data1D(x=self.x, y=output, dy=error)
+        new_plot.name = "I(Q) Simulation"
+        new_plot.group_id = "simulation_output"
+        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
+        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Simulation I(Q)"))
+        
+        # Create the plotting event to pop up the P(r) plot.
+        r, pr = self.volCanvas.getPrData()
+        new_plot = Data1D(x=r, y=pr, dy=[0]*len(r))
+        new_plot.name = "P(r) Simulation"
+        new_plot.group_id = "simulated_pr"
+        new_plot.xaxis("\\rm{r}", 'A')
+        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}") 
+        
+        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Simulated P(r)"))        
+        
+        
+        # Notify the user of the simlation time and update the basic
+        # simulation estimate that will allow us to estimate simulation time
+        # next time we are asked to simulate.
+        msg = "Calculation completed in %g secs! [%g points]" % (elapsed, self.volCanvas.npts)
+        wx.PostEvent(self.parent, StatusEvent(status=msg))
+        if self.volCanvas.npts>0:
+            self.speed = elapsed/self.volCanvas.npts**2
+         
+    
+    def get_perspective(self):
+        """
+            Get the list of panel names for this perspective
+        """
+        return self.perspective
+    
+    def populate_menu(self, id, owner):
+        """
+            Create a menu for the plug-in
+        """
+        return []  
+    
+    def _change_point_density(self, point_density):
+        """
+            Placeholder for changing the simulation point density
+            TODO: refactor this away by writing a single update method for the simulation parameters
+        """
+        self.volCanvas.setParam('lores_density', point_density)
+    
+    def help(self, evt):
+        """
+            Provide help for the simulation
+        """
+        pass
+    
+    def on_perspective(self, event):
+        """
+            Call back function for the perspective menu item.
+            We notify the parent window that the perspective
+            has changed.
+        """
+        self.parent.set_perspective(self.perspective)
+    
+    def post_init(self):
+        """
+            Post initialization call back to close the loose ends
+            [Somehow openGL needs this call]
+        """
+        self.parent.set_perspective(self.perspective) 
+        self.parent._mgr.Update()
+
+    def _refresh_3D_viewer(self):
+        """
+            Refresh the 3D viewer window
+            #TODO: don't access data member directly
+        """
+        self.plotPanel.canvas.Refresh(False)
+        # Give focus back to 3D canvas so that the 
+        # zooming works
+        #self.plotPanel.canvas.SetFocus()
+        #self.plotPanel.SetFocus()
