Index: sansrealspace/src/realspace/VolumeCanvas.py
===================================================================
--- sansrealspace/src/realspace/VolumeCanvas.py	(revision a2c1196ad8c06b1343cc4c9c510c9764628d9da2)
+++ sansrealspace/src/realspace/VolumeCanvas.py	(revision 883a2ef72c0562da97812328b76817fba21aeda1)
@@ -1,10 +1,30 @@
 #!/usr/bin/env python
 """ Volume Canvas
-    Simulation canvas
+
+    Simulation canvas for real-space simulation of SANS scattering intensity.
+    The user can create an arrangement of basic shapes and estimate I(q) and
+    I(q_x, q_y). Error estimates on the simulation are also available. 
+    
+    Example:
+    
+    import sans.realspace.VolumeCanvas as VolumeCanvas
+    canvas = VolumeCanvas.VolumeCanvas()
+    canvas.setParam('lores_density', 0.01)
+    
+    sphere = SphereDescriptor()
+    handle = canvas.addObject(sphere)
+
+    output, error = canvas.getIqError(q=0.1)
+    output, error = canvas.getIq2DError(0.1, 0.1)
+    
+    or alternatively:
+    iq = canvas.run(0.1)
+    i2_2D = canvas.run([0.1, 1.57])
+    
 """
 
 from sans.models.BaseComponent import BaseComponent
-from sansModeling.pointsmodelpy import pointsmodelpy
-from sansModeling.geoshapespy import geoshapespy
+from sans.simulation.pointsmodelpy import pointsmodelpy
+from sans.simulation.geoshapespy import geoshapespy
 
 import os.path, math
@@ -13,4 +33,10 @@
     """
         Class to hold the information about a shape
+        The descriptor holds a dictionary of parameters.
+        
+        Note: if shape parameters are accessed directly
+        from outside VolumeCanvas. The getPr method
+        should be called before evaluating I(q).
+                
     """
     def __init__(self):
@@ -48,4 +74,9 @@
     """
         Descriptor for a sphere
+        
+        The parameters are:
+            - radius [Angstroem] [default = 20 A]
+            - Contrast [A-2] [default = 1 A-2]
+            
     """
     def __init__(self):
@@ -56,5 +87,7 @@
         # Default parameters
         self.params["type"]   = "sphere"
+        # Radius of the sphere
         self.params["radius"] = 20.0
+        # Constrast parameter
         self.params["contrast"] = 1.0
 
@@ -74,4 +107,9 @@
         Descriptor for a cylinder
         Orientation: Default cylinder is along Y
+        
+        Parameters:
+            - Length [default = 40 A]
+            - Radius [default = 10 A]
+            - Contrast [default = 1 A-2]
     """
     def __init__(self):
@@ -82,6 +120,9 @@
         # Default parameters
         self.params["type"]   = "cylinder"
+        # Length of the cylinder
         self.params["length"] = 40.0
+        # Radius of the cylinder
         self.params["radius"] = 10.0
+        # Constrast parameter
         self.params["contrast"] = 1.0
         
@@ -101,4 +142,10 @@
     """
         Descriptor for an ellipsoid
+        
+        Parameters:
+            - Radius_x along the x-axis [default = 30 A]
+            - Radius_y along the y-axis [default = 20 A]
+            - Radius_z along the z-axis [default = 10 A]
+            - contrast [default = 1 A-2]
     """
     def __init__(self):
@@ -129,4 +176,11 @@
     """
         Descriptor for an helix
+        
+        Parameters:
+            -radius_helix: the radius of the helix [default = 10 A]
+            -radius_tube: radius of the "tube" that forms the helix [default = 3 A]
+            -pitch: distance between two consecutive turns of the helix [default = 34 A]
+            -turns: number of turns of the helix [default = 3]
+            -contrast: contrast parameter [default = 1 A-2]
     """
     def __init__(self):
@@ -158,4 +212,7 @@
     """
         Descriptor for a PDB set of points
+        
+        Parameter:
+            - file = name of the PDB file
     """
     def __init__(self, filename):
@@ -192,4 +249,8 @@
         Class representing an empty space volume to add 
         geometrical object to.
+        
+        For 1D I(q) simulation, getPr() is called internally for the
+        first call to getIq().
+        
     """
     
@@ -213,4 +274,12 @@
         self.npts = 0
         self.hasPr = False        
+        
+    def _model_changed(self):
+        """
+            Reset internal data members to reflect the fact that the 
+            real-space model has changed
+        """
+        self.hasPr  = False
+        self.points = None
         
     def addObject(self, shapeDesc, id = None):
@@ -234,5 +303,5 @@
 
         #model changed, need to recalculate P(r)
