Index: sasmodels/modelinfo.py
===================================================================
--- sasmodels/modelinfo.py	(revision 4f4d3e3574a18563fda8a9f5e84a9106aa7a1f1e)
+++ sasmodels/modelinfo.py	(revision 4f4d3e3574a18563fda8a9f5e84a9106aa7a1f1e)
@@ -164,5 +164,4 @@
     parameter.length = length
     parameter.length_control = control
-
     return parameter
 
@@ -265,7 +264,7 @@
     will have a magnitude and a direction, which may be different from
     other sld parameters. The volume parameters are used for calls
-    to form_volume within the kernel (required for volume normalization)
-    and for calls to ER and VR for effective radius and volume ratio
-    respectively.
+    to form_volume within the kernel (required for volume normalization),
+    to shell_volume (for hollow shapes), and to effective_radius (for
+    structure factor interactions) respectively.
 
     *description* is a short description of the parameter.  This will
@@ -424,4 +423,8 @@
       parameters counted as n individual parameters p1, p2, ...
 
+    * *common_parameters* is the list of common parameters, with a unique
+      copy for each model so that structure factors can have a default
+      background of 0.0.
+
     * *call_parameters* is the complete list of parameters to the kernel,
       including scale and background, with vector parameters recorded as
@@ -436,12 +439,14 @@
     parameters don't use vector notation, and instead use p1, p2, ...
     """
-    # scale and background are implicit parameters
-    COMMON = [Parameter(*p) for p in COMMON_PARAMETERS]
-
     def __init__(self, parameters):
         # type: (List[Parameter]) -> None
+
+        # scale and background are implicit parameters
+        # Need them to be unique to each model in case they have different
+        # properties, such as default=0.0 for structure factor backgrounds.
+        self.common_parameters = [Parameter(*p) for p in COMMON_PARAMETERS]
+
         self.kernel_parameters = parameters
         self._set_vector_lengths()
-
         self.npars = sum(p.length for p in self.kernel_parameters)
         self.nmagnetic = sum(p.length for p in self.kernel_parameters
@@ -450,5 +455,4 @@
         if self.nmagnetic:
             self.nvalues += 3 + 3*self.nmagnetic
-
         self.call_parameters = self._get_call_parameters()
         self.defaults = self._get_defaults()
@@ -490,4 +494,15 @@
                          if p.polydisperse and p.type not in ('orientation', 'magnetic'))
         self.pd_2d = set(p.name for p in self.call_parameters if p.polydisperse)
+
+    def set_zero_background(self):
+        """
+        Set the default background to zero for this model.  This is done for
+        structure factor models.
+        """
+        # type: () -> None
+        # Make sure background is the second common parameter.
+        assert self.common_parameters[1].id == "background"
+        self.common_parameters[1].default = 0.0
+        self.defaults = self._get_defaults()
 
     def check_angles(self):
@@ -589,5 +604,5 @@
     def _get_call_parameters(self):
         # type: () -> List[Parameter]
-        full_list = self.COMMON[:]
+        full_list = self.common_parameters[:]
         for p in self.kernel_parameters:
             if p.length == 1:
@@ -692,5 +707,5 @@
 
         # Gather the user parameters in order
-        result = control + self.COMMON
+        result = control + self.common_parameters
         for p in self.kernel_parameters:
             if not is2d and p.type in ('orientation', 'magnetic'):
@@ -741,5 +756,5 @@
 
 #: Set of variables defined in the model that might contain C code
-C_SYMBOLS = ['Imagnetic', 'Iq', 'Iqxy', 'Iqac', 'Iqabc', 'form_volume', 'c_code']
+C_SYMBOLS = ['Imagnetic', 'Iq', 'Iqxy', 'Iqac', 'Iqabc', 'form_volume', 'shell_volume', 'c_code']
 
 def _find_source_lines(model_info, kernel_module):
@@ -790,8 +805,22 @@
         # Custom sum/multi models
         return kernel_module.model_info
+
     info = ModelInfo()
+
+    # Build the parameter table
     #print("make parameter table", kernel_module.parameters)
     parameters = make_parameter_table(getattr(kernel_module, 'parameters', []))
+
+    # background defaults to zero for structure factor models
+    structure_factor = getattr(kernel_module, 'structure_factor', False)
+    if structure_factor:
+        parameters.set_zero_background()
+
+    # TODO: remove demo parameters
+    # The plots in the docs are generated from the model default values.
+    # Sascomp set parameters from the command line, and so doesn't need
+    # demo values for testing.
     demo = expand_pars(parameters, getattr(kernel_module, 'demo', None))
+
     filename = abspath(kernel_module.__file__).replace('.pyc', '.py')
     kernel_id = splitext(basename(filename))[0]
@@ -811,4 +840,7 @@
     info.category = getattr(kernel_module, 'category', None)
     info.structure_factor = getattr(kernel_module, 'structure_factor', False)
+    # TODO: find Fq by inspection
+    info.effective_radius_type = getattr(kernel_module, 'effective_radius_type', None)
+    info.have_Fq = getattr(kernel_module, 'have_Fq', False)
     info.profile_axes = getattr(kernel_module, 'profile_axes', ['x', 'y'])
     # Note: custom.load_custom_kernel_module assumes the C sources are defined
@@ -816,9 +848,9 @@
     info.source = getattr(kernel_module, 'source', [])
     info.c_code = getattr(kernel_module, 'c_code', None)
+    info.effective_radius = getattr(kernel_module, 'effective_radius', None)
     # TODO: check the structure of the tests
     info.tests = getattr(kernel_module, 'tests', [])
-    info.ER = getattr(kernel_module, 'ER', None) # type: ignore
-    info.VR = getattr(kernel_module, 'VR', None) # type: ignore
     info.form_volume = getattr(kernel_module, 'form_volume', None) # type: ignore
+    info.shell_volume = getattr(kernel_module, 'shell_volume', None) # type: ignore
     info.Iq = getattr(kernel_module, 'Iq', None) # type: ignore
     info.Iqxy = getattr(kernel_module, 'Iqxy', None) # type: ignore
@@ -844,5 +876,4 @@
     info.lineno = {}
     _find_source_lines(info, kernel_module)
-
     return info
 
@@ -925,38 +956,18 @@
     #: provided in the file.
     structure_factor = None # type: bool
+    #: True if the model defines an Fq function with signature
+    #: void Fq(double q, double *F1, double *F2, ...)
+    have_Fq = False
+    #: List of options for computing the effective radius of the shape,
+    #: or None if the model is not usable as a form factor model.
+    effective_radius_type = None   # type: List[str]
     #: List of C source files used to define the model.  The source files
     #: should define the *Iq* function, and possibly *Iqac* or *Iqabc* if the
     #: model defines orientation parameters. Files containing the most basic
     #: functions must appear first in the list, followed by the files that
-    #: use those functions.  Form factors are indicated by providing
-    #: an :attr:`ER` function.
+    #: use those functions.
     source = None           # type: List[str]
-    #: The set of tests that must pass.  The format of the tests is described
-    #: in :mod:`model_test`.
-    tests = None            # type: List[TestCondition]
-    #: Returns the effective radius of the model given its volume parameters.
-    #: The presence of *ER* indicates that the model is a form factor model
-    #: that may be used together with a structure factor to form an implicit
-    #: multiplication model.
-    #:
-    #: The parameters to the *ER* function must be marked with type *volume*.
-    #: in the parameter table.  They will appear in the same order as they
-    #: do in the table.  The values passed to *ER* will be vectors, with one
-    #: value for each polydispersity condition.  For example, if the model
-    #: is polydisperse over both length and radius, then both length and
-    #: radius will have the same number of values in the vector, with one
-    #: value for each *length X radius*.  If only *radius* is polydisperse,
-    #: then the value for *length* will be repeated once for each value of
-    #: *radius*.  The *ER* function should return one effective radius for
-    #: each parameter set.  Multiplicity parameters will be received as
-    #: arrays, with one row per polydispersity condition.
-    ER = None               # type: Optional[Callable[[np.ndarray], np.ndarray]]
-    #: Returns the occupied volume and the total volume for each parameter set.
-    #: See :attr:`ER` for details on the parameters.
-    VR = None               # type: Optional[Callable[[np.ndarray], Tuple[np.ndarray, np.ndarray]]]
-    #: Arbitrary C code containing supporting functions, etc., to be inserted
-    #: after everything in source.  This can include Iq and Iqxy functions with
-    #: the full function signature, including all parameters.
-    c_code = None
+    #: inline source code, added after all elements of source
+    c_code = None           # type: Optional[str]
     #: Returns the form volume for python-based models.  Form volume is needed
     #: for volume normalization in the polydispersity integral.  If no
@@ -966,4 +977,12 @@
     #: C code, either defined as a string, or in the sources.
     form_volume = None      # type: Union[None, str, Callable[[np.ndarray], float]]
+    #: Returns the shell volume for python-based models.  Form volume and
+    #: shell volume are needed for volume normalization in the polydispersity
+    #: integral and structure interactions for hollow shapes.  If no
+    #: parameters are *volume* parameters, then shell volume is not needed.
+    #: For C-based models, (with :attr:`sources` defined, or with :attr:`Iq`
+    #: defined using a string containing C code), shell_volume must also be
+    #: C code, either defined as a string, or in the sources.
+    shell_volume = None      # type: Union[None, str, Callable[[np.ndarray], float]]
     #: Returns *I(q, a, b, ...)* for parameters *a*, *b*, etc. defined
     #: by the parameter table.  *Iq* can be defined as a python function, or
@@ -1000,4 +1019,7 @@
     #: Line numbers for symbols defining C code
     lineno = None           # type: Dict[str, int]
+    #: The set of tests that must pass.  The format of the tests is described
+    #: in :mod:`model_test`.
+    tests = None            # type: List[TestCondition]
 
     def __init__(self):