-        self.hasPr = False
+        self._model_changed()
 
         return id
@@ -247,4 +316,6 @@
             could be called. If multiple shapes are involved, then 
             simulation has to be performed.
+            
+            This function is deprecated, use addObject().
         
             @param shape: name of the object to add to the canvas [string]
@@ -265,15 +336,5 @@
             raise ValueError, "VolumeCanvas.add: Unknown shape %s" % shape
         
-        # Self the order number
-        shapeDesc.params['order'] = self.shapecount
-        # Store the shape in a dictionary entry associated
-        # with the handle
-        self.shapes[id] = shapeDesc
-        self.shapecount += 1
-
-        #model changed, need to recalculate P(r)
-        self.hasPr = False
-
-        return id
+        return self.addObject(shapeDesc, id)
 
     def delete(self, id):
@@ -289,9 +350,20 @@
 
         #model changed, need to recalculate P(r)
-        self.hasPr = False
+        self._model_changed()
 
 
     def setParam(self, name, value):    
         """
+            Function to set the value of a parameter. 
+            Both VolumeCanvas parameters and shape parameters
+            are accessible. 
+            
+            Note: if shape parameters are accessed directly
+            from outside VolumeCanvas. The getPr method
+            should be called before evaluating I(q).
+        
+            TODO: implemented a check method to protect
+            against that.
+        
             @param name: name of the parameter to change
             @param value: value to give the parameter
@@ -312,5 +384,5 @@
                     # The parameter was found, now change it
                     self.shapes[toks[0]].params[toks[1]] = value
-                    self.hasPr = False
+                    self._model_changed()
                 else:
                     raise ValueError, "Could not find parameter %s" % name
@@ -322,5 +394,5 @@
             # shape, see if the parameter is part of this object
             BaseComponent.setParam(self, name, value)
-            self.hasPr = False
+            self._model_changed()
 
     def getParam(self, name):    
@@ -412,5 +484,5 @@
         return self.shapes.keys()
 
-    def addSingleShape(self, shapeDesc):
+    def _addSingleShape(self, shapeDesc):
         """
             create shapeobject based on shapeDesc
@@ -425,5 +497,5 @@
                 shapeDesc.shapeObject, shapeDesc.params['contrast'])  
 
-    def createVolumeFromList(self):
+    def _createVolumeFromList(self):
         """
             Create a new lores model with all the shapes in our internal list
@@ -469,5 +541,5 @@
         for i in range(len_list-1, -1, -1):
             shapedesc = self.shapes[obj_list[i][1]]
-            self.addSingleShape(shapedesc)
+            self._addSingleShape(shapedesc)
 
         return 0     
@@ -475,5 +547,8 @@
     def getPr(self):
         """
-            Calculate P(r)
+            Calculate P(r) from the objects on the canvas.
+            This method should always be called after the shapes
+            on the VolumeCanvas have changed.
+            
             @return: calculation output flag 
         """
@@ -483,9 +558,9 @@
         # If there are not shapes, do nothing
         if len(self.shapes) == 0:
-            self.hasPr = False
+            self._model_changed()
             return 0
         
         # generate space filling points from shape list
-        self.createVolumeFromList()
+        self._createVolumeFromList()
 
         self.points = pointsmodelpy.new_point3dvec()
@@ -553,9 +628,14 @@
     def _create_modelObject(self):
         """
-            Returns simulate I(q) for given q_x and q_y values.
-            Also returns model object
-            @param qx: q_x [A-1]
-            @param qy: q_y [A-1]
-            @return: I(q) [cm-1], model object
+            Create the simulation model obejct from the list
+            of shapes.
+            
+            This method needs to be called each time a parameter
+            changes because of the way the underlying library
+            was (badly) written. It is impossible to change a 
+            parameter, or remove a shape without having to 
+            refill the space points.
+            
+            TODO: improve that.
         """
         # To find a complete example of the correct call order:
@@ -564,9 +644,9 @@
         # If there are not shapes, do nothing
         if len(self.shapes) == 0:
-            self.hasPr = False
+            self._model_changed()
             return 0
         
         # generate space filling points from shape list
-        self.createVolumeFromList()
+        self._createVolumeFromList()
 
         self.points = pointsmodelpy.new_point3dvec()
@@ -590,6 +670,15 @@
             @return: I(q) [cm-1]
         """
-        self._create_modelObject()
-                
+        
+        # If this is the first simulation call, we need to generate the
+        # space points
+        if self.points == None:
+            self._create_modelObject()
+            
+            # Protect against empty model
+            if self.points == None:
+                return 0
+               
+        # Evalute I(q) 
         norm =  1.0e8/self.params['lores_density']*self.params['scale']
         return norm*pointsmodelpy.get_complex_iq_2D(self.complex_model, self.points, qx, qy)\
@@ -671,5 +760,6 @@
             uncertainty.
             
-            @param q: q-value [float]
+            @param qx: qx-value [float]
+            @param qy: qy-value [float]
             @return: mean, error [float, float]
         """
Index: sansrealspace/src/realspace/__init__.py
===================================================================
--- sansrealspace/src/realspace/__init__.py	(revision 1141c5de42a96f026561fdd531c4ca0612cdebed)
+++ sansrealspace/src/realspace/__init__.py	(revision 883a2ef72c0562da97812328b76817fba21aeda1)
@@ -22,8 +22,5 @@
 #
 # \subsection depends External Dependencies
-# You have to have the DANSE distutils-adpt package installed:
-# \verbatime
-#$ svn co svn://danse.us/distutils-adpt
-# \endverbatim
+# None
 #
 # \subsection build Building the code
@@ -61,5 +58,6 @@
 # To add an object:
 # \verbatim
-#    handle = canvas.add('sphere')
+#    sphare = VolumeCanvas.SphereDescriptor()
+#    handle = canvas.addObject(sphere)
 #    canvas.setParam('%s.radius' % handle, 15.0)
 # \endverbatim
@@ -68,4 +66,5 @@
 # \verbatim
 #    output, error = canvas.getIqError(q=0.1)
+#    output, error = canvas.getIq2DError(qx=0.1, qy=0.1)
 # \endverbatim
 #
Index: sansrealspace/src/realspace/test/cylinder_d=0.5_Iq2D.txt
===================================================================
--- sansrealspace/src/realspace/test/cylinder_d=0.5_Iq2D.txt	(revision f980d4a81cb3e30b1036a193f676c8fcca7fdfb8)
+++ sansrealspace/src/realspace/test/cylinder_d=0.5_Iq2D.txt	(revision 883a2ef72c0562da97812328b76817fba21aeda1)
@@ -1,27 +1,27 @@
 PARS: {'scale': 1.0, 'length': 100, 'radius': 20, 'background': 0.0, 'cyl_theta': 1.5707963267948966, 'contrast': 1.0, 'cyl_phi': 1.5707963267948966}
 <q>  <ana>  <sim>  <err>
-0.001  1.25586e+013  1.25584e+013  5.25262e+008
-0.00929167  1.1909e+013  1.19058e+013  1.05064e+010
-0.0175833  1.0341e+013  1.03345e+013  3.12179e+010
-0.025875  8.16205e+012  8.14926e+012  5.17962e+010
-0.0341667  5.77306e+012  5.81045e+012  6.21145e+010
-0.0424583  3.56425e+012  3.55223e+012  5.90114e+010
-0.05075  1.82415e+012  1.80424e+012  4.48859e+010
-0.0590417  6.86403e+011  6.93632e+011  2.71769e+010
-0.0673333  1.26379e+011  1.2136e+011  1.06077e+010
-0.075625  1.69416e+009  1.50569e+009  1.08773e+009
-0.0839167  1.17674e+011  1.24732e+011  9.32509e+009
-0.0922083  2.93262e+011  2.7841e+011  1.38067e+010
-0.1005  4.06932e+011  4.09882e+011  1.72092e+010
-0.108792  4.1248e+011  4.37694e+011  1.84369e+010
-0.117083  3.26735e+011  3.07555e+011  1.59575e+010
-0.125375  2.00372e+011  1.98719e+011  1.30112e+010
-0.133667  8.63351e+010  8.31996e+010  8.33716e+009
-0.141958  1.76833e+010  1.4757e+010  3.47028e+009
-0.15025  2.29778e+008  5.88028e+008  6.83052e+008
-0.158542  1.83678e+010  1.6151e+010  3.56511e+009
-0.166833  4.78555e+010  5.04122e+010  6.33775e+009
-0.175125  6.83196e+010  7.36705e+010  7.69267e+009
-0.183417  7.03999e+010  7.70008e+010  7.86228e+009
-0.191708  5.61199e+010  5.68128e+010  6.74312e+009
-0.2  3.43293e+010  3.12432e+010  4.97778e+009
+0.001  1.25586e+013  1.25584e+013  5.24372e+008
+0.00929167  1.1909e+013  1.19119e+013  1.04128e+010
+0.0175833  1.0341e+013  1.03444e+013  3.11005e+010
+0.025875  8.16205e+012  8.19485e+012  5.13766e+010
+0.0341667  5.77306e+012  5.76297e+012  6.22366e+010
+0.0424583  3.56425e+012  3.54391e+012  5.90911e+010
+0.05075  1.82415e+012  1.8406e+012  4.53958e+010
+0.0590417  6.86403e+011  6.94445e+011  2.73011e+010
+0.0673333  1.26379e+011  1.26541e+011  1.08456e+010
+0.075625  1.69416e+009  1.95158e+009  1.23535e+009
+0.0839167  1.17674e+011  1.26397e+011  9.39118e+009
+0.0922083  2.93262e+011  2.85994e+011  1.40496e+010
+0.1005  4.06932e+011  4.13645e+011  1.72159e+010
+0.108792  4.1248e+011  4.28788e+011  1.82404e+010
+0.117083  3.26735e+011  3.25064e+011  1.63836e+010
+0.125375  2.00372e+011  1.90569e+011  1.27255e+010
+0.133667  8.63351e+010  8.94079e+010  8.63641e+009
+0.141958  1.76833e+010  1.23431e+010  3.16684e+009
+0.15025  2.29778e+008  1.37597e+008  3.31662e+008
+0.158542  1.83678e+010  2.03592e+010  4.03062e+009
+0.166833  4.78555e+010  4.76265e+010  6.15775e+009
+0.175125  6.83196e+010  6.19958e+010  7.01335e+009
+0.183417  7.03999e+010  7.12558e+010  7.51039e+009
+0.191708  5.61199e+010  5.79624e+010  6.79382e+009
+0.2  3.43293e+010  3.93938e+010  5.56512e+009
Index: sansrealspace/src/realspace/test/simulation_stimuli.py
===================================================================
--- sansrealspace/src/realspace/test/simulation_stimuli.py	(revision 8c050c1b7dcc94662d445dc0ff88c8414a021252)
+++ sansrealspace/src/realspace/test/simulation_stimuli.py	(revision 883a2ef72c0562da97812328b76817fba21aeda1)
@@ -46,5 +46,7 @@
     def setup(self):
         """Setup the canvas for stimuli application"""
-        return VolumeCanvas.VolumeCanvas()
+        volume = VolumeCanvas.VolumeCanvas()
+        volume.params['lores_density'] = 0.05
+        return volume
     
     def getRandomStimulus(self):
Index: sansrealspace/src/realspace/test/utest_oriented.py
===================================================================
--- sansrealspace/src/realspace/test/utest_oriented.py	(revision f980d4a81cb3e30b1036a193f676c8fcca7fdfb8)
+++ sansrealspace/src/realspace/test/utest_oriented.py	(revision 883a2ef72c0562da97812328b76817fba21aeda1)
@@ -434,5 +434,51 @@
             raise sys.exc_type, sys.exc_value
 
-          
+class TestParamChange(unittest.TestCase):
+    """ Tests for oriented (2D) systems """
+        
+    def setUp(self):
+        """ Set up cylinder model """
+        from sans.models.CylinderModel import CylinderModel
+        radius = 5
+        length = 40
+        density = 20
+    
+        # Analytical model
+        self.ana = CylinderModel()
+        self.ana.setParam('scale', 1.0)
+        self.ana.setParam('contrast', 1.0)
+        self.ana.setParam('background', 0.0)
+        self.ana.setParam('radius', radius)
+        self.ana.setParam('length', length)
+        self.ana.setParam('cyl_theta', math.pi/2.0)
+        self.ana.setParam('cyl_phi', math.pi/2.0)
+    
+        # Simulation model
+        self.model = VolumeCanvas.VolumeCanvas()
+        self.handle = self.model.add('cylinder')
+        self.model.setParam('lores_density', density)
+        self.model.setParam('%s.radius' % self.handle, radius)
+        self.model.setParam('%s.length' % self.handle, length)
+        self.model.setParam('scale' , 1.0)
+        self.model.setParam('%s.contrast' % self.handle, 1.0)
+        self.model.setParam('background' , 0.0)
+        self.model.setParam('%s.orientation' % self.handle, [0,0,0])
+    
+    def testalongY(self):
+        """ Test that a parameter change forces the generation
+            of new space points
+        """
+        ana_val = self.ana.runXY([0.1, 0.2])
+        sim_val = self.model.getIq2D(0.1, 0.2)
+        
+        self.assert_( math.fabs(sim_val/ana_val-1.0)<0.05 )
+        
+        # Change the radius a re-evaluate
+        self.ana.setParam('radius', 10)
+        self.model.setParam('%s.radius' % self.handle, 10)
+        
+        ana_val = self.ana.runXY([0.1, 0.2])
+        sim_val = self.model.getIq2D(0.1, 0.2)
+        self.assert_( math.fabs(sim_val/ana_val-1.0)<0.05 )
 
 
