Index: src/sas/sasgui/__init__.py
===================================================================
--- src/sas/sasgui/__init__.py	(revision c6bdb3b56ff2c1bf95baa3241ca1df10d74a79d7)
+++ src/sas/sasgui/__init__.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -1,85 +1,0 @@
-import sys
-import os
-from os.path import exists, expanduser, dirname, realpath, join as joinpath
-
-
-def dirn(path, n):
-    path = realpath(path)
-    for _ in range(n):
-        path = dirname(path)
-    return path
-
-# Set up config directories
-def make_user_folder():
-    path = joinpath(expanduser("~"),'.sasview')
-    if not exists(path):
-        os.mkdir(path)
-    return path
-
-
-def find_app_folder():
-    # We are starting out with the following info:
-    #     __file__ = .../sas/sasgui/__init__.pyc
-    # Check if the sister path .../sas/sasview exists, and use it as the
-    # app directory.  This will only be the case if the app is not frozen.
-    path = joinpath(dirn(__file__, 2), 'sasview')
-    if exists(path):
-        return path
-
-    # If we are running frozen, then root is a parent directory
-    if sys.platform == 'darwin':
-        # Here is the path to the file on the mac:
-        #     .../Sasview.app/Contents/Resources/lib/python2.7/site-packages.zip/sas/sasgui/__init__.pyc
-        # We want the path to the Resources directory.
-        path = dirn(__file__, 6)
-    elif os.name == 'nt':
-        # Here is the path to the file on windows:
-        #     ../Sasview/library.zip/sas/sasgui/__init__.pyc
-        # We want the path to the Sasview directory.
-        path = dirn(__file__, 4)
-    else:
-        raise RuntimeError("Couldn't find the app directory")
-    return path
-
-USER_FOLDER = make_user_folder()
-APP_FOLDER = find_app_folder()
-
-
-def get_app_dir():
-    return APP_FOLDER
-
-def get_user_dir():
-    return USER_FOLDER
-
-def get_custom_config_path():
-    dirname = os.path.join(get_user_dir(), 'config')
-    # If the directory doesn't exist, create it
-    if not os.path.exists(dirname):
-        os.makedirs(dirname)
-    path = os.path.join(dirname, "custom_config.py")
-    return path
-
-_config_cache = None
-def get_local_config():
-    global _config_cache
-    if not _config_cache:
-        _config_cache = _load_config()
-    return _config_cache
-
-def _load_config():
-    import os
-    import sys
-    import logging
-    from sasmodels.custom import load_module_from_path
-
-    logger = logging.getLogger(__name__)
-    dirname = get_app_dir()
-    filename = 'local_config.py'
-    path = os.path.join(dirname, filename)
-    try:
-        module = load_module_from_path('sas.sasgui.local_config', path)
-        logger.info("GuiManager loaded %s", path)
-        return module
-    except Exception as exc:
-        logger.critical("Error loading %s: %s", path, exc)
-        sys.exit()
Index: src/sas/sasgui/guiframe/CategoryInstaller.py
===================================================================
--- src/sas/sasgui/guiframe/CategoryInstaller.py	(revision 6e50a8d413a9ee39f4e97836486b9f343ad3f470)
+++ src/sas/sasgui/guiframe/CategoryInstaller.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -15,5 +15,5 @@
 from collections import defaultdict, OrderedDict
 
-from sas.sasgui import get_user_dir
+from sas import get_user_dir
 
 USER_FILE = 'categories.json'
@@ -27,4 +27,42 @@
     Note - class is entirely static!
     """
+
+    @staticmethod
+    def _get_installed_model_dir():
+        """
+        returns the dir where installed_models.txt should be
+        """
+        from sas.sascalc.dataloader.readers import get_data_path
+        return get_data_path()
+
+    @staticmethod
+    def _get_default_cat_file_dir():
+        """
+        returns the dir where default_cat.j should be
+        """
+        # The default categories file is usually found with the code, except
+        # when deploying using py2app (it will be in Contents/Resources), or
+        # py2exe (it will be in the exec dir).
+        import sas.sasview
+        cat_file = "default_categories.json"
+
+        possible_cat_file_paths = [
+            os.path.join(os.path.split(sas.sasview.__file__)[0], cat_file),           # Source
+            os.path.join(os.path.dirname(sys.executable), '..', 'Resources', cat_file), # Mac
+            os.path.join(os.path.dirname(sys.executable), cat_file)                     # Windows
+        ]
+
+        for path in possible_cat_file_paths:
+            if os.path.isfile(path):
+                return os.path.dirname(path)
+
+        raise RuntimeError('CategoryInstaller: Could not find folder containing default categories')
+
+    @staticmethod
+    def _get_home_dir():
+        """
+        returns the users sasview config dir
+        """
+        return os.path.join(os.path.expanduser("~"), ".sasview")
 
     @staticmethod
@@ -81,5 +119,5 @@
                which are user supplied.
         """
-        _model_dict = { model.name: model for model in model_list}
+        _model_dict = {model.name: model for model in model_list}
         _model_list = _model_dict.keys()
 
@@ -107,5 +145,5 @@
                         by_model_dict.pop(model_name)
                         model_enabled_dict.pop(model_name)
-                    except:
+                    except Exception:
                         logger.error("CategoryInstaller: %s", sys.exc_value)
                 else:
@@ -113,6 +151,8 @@
         if del_name or (len(add_list) > 0):
             for model in add_list:
-                model_enabled_dict[model]= True
-                if _model_dict[model].category is None or len(str(_model_dict[model].category.capitalize())) == 0:
+                model_enabled_dict[model] = True
+                # TODO: should be:  not _model_dict[model].category
+                if (_model_dict[model].category is None
+                        or len(str(_model_dict[model].category.capitalize())) == 0):
                     by_model_dict[model].append('Uncategorized')
                 else:
Index: src/sas/sasgui/guiframe/aboutbox.py
===================================================================
--- src/sas/sasgui/guiframe/aboutbox.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/guiframe/aboutbox.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -25,5 +25,5 @@
 import os
 
-from sas.sasgui import get_local_config
+from sas import get_local_config
 config = get_local_config()
 
Index: src/sas/sasgui/guiframe/acknowledgebox.py
===================================================================
--- src/sas/sasgui/guiframe/acknowledgebox.py	(revision 74c8cd08df1c5d3f23477827c0f272e88bbbea9d)
+++ src/sas/sasgui/guiframe/acknowledgebox.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -13,5 +13,5 @@
 from wx.lib.expando import ExpandoTextCtrl
 
-from sas.sasgui import get_local_config
+from sas import get_local_config
 config = get_local_config()
 
Index: src/sas/sasgui/guiframe/config.py
===================================================================
--- src/sas/sasgui/guiframe/config.py	(revision d9089328dec4c154a6bac2735e52b5e6fb3c8af2)
+++ src/sas/sasgui/guiframe/config.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -145,7 +145,4 @@
 UPDATE_TIMEOUT = 2
 
-#OpenCL option
-SAS_OPENCL = None
-
 # Time out for updating sasview
 UPDATE_TIMEOUT = 2
Index: src/sas/sasgui/guiframe/customdir.py
===================================================================
--- src/sas/sasgui/guiframe/customdir.py	(revision c6bdb3b56ff2c1bf95baa3241ca1df10d74a79d7)
+++ 	(revision )
@@ -1,59 +1,0 @@
-# Setup and find Custom config dir
-from __future__ import print_function
-
-import os.path
-import logging
-import shutil
-
-from sasmodels.custom import load_module_from_path
-
-from sas.sasgui import get_custom_config_path, get_app_dir
-
-logger = logging.getLogger(__name__)
-
-_config_cache = None
-def setup_custom_config():
-    """
-    Setup the custom config dir and cat file
-    """
-    global _config_cache
-    if not _config_cache:
-        _config_cache = _setup_custom_config()
-    return _config_cache
-
-
-def _setup_custom_config():
-    path = get_custom_config_path()
-    if not os.path.isfile(path):
-        try:
-            # if the custom config file does not exist, copy the default from
-            # the app dir
-            shutil.copyfile(os.path.join(get_app_dir(), "custom_config.py"),
-                            path)
-        except Exception:
-            logger.error("Could not copy default custom config.")
-
-    #Adding SAS_OPENCL if it doesn't exist in the config file
-    # - to support backcompability
-    if not "SAS_OPENCL" in open(path).read():
-        try:
-            open(config_file, "a+").write("SAS_OPENCL = \"None\"\n")
-        except Exception:
-            logger.error("Could not update custom config with SAS_OPENCL.")
-
-    custom_config = _load_config(path)
-    return custom_config
-
-
-def _load_config(path):
-    if os.path.exists(path):
-        try:
-            module = load_module_from_path('sas.sasview.custom_config', path)
-            logger.info("GuiManager loaded %s", path)
-            return module
-        except Exception as exc:
-            logger.error("Error loading %s: %s", path, exc)
-
-    from sas.sasview import custom_config
-    logger.info("GuiManager custom_config defaults to sas.sasview.custom_config")
-    return custom_config
Index: src/sas/sasgui/guiframe/data_panel.py
===================================================================
--- src/sas/sasgui/guiframe/data_panel.py	(revision c6bdb3b56ff2c1bf95baa3241ca1df10d74a79d7)
+++ src/sas/sasgui/guiframe/data_panel.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -33,5 +33,5 @@
 from sas.sasgui.guiframe.local_perspectives.plotting.SimplePlot \
     import PlotFrame as QucikPlotDialog
-from sas.sasgui import get_local_config
+from sas import get_local_config
 
 config = get_local_config()
Index: src/sas/sasgui/guiframe/documentation_window.py
===================================================================
--- src/sas/sasgui/guiframe/documentation_window.py	(revision 2746eab3d2fd36206508d585c8b2ed061fabce96)
+++ src/sas/sasgui/guiframe/documentation_window.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -27,5 +27,5 @@
     WX_SUPPORTS_HTML2 = False
 
-from sas.sasgui import get_app_dir
+from sas import get_app_dir
 
 # Don't use wx html renderer on windows.
Index: src/sas/sasgui/guiframe/gui_manager.py
===================================================================
--- src/sas/sasgui/guiframe/gui_manager.py	(revision 2f22db96dce134a5ac8f4dba0b22c4df370c5431)
+++ src/sas/sasgui/guiframe/gui_manager.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -28,5 +28,5 @@
 from matplotlib import _pylab_helpers
 
-from sas.sasgui import get_local_config, get_app_dir, get_user_dir
+from sas import get_local_config, get_custom_config, get_app_dir, get_user_dir
 from sas.sasgui.guiframe.events import EVT_CATEGORY
 from sas.sasgui.guiframe.events import EVT_STATUS
@@ -47,5 +47,4 @@
 from sas.sascalc.dataloader.loader import Loader
 from sas.sasgui.guiframe.proxy import Connection
-from sas.sasgui.guiframe.customdir import setup_custom_config
 
 logger = logging.getLogger(__name__)
@@ -53,5 +52,5 @@
 
 config = get_local_config()
-custom_config = setup_custom_config()
+custom_config = get_custom_config()
 
 # read some constants from config
@@ -63,5 +62,4 @@
 SPLASH_SCREEN_HEIGHT = config.SPLASH_SCREEN_HEIGHT
 SS_MAX_DISPLAY_TIME = config.SS_MAX_DISPLAY_TIME
-SAS_OPENCL = config.SAS_OPENCL
 if not WELCOME_PANEL_ON:
     WELCOME_PANEL_SHOW = False
@@ -138,8 +136,4 @@
         PARENT_FRAME = wx.Frame
         CHILD_FRAME = wx.Frame
-
-#Initiliaze enviromental variable with custom setting but only if variable not set
-if SAS_OPENCL and not "SAS_OPENCL" in os.environ:
-    os.environ["SAS_OPENCL"] = SAS_OPENCL
 
 class ViewerFrame(PARENT_FRAME):
Index: src/sas/sasgui/guiframe/local_perspectives/data_loader/data_loader.py
===================================================================
--- src/sas/sasgui/guiframe/local_perspectives/data_loader/data_loader.py	(revision dcb91cf83e474bdbe1c788d87690ca5f5e7841cb)
+++ src/sas/sasgui/guiframe/local_perspectives/data_loader/data_loader.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -10,8 +10,9 @@
 logger = logging.getLogger(__name__)
 
+from sas import get_local_config
+
 from sas.sascalc.dataloader.loader import Loader
 from sas.sascalc.dataloader.loader_exceptions import NoKnownLoaderException
 
-from sas.sasgui import get_local_config
 from sas.sasgui.guiframe.plugin_base import PluginBase
 from sas.sasgui.guiframe.events import StatusEvent
Index: src/sas/sasgui/guiframe/report_dialog.py
===================================================================
--- src/sas/sasgui/guiframe/report_dialog.py	(revision 463e7ffce130ae386f93cd18b87a1120743e88b5)
+++ src/sas/sasgui/guiframe/report_dialog.py	(revision 69a68979fe94b8f135a602e7f02b162e34616d53)
@@ -13,5 +13,5 @@
 if sys.platform == "win32":
     _STATICBOX_WIDTH = 450
-    PANEL_WIDTH = 500 
+    PANEL_WIDTH = 500
     PANEL_HEIGHT = 700
     FONT_VARIANT = 0
@@ -26,9 +26,9 @@
 
 class BaseReportDialog(wx.Dialog):
-    
+
     def __init__(self, report_list, *args, **kwds):
         """
         Initialization. The parameters added to Dialog are:
-        
+
         :param report_list: list of html_str, text_str, image for report
         """
@@ -48,7 +48,6 @@
         self.report_list = report_list
         # wild card
-        # pdf supporting only on MAC
-        if self.is_pdf:
-            self.wild_card = ' PDF files (*.pdf)|*.pdf|'
+        if self.is_pdf:  # pdf writer is available
+            self.wild_card = 'PDF files (*.pdf)|*.pdf|'
             self.index_offset = 0
         else:
@@ -63,5 +62,5 @@
         """
         hbox = wx.BoxSizer(wx.HORIZONTAL)
-        
+
         # buttons
         button_close = wx.Button(self, wx.ID_OK, "Close")
@@ -75,10 +74,10 @@
                           id=button_print.GetId())
         hbox.Add(button_print)
-        
+
         button_save = wx.Button(self, wx.NewId(), "Save")
         button_save.SetToolTipString("Save this report.")
         button_save.Bind(wx.EVT_BUTTON, self.onSave, id=button_save.GetId())
         hbox.Add(button_save)
-        
+
         # panel for report page
         vbox = wx.BoxSizer(wx.VERTICAL)
@@ -87,5 +86,5 @@
         # set the html page with the report string
         self.hwindow.SetPage(self.report_html)
-        
+
         # add panels to boxsizers
         vbox.Add(hbox)
@@ -103,5 +102,5 @@
         previewh = html.HtmlEasyPrinting(name="Printing", parentWindow=self)
         previewh.PreviewText(self.report_html)
-        
+
     def onPrint(self, event=None):
         """
@@ -118,9 +117,9 @@
         """
         self.Close()
-    
+
     def HTML2PDF(self, data, filename):
         """
         Create a PDF file from html source string.
-        Returns True is the file creation was successful. 
+        Returns True is the file creation was successful.
         : data: html string
         : filename: name of file to be saved
@@ -136,6 +135,5 @@
             self.Update()
             return pisaStatus.err
-        except:
+        except Exception:
             logger.error("Error creating pdf: %s" % sys.exc_value)
         return False
-
Index: src/sas/sasgui/guiframe/startup_configuration.py
===================================================================
--- src/sas/sasgui/guiframe/startup_configuration.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/guiframe/startup_configuration.py	(revision b963b2066d6a76fe5073000c5a596a6fd0130ac4)
@@ -13,5 +13,5 @@
 import wx
 
-from sas.sasgui import get_custom_config_path
+from sas import make_custom_config_path
 from sas.sasgui.guiframe.events import StatusEvent
 from sas.sasgui.guiframe.gui_style import GUIFRAME
@@ -194,5 +194,5 @@
         Write custom configuration
         """
-        path = get_custom_config_path()
+        path = make_custom_config_path()
         with open(path, 'w') as out_f:
             out_f.write("#Application appearance custom configuration\n")
Index: src/sas/sasgui/perspectives/calculator/model_editor.py
===================================================================
--- src/sas/sasgui/perspectives/calculator/model_editor.py	(revision 23359ccb520c33d89444d0732d752a3bc0226916)
+++ src/sas/sasgui/perspectives/calculator/model_editor.py	(revision 23359ccb520c33d89444d0732d752a3bc0226916)
@@ -31,10 +31,13 @@
 import re
 import logging
+import datetime
+
 from wx.py.editwindow import EditWindow
+
 from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
 from .pyconsole import show_model_output, check_model
 
 logger = logging.getLogger(__name__)
-
 
 if sys.platform.count("win32") > 0:
@@ -78,5 +81,5 @@
     a Modal Dialog.
 
-    :TODO the build in compiler currently balks at when it tries to import
+    :TODO the built in compiler currently balks at when it tries to import
     a model whose name contains spaces or symbols (such as + ... underscore
     should be fine).  Have fixed so the editor cannot save such a file name
@@ -335,5 +338,5 @@
             list_fnames = os.listdir(self.plugin_dir)
             # fake existing regular model name list
-            m_list = [model + ".py" for model in self.model_list]
+            m_list = [model.name + ".py" for model in self.model_list]
             list_fnames.append(m_list)
             if t_fname in list_fnames and title != mname:
@@ -533,5 +536,5 @@
             desc_line = "\nmodel_info.description = '{}'".format(description)
         name = os.path.splitext(os.path.basename(self.fname))[0]
-        output = SUM_TEMPLATE.format(name=name, model1=model1_name, 
+        output = SUM_TEMPLATE.format(name=name, model1=model1_name,
             model2=model2_name, operator=self._operator, desc_line=desc_line)
         with open(self.fname, 'w') as out_f:
@@ -789,5 +792,5 @@
                     exec "float(math.%s)" % item
                     self.math_combo.Append(str(item))
-                except:
+                except Exception:
                     self.math_combo.Append(str(item) + "()")
         self.math_combo.Bind(wx.EVT_COMBOBOX, self._on_math_select)
@@ -914,14 +917,7 @@
             msg = "Name exists already."
 
-        # Prepare the messagebox
+        #
         if self.base is not None and not msg:
             self.base.update_custom_combo()
-            # Passed exception in import test as it will fail for sasmodels.sasview_model class
-            # Should add similar test for new style?
-            Model = None
-            try:
-                exec "from %s import Model" % name
-            except:
-                logger.error(sys.exc_value)
 
         # Prepare the messagebox
@@ -956,111 +952,73 @@
         :param func_str: content of func; Strings
         """
-        try:
-            out_f = open(fname, 'w')
-        except:
-            raise
-        # Prepare the content of the function
-        lines = CUSTOM_TEMPLATE.split('\n')
-
-        has_scipy = func_str.count("scipy.")
-        if has_scipy:
-            lines.insert(0, 'import scipy')
-
-        # Think about 2D later
-        #self.is_2d = func_str.count("#self.ndim = 2")
-        #line_2d = ''
-        #if self.is_2d:
-        #    line_2d = CUSTOM_2D_TEMP.split('\n')
-
-        # Also think about test later
-        #line_test = TEST_TEMPLATE.split('\n')
-        #local_params = ''
-        #spaces = '        '#8spaces
-        spaces4  = ' '*4
-        spaces13 = ' '*13
-        spaces16 = ' '*16
+        out_f = open(fname, 'w')
+
+        out_f.write(CUSTOM_TEMPLATE % {
+            'name': name,
+            'title': 'User model for ' + name,
+            'description': desc_str,
+            'date': datetime.datetime.now().strftime('%YYYY-%mm-%dd'),
+        })
+
+        # Write out parameters
         param_names = []    # to store parameter names
-        has_scipy = func_str.count("scipy.")
-        if has_scipy:
-            lines.insert(0, 'import scipy')
-
-        # write function here
-        for line in lines:
-            # The location where to put the strings is
-            # hard-coded in the template as shown below.
-            out_f.write(line + '\n')
-            if line.count('#name'):
-                out_f.write('name = "%s" \n' % name)
-            elif line.count('#title'):
-                out_f.write('title = "User model for %s"\n' % name)
-            elif line.count('#description'):
-                out_f.write('description = "%s"\n' % desc_str)
-            elif line.count('#parameters'):
-                out_f.write('parameters = [ \n')
-                for param_line in param_str.split('\n'):
-                    p_line = param_line.lstrip().rstrip()
-                    if p_line:
-                        pname, pvalue, desc = self.get_param_helper(p_line)
-                        param_names.append(pname)
-                        out_f.write("%s['%s', '', %s, [-numpy.inf, numpy.inf], '', '%s'],\n" % (spaces16, pname, pvalue, desc))
-                for param_line in pd_param_str.split('\n'):
-                    p_line = param_line.lstrip().rstrip()
-                    if p_line:
-                        pname, pvalue, desc = self.get_param_helper(p_line)
-                        param_names.append(pname)
-                        out_f.write("%s['%s', '', %s, [-numpy.inf, numpy.inf], 'volume', '%s'],\n" % (spaces16, pname, pvalue, desc))
-                out_f.write('%s]\n' % spaces13)
-
-        # No form_volume or ER available in simple model editor
-        out_f.write('def form_volume(*arg): \n')
-        out_f.write('    return 1.0 \n')
-        out_f.write('\n')
-        out_f.write('def ER(*arg): \n')
-        out_f.write('    return 1.0 \n')
-
-        # function to compute
-        out_f.write('\n')
-        out_f.write('def Iq(x ')
-        for name in param_names:
-            out_f.write(', %s' % name)
-        out_f.write('):\n')
+        pd_params = []
+        out_f.write('parameters = [ \n')
+        out_f.write('#   ["name", "units", default, [lower, upper], "type", "description"],\n')
+        for pname, pvalue, desc in self.get_param_helper(param_str):
+            param_names.append(pname)
+            out_f.write("    ['%s', '', %s, [-inf, inf], '', '%s'],\n"
+                        % (pname, pvalue, desc))
+        for pname, pvalue, desc in self.get_param_helper(pd_param_str):
+            param_names.append(pname)
+            pd_params.append(pname)
+            out_f.write("    ['%s', '', %s, [-inf, inf], 'volume', '%s'],\n"
+                        % (pname, pvalue, desc))
+        out_f.write('    ]\n')
+
+        # Write out function definition
+        out_f.write('def Iq(%s):\n' % ', '.join(['x'] + param_names))
+        out_f.write('    """Absolute scattering"""\n')
+        if "scipy." in func_str:
+            out_f.write('    import scipy')
+        if "numpy." in func_str:
+            out_f.write('    import numpy')
+        if "np." in func_str:
+            out_f.write('    import numpy as np')
         for func_line in func_str.split('\n'):
             out_f.write('%s%s\n' % (spaces4, func_line))
-
-        Iqxy_string = 'return Iq(numpy.sqrt(x**2+y**2) '
-
+        out_f.write('## uncomment the following if Iq works for vector x\n')
+        out_f.write('#Iq.vectorized = True\n')
+
+        # If polydisperse, create place holders for form_volume, ER and VR
+        if pd_params:
+            out_f.write('\n')
+            out_f.write(CUSTOM_TEMPLATE_PD % {'args': ', '.join(pd_params)})
+
+        # Create place holder for Iqxy
         out_f.write('\n')
-        out_f.write('def Iqxy(x, y ')
-        for name in param_names:
-            out_f.write(', %s' % name)
-            Iqxy_string += ', ' + name
-        out_f.write('):\n')
-        Iqxy_string += ')'
-        out_f.write('%s%s\n' % (spaces4, Iqxy_string))
+        out_f.write('#def Iqxy(%s):\n' % ', '.join(["x", "y"] + param_names))
+        out_f.write('#    """Absolute scattering of oriented particles."""\n')
+        out_f.write('#    ...\n')
+        out_f.write('#    return oriented_form(x, y, args)\n')
+        out_f.write('## uncomment the following if Iqxy works for vector x, y\n')
+        out_f.write('#Iqxy.vectorized = True\n')
 
         out_f.close()
 
-    def get_param_helper(self, line):
-        """
-        Get string in line to define the params dictionary
-
-        :param line: one line of string got from the param_str
-        """
-        items = line.split(";")
-        for item in items:
-            name = item.split("=")[0].strip()
-            description = ""
-            try:
-                value = item.split("=")[1].strip()
-                if value.count("#"):
-                    # If line ends in a comment, remove it before parsing float
-                    index = value.index("#")
-                    description = value[(index + 1):].strip()
-                    value = value[:value.index("#")].strip()
-                float(value)
-            except ValueError:
-                value = 1.0 # default
-
-        return name, value, description
+    def get_param_helper(self, param_str):
+        """
+        yield a sequence of name, value pairs for the parameters in param_str
+
+        Parameters can be defined by one per line by name=value, or multiple
+        on the same line by separating the pairs by semicolon or comma.  The
+        value is optional and defaults to "1.0".
+        """
+        for line in param_str.replace(';', ',').split('\n'):
+            for item in line.split(','):
+                defn, desc = item.split('#', 1) if '#' in item else (item, '')
+                name, value = defn.split('=', 1) if '=' in defn else (defn, '1.0')
+                if name:
+                    yield [v.strip() for v in (name, value, desc)]
 
     def set_function_helper(self, line):
@@ -1096,6 +1054,6 @@
         running "file:///...."
 
-    :param evt: Triggers on clicking the help button
-    """
+        :param evt: Triggers on clicking the help button
+        """
 
         _TreeLocation = "user/sasgui/perspectives/fitting/fitting_help.html"
@@ -1140,74 +1098,58 @@
 ## Templates for plugin models
 
-CUSTOM_TEMPLATE = """
+CUSTOM_TEMPLATE = '''\
+r"""
+Definition
+----------
+
+Calculates %(name)s.
+
+%(description)s
+
+References
+----------
+
+Authorship and Verification
+---------------------------
+
+* **Author:** --- **Date:** %(date)s
+* **Last Modified by:** --- **Date:** %(date)s
+* **Last Reviewed by:** --- **Date:** %(date)s
+"""
+
 from math import *
-import os
-import sys
-import numpy
-
-#name
-
-#title
-
-#description
-
-#parameters
-
-"""
-
-CUSTOM_2D_TEMP = """
-    def run(self, x=0.0, y=0.0):
-        if x.__class__.__name__ == 'list':
-            x_val = x[0]
-            y_val = y[0]*0.0
-            return self.function(x_val, y_val)
-        elif x.__class__.__name__ == 'tuple':
-            msg = "Tuples are not allowed as input to BaseComponent models"
-            raise ValueError, msg
-        else:
-            return self.function(x, 0.0)
-    def runXY(self, x=0.0, y=0.0):
-        if x.__class__.__name__ == 'list':
-            return self.function(x, y)
-        elif x.__class__.__name__ == 'tuple':
-            msg = "Tuples are not allowed as input to BaseComponent models"
-            raise ValueError, msg
-        else:
-            return self.function(x, y)
-    def evalDistribution(self, qdist):
-        if qdist.__class__.__name__ == 'list':
-            msg = "evalDistribution expects a list of 2 ndarrays"
-            if len(qdist)!=2:
-                raise RuntimeError, msg
-            if qdist[0].__class__.__name__ != 'ndarray':
-                raise RuntimeError, msg
-            if qdist[1].__class__.__name__ != 'ndarray':
-                raise RuntimeError, msg
-            v_model = numpy.vectorize(self.runXY, otypes=[float])
-            iq_array = v_model(qdist[0], qdist[1])
-            return iq_array
-        elif qdist.__class__.__name__ == 'ndarray':
-            v_model = numpy.vectorize(self.runXY, otypes=[float])
-            iq_array = v_model(qdist)
-            return iq_array
-"""
-TEST_TEMPLATE = """
-######################################################################
-## THIS IS FOR TEST. DO NOT MODIFY THE FOLLOWING LINES!!!!!!!!!!!!!!!!
-if __name__ == "__main__":
-    m= Model()
-    out1 = m.runXY(0.0)
-    out2 = m.runXY(0.01)
-    isfine1 = numpy.isfinite(out1)
-    isfine2 = numpy.isfinite(out2)
-    print "Testing the value at Q = 0.0:"
-    print out1, " : finite? ", isfine1
-    print "Testing the value at Q = 0.01:"
-    print out2, " : finite? ", isfine2
-    if isfine1 and isfine2:
-        print "===> Simple Test: Passed!"
-    else:
-        print "===> Simple Test: Failed!"
-"""
+from numpy import inf
+
+name = "%(name)s"
+title = "%(title)s"
+description = """%(description)s"""
+
+'''
+
+CUSTOM_TEMPLATE_PD = '''\
+def form_volume(%(args)s):
+    """
+    Volume of the particles used to compute absolute scattering intensity
+    and to weight polydisperse parameter contributions.
+    """
+    return 0.0
+
+def ER(%(args)s):
+    """
+    Effective radius of particles to be used when computing structure factors.
+
+    Input parameters are vectors ranging over the mesh of polydispersity values.
+    """
+    return 0.0
+
+def VR(%(args)s):
+    """
+    Volume ratio of particles to be used when computing structure factors.
+
+    Input parameters are vectors ranging over the mesh of polydispersity values.
+    """
+    return 1.0
+'''
+
 SUM_TEMPLATE = """
 from sasmodels.core import load_model_info
@@ -1219,15 +1161,7 @@
 """
 if __name__ == "__main__":
-#    app = wx.PySimpleApp()
     main_app = wx.App()
     main_frame = TextDialog(id=1, model_list=["SphereModel", "CylinderModel"],
-                       plugin_dir='../fitting/plugin_models')
+                            plugin_dir='../fitting/plugin_models')
     main_frame.ShowModal()
     main_app.MainLoop()
-
-#if __name__ == "__main__":
-#    from sas.sasgui.perspectives.fitting import models
-#    dir_path = models.find_plugins_dir()
-#    app = wx.App()
-#    window = EditorWindow(parent=None, base=None, path=dir_path, title="Editor")
-#    app.MainLoop()
Index: src/sas/sasgui/perspectives/fitting/basepage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/basepage.py	(revision 33dc18f1612814b89c60662f4f734c0d8831ac72)
+++ src/sas/sasgui/perspectives/fitting/basepage.py	(revision 33dc18f1612814b89c60662f4f734c0d8831ac72)
@@ -6,6 +6,4 @@
 import sys
 import os
-import wx
-import numpy as np
 import time
 import copy
@@ -14,11 +12,21 @@
 import logging
 import traceback
-
 from Queue import Queue
 from threading import Thread
 from collections import defaultdict
+
+import numpy as np
+
+import wx
 from wx.lib.scrolledpanel import ScrolledPanel
 
+from sasmodels.sasview_model import MultiplicationModel
 from sasmodels.weights import MODELS as POLYDISPERSITY_MODELS
+from sasmodels.weights import GaussianDispersion
+
+from sas.sascalc.dataloader.data_info import Detector
+from sas.sascalc.dataloader.data_info import Source
+from sas.sascalc.fit.pagestate import PageState
+from sas.sascalc.fit.models import PLUGIN_NAME_BASE
 
 from sas.sasgui.guiframe.panel_base import PanelBase
@@ -32,9 +40,9 @@
 from sas.sasgui.guiframe.dataFitting import check_data_validity
 from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
-from sas.sascalc.dataloader.data_info import Detector
-from sas.sascalc.dataloader.data_info import Source
-from sas.sasgui.perspectives.fitting.pagestate import PageState
 from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
 from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
+from .report_dialog import ReportDialog
+from .utils import get_weight
 
 logger = logging.getLogger(__name__)
@@ -155,5 +163,5 @@
         self.disp_cb_dict = {}
 
-        # self.state = PageState(parent=parent)
+        # self.state = PageState()
         # dictionary containing list of models
         self.model_list_box = {}
@@ -202,6 +210,5 @@
         self.fitrange = True
         # Create memento to save the current state
-        self.state = PageState(parent=self.parent,
-                               model=self.model, data=self.data)
+        self.state = PageState(model=self.model, data=self.data)
         # flag to determine if state has change
         self.state_change = False
@@ -295,5 +302,5 @@
         """
         x = np.linspace(start=self.qmin_x, stop=self.qmax_x,
-                           num=self.npts_x, endpoint=True)
+                        num=self.npts_x, endpoint=True)
         self.data = Data1D(x=x)
         self.data.xaxis('\\rm{Q}', "A^{-1}")
@@ -321,5 +328,5 @@
 
         x = np.logspace(start=qmin, stop=qmax,
-                           num=self.npts_x, endpoint=True, base=10.0)
+                        num=self.npts_x, endpoint=True, base=10.0)
         self.data = Data1D(x=x)
         self.data.xaxis('\\rm{Q}', "A^{-1}")
@@ -510,22 +517,20 @@
         self.state.manager = manager
 
-    def populate_box(self, model_dict):
+    def populate_box(self, model_list_box):
         """
         Store list of model
 
-        :param model_dict: dictionary containing list of models
-
-        """
-        self.model_list_box = model_dict
-        self.state.model_list_box = self.model_list_box
+        :param model_list_box: dictionary containing categorized models
+        """
+        self.model_list_box = model_list_box
         self.initialize_combox()
 
-    def set_model_dictionary(self, model_dict):
+    def set_model_dictionary(self, model_dictionary):
         """
         Store a dictionary linking model name -> model object
 
-        :param model_dict: dictionary containing list of models
-        """
-        self.model_dict = model_dict
+        :param model_dictionary: dictionary containing all models
+        """
+        self.model_dictionary = model_dictionary
 
     def initialize_combox(self):
@@ -533,5 +538,5 @@
         put default value in the combo box
         """
-        if self.model_list_box is not None and len(self.model_list_box) > 0:
+        if self.model_list_box:
             self._populate_box(self.structurebox,
                                self.model_list_box["Structure Factors"])
@@ -632,6 +637,50 @@
         # Get plot image from plotpanel
         images, canvases = self.get_images()
-        # get the report dialog
-        self.state.report(images, canvases)
+        imgRAM, images, refs = self._build_plots_for_report(images, canvases)
+
+        # get the strings for report
+        report_str, text_str = self.state.report(fig_urls=refs)
+
+        # Show the dialog
+        report_list = [report_str, text_str, images]
+        dialog = ReportDialog(report_list, None, wx.ID_ANY, "")
+        dialog.Show()
+
+    def _build_plots_for_report(self, figs, canvases):
+        """
+        Build image state that wx.html understand
+        by plotting, putting it into wx.FileSystem image object
+        """
+        images = []
+        refs = []
+
+        # For no figures in the list, prepare empty plot
+        if figs is None or len(figs) == 0:
+            figs = [None]
+
+        # Loop over the list of figures
+        # use wx.MemoryFSHandler
+        imgRAM = wx.MemoryFSHandler()
+        for fig in figs:
+            if fig is not None:
+                ind = figs.index(fig)
+                canvas = canvases[ind]
+
+            # store the image in wx.FileSystem Object
+            wx.FileSystem.AddHandler(wx.MemoryFSHandler())
+
+            # index of the fig
+            ind = figs.index(fig)
+
+            # AddFile, image can be retrieved with 'memory:filename'
+            name = 'img_fit%s.png' % ind
+            refs.append('memory:' + name)
+            imgRAM.AddFile(name, canvas.bitmap, wx.BITMAP_TYPE_PNG)
+
+            # append figs
+            images.append(fig)
+
+        return imgRAM, images, refs
+
 
     def on_save(self, event):
@@ -855,5 +904,4 @@
             self.state.disable_disp = self.disable_disp.GetValue()
 
-        self.state.smearer = copy.deepcopy(self.current_smearer)
         if hasattr(self, "enable_smearer"):
             self.state.enable_smearer = \
@@ -871,5 +919,5 @@
         if len(self._disp_obj_dict) > 0:
             for k, v in self._disp_obj_dict.iteritems():
-                self.state._disp_obj_dict[k] = v.type
+                self.state.disp_obj_dict[k] = v.type
 
             self.state.values = copy.deepcopy(self.values)
@@ -889,5 +937,5 @@
                                     self.state.str_parameters)
         self._copy_parameters_state(self.orientation_params,
-                                     self.state.orientation_params)
+                                    self.state.orientation_params)
         self._copy_parameters_state(self.orientation_params_disp,
                                     self.state.orientation_params_disp)
@@ -920,5 +968,4 @@
             self.state.disable_disp = self.disable_disp.GetValue()
 
-        self.state.smearer = copy.deepcopy(self.current_smearer)
         if hasattr(self, "enable_smearer"):
             self.state.enable_smearer = \
@@ -944,9 +991,9 @@
                         try:
                             self.state.disp_cb_dict[k] = v.GetValue()
-                        except:
+                        except Exception:
                             self.state.disp_cb_dict[k] = None
             if len(self._disp_obj_dict) > 0:
                 for k, v in self._disp_obj_dict.iteritems():
-                    self.state._disp_obj_dict[k] = v.type
+                    self.state.disp_obj_dict[k] = v.type
 
             self.state.values = copy.deepcopy(self.values)
@@ -990,5 +1037,5 @@
             # to support older version
             category_pos = int(state.categorycombobox)
-        except:
+        except Exception:
             category_pos = 0
             for ind_cat in range(self.categorybox.GetCount()):
@@ -1002,5 +1049,5 @@
             # to support older version
             formfactor_pos = int(state.formfactorcombobox)
-        except:
+        except Exception:
             formfactor_pos = 0
             for ind_form in range(self.formfactorbox.GetCount()):
@@ -1015,5 +1062,5 @@
             # to support older version
             structfactor_pos = int(state.structurecombobox)
-        except:
+        except Exception:
             structfactor_pos = 0
             for ind_struct in range(self.structurebox.GetCount()):
@@ -1182,13 +1229,12 @@
         self.categorybox.Select(category_pos)
         self._show_combox(None)
-        from models import PLUGIN_NAME_BASE
-        if self.categorybox.GetValue() == CUSTOM_MODEL \
-                and PLUGIN_NAME_BASE not in state.formfactorcombobox:
+        if (self.categorybox.GetValue() == CUSTOM_MODEL
+                and PLUGIN_NAME_BASE not in state.formfactorcombobox):
             state.formfactorcombobox = \
                 PLUGIN_NAME_BASE + state.formfactorcombobox
         formfactor_pos = 0
         for ind_form in range(self.formfactorbox.GetCount()):
-            if self.formfactorbox.GetString(ind_form) == \
-                                                (state.formfactorcombobox):
+            if (self.formfactorbox.GetString(ind_form)
+                    == state.formfactorcombobox):
                 formfactor_pos = int(ind_form)
                 break
@@ -1200,6 +1246,6 @@
             state.structurecombobox = unicode(state.structurecombobox)
             for ind_struct in range(self.structurebox.GetCount()):
-                if self.structurebox.GetString(ind_struct) == \
-                                                (state.structurecombobox):
+                if (self.structurebox.GetString(ind_struct)
+                        == state.structurecombobox):
                     structfactor_pos = int(ind_struct)
                     break
@@ -1252,5 +1298,5 @@
             self.dI_sqrdata.SetValue(state.dI_sqrdata)
             self.dI_idata.SetValue(state.dI_idata)
-        except:
+        except Exception:
             # to support older state file formats
             self.dI_noweight.SetValue(False)
@@ -1308,5 +1354,5 @@
         self.weights = copy.deepcopy(state.weights)
 
-        for key, disp_type in state._disp_obj_dict.iteritems():
+        for key, disp_type in state.disp_obj_dict.iteritems():
             # disp_model = disp
             disp_model = POLYDISPERSITY_MODELS[disp_type]()
@@ -1542,5 +1588,5 @@
                         try:
                             self.npts_x = float(self.Npts_total.GetValue())
-                        except:
+                        except Exception:
                             flag = False
                             return flag
@@ -1583,8 +1629,7 @@
             return
 
-        for j in range(len(listtorestore)):
+        for item_page in listtorestore:
             for param in statelist:
-                if param[1] == listtorestore[j][1]:
-                    item_page = listtorestore[j]
+                if param[1] == item_page[1]:
                     item_page_info = param
                     if (item_page_info[1] == "theta" or item_page_info[1] ==
@@ -1632,9 +1677,6 @@
         listtorestore = copy.deepcopy(statelist)
 
-        for j in range(len(listtorestore)):
-            item_page = listtorestore[j]
-            item_page_info = statelist[j]
+        for item_page, item_page_info in zip(listtorestore, statelist):
             # change the state of the check box for simple parameters
-
             if item_page[0] is not None:
                 item_page[0].SetValue(format_number(item_page_info[0], True))
@@ -1741,5 +1783,4 @@
                     temp_smear = self.current_smearer
             # compute weight for the current data
-            from sas.sasgui.perspectives.fitting.utils import get_weight
             flag = self.get_weight_flag()
             weight = get_weight(data=self.data, is2d=self._is_2D(), flag=flag)
@@ -1768,5 +1809,4 @@
 
         from sas.sasgui.plottools import Data1D as pf_data1d
-        # from sas.sasgui.perspectives.theory.profile_dialog import SLDPanel
         from sas.sasgui.guiframe.local_perspectives.plotting.profile_dialog \
             import SLDPanel
@@ -1829,10 +1869,10 @@
             if mod_cat == CUSTOM_MODEL:
                 for model in self.model_list_box[mod_cat]:
-                    m_list.append(self.model_dict[model.name])
+                    m_list.append(self.model_dictionary[model.name])
             else:
                 cat_dic = self.master_category_dict[mod_cat]
-                for (model, enabled) in cat_dic:
+                for model, enabled in cat_dic:
                     if enabled:
-                        m_list.append(self.model_dict[model])
+                        m_list.append(self.model_dictionary[model])
         except Exception:
             msg = traceback.format_exc()
@@ -1906,5 +1946,5 @@
                     wx.PostEvent(self.parent, StatusEvent(status=msg))
                     return
-            except:
+            except Exception:
                 tcrtl.SetBackgroundColour("pink")
                 msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
@@ -1963,5 +2003,5 @@
                     wx.PostEvent(self._manager.parent, StatusEvent(status=msg))
                     return
-            except:
+            except Exception:
                 tcrtl.SetBackgroundColour("pink")
                 msg = "Model Error: wrong value entered: %s" % sys.exc_info()[1]
@@ -2063,5 +2103,4 @@
 
         if struct_factor is not None:
-            from sasmodels.sasview_model import MultiplicationModel
             self.model = MultiplicationModel(form_factor(self.multi_factor),
                                              struct_factor())
@@ -2141,5 +2180,5 @@
             # q value from qx and qy
             radius = np.sqrt(data.qx_data * data.qx_data +
-                                data.qy_data * data.qy_data)
+                             data.qy_data * data.qy_data)
             # get unmasked index
             index_data = (float(self.qmin.GetValue()) <= radius) & \
@@ -2370,5 +2409,5 @@
         put gaussian dispersity into current model
         """
-        if len(self.param_toFit) > 0:
+        if self.param_toFit:
             for item in self.fittable_param:
                 if item in self.param_toFit:
@@ -2385,12 +2424,7 @@
         self.weights = {}
 
-        # from sas.models.dispersion_models import GaussianDispersion
-        from sasmodels.weights import GaussianDispersion
-        if len(self.disp_cb_dict) == 0:
-            self.save_current_state()
+        if not self.disp_cb_dict:
             self.sizer4_4.Clear(True)
-            self.Layout()
-            return
-        if (len(self.disp_cb_dict) > 0):
+        else:
             for p in self.disp_cb_dict:
                 # The parameter was un-selected.
@@ -2465,5 +2499,5 @@
                 self._disp_obj_dict[name1] = disp_model
                 self.model.set_dispersion(param_name, disp_model)
-                self.state._disp_obj_dict[name1] = disp_model.type
+                self.state.disp_obj_dict[name1] = disp_model.type
 
                 value1 = str(format_number(self.model.getParam(name1), True))
@@ -2580,5 +2614,5 @@
         self._disp_obj_dict[name] = disp
         self.model.set_dispersion(name.split('.')[0], disp)
-        self.state._disp_obj_dict[name] = disp.type
+        self.state.disp_obj_dict[name] = disp.type
         self.values[name] = values
         self.weights[name] = weights
@@ -2938,20 +2972,20 @@
             # go through the parameters
             strings = self._get_copy_helper(self.parameters,
-                                           self.orientation_params)
+                                            self.orientation_params)
             content += strings
 
             # go through the fittables
             strings = self._get_copy_helper(self.fittable_param,
-                                           self.orientation_params_disp)
+                                            self.orientation_params_disp)
             content += strings
 
             # go through the fixed params
             strings = self._get_copy_helper(self.fixed_param,
-                                           self.orientation_params_disp)
+                                            self.orientation_params_disp)
             content += strings
 
             # go through the str params
             strings = self._get_copy_helper(self.str_parameters,
-                                           self.orientation_params)
+                                            self.orientation_params)
             content += strings
             return content
@@ -2966,5 +3000,5 @@
         the names of parameters that have been fitted
 
-        :returns: all_params - A list of all parameters, in the format of 
+        :returns: all_params - A list of all parameters, in the format of
         self.parameters
         :returns: fitted_par_names - A list of the names of parameters that have
@@ -3043,5 +3077,5 @@
                 # Only print errors for fitted parameters
                 content += param[4].GetValue()
-                content += tab 
+                content += tab
 
         return content
@@ -3063,7 +3097,7 @@
             # Do nothing if self.parameters doesn't exist
             return False
-        
-        content = '\\begin{table}'
-        content += '\\begin{tabular}[h]'
+
+        content = r'\begin{table}'
+        content += r'\begin{tabular}[h]'
 
         crlf = chr(13) + chr(10)
@@ -3075,5 +3109,5 @@
         for param in all_params:
             content += 'l|l|'
-        content += '}\hline'
+        content += r'}\hline'
         content += crlf
 
@@ -3081,13 +3115,13 @@
         for index, param in enumerate(all_params):
             name = param[1] # Parameter name
-            content += name.replace('_', '\_')  # Escape underscores
+            content += name.replace('_', r'\_')  # Escape underscores
             if name in fitted_param_names:
                 # Only print errors for fitted parameters
                 content += ' & '
-                content += name.replace('_', '\_') + "\_err"
+                content += name.replace('_', r'\_') + r"\_err"
             if index < len(all_params) - 1:
                 content += ' & '
 
-        content += '\\\\ \\hline'
+        content += r'\\ \hline'
         content += crlf
 
@@ -3108,9 +3142,9 @@
             if index < len(all_params) - 1:
                 content += ' & '
-        
-        content += '\\\\ \\hline'
+
+        content += r'\\ \hline'
         content += crlf
-        content += '\\end{tabular}'
-        content += '\\end{table}'
+        content += r'\end{tabular}'
+        content += r'\end{table}'
 
         return content
@@ -3369,5 +3403,5 @@
                                 if name.endswith('.npts'):
                                     pd = int(pd)
-                            except:
+                            except Exception:
                                 # continue
                                 if not pd and pd != '':
@@ -3455,5 +3489,5 @@
                 self._disp_obj_dict[name] = disp_model
                 self.model.set_dispersion(param_name, disp_model)
-                self.state._disp_obj_dict[name] = disp_model.type
+                self.state.disp_obj_dict[name] = disp_model.type
                 # TODO: It's not an array, why update values and weights?
                 self.model._persistency_dict[param_name] = \
@@ -3555,5 +3589,5 @@
         self.model_box.Clear()
 
-        if category == 'Plugin Models':
+        if category == CUSTOM_MODEL:
             for model in self.model_list_box[category]:
                 str_m = str(model).split(".")[0]
@@ -3561,7 +3595,7 @@
 
         else:
-            for (model, enabled) in sorted(self.master_category_dict[category],
-                                           key=lambda name: name[0]):
-                if(enabled):
+            for model, enabled in sorted(self.master_category_dict[category],
+                                         key=lambda name: name[0]):
+                if enabled:
                     self.model_box.Append(model)
 
@@ -3804,10 +3838,10 @@
         self.Bind(wx.EVT_SET_FOCUS, self._on_set_focus)
         self.Bind(wx.EVT_KILL_FOCUS, self._silent_kill_focus
-        if kill_focus_callback is None else kill_focus_callback)
+                  if kill_focus_callback is None else kill_focus_callback)
         self.Bind(wx.EVT_TEXT_ENTER, parent._onparamEnter
-        if text_enter_callback is None else text_enter_callback)
+                  if text_enter_callback is None else text_enter_callback)
         if not ON_MAC:
             self.Bind(wx.EVT_LEFT_UP, self._highlight_text
-            if mouse_up_callback is None else mouse_up_callback)
+                      if mouse_up_callback is None else mouse_up_callback)
 
     def _on_set_focus(self, event):
Index: src/sas/sasgui/perspectives/fitting/batchfitpage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/batchfitpage.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/perspectives/fitting/batchfitpage.py	(revision 50fcb09b3ee47cf23c26b90d9c291f41415c0aa5)
@@ -5,6 +5,12 @@
 import wx.lib.newevent
 import math
+
+from sas.sascalc.fit.qsmearing import smear_selection
+
 from sas.sasgui.guiframe.events import StatusEvent
 from sas.sasgui.guiframe.events import NewPlotEvent
+from sas.sasgui.perspectives.fitting.basepage import PageInfoEvent
+from sas.sasgui.perspectives.fitting.fitpage import FitPage
+from sas.sasgui.perspectives.fitting.fitpage import check_data_validity
 
 (Chi2UpdateEvent, EVT_CHI2_UPDATE) = wx.lib.newevent.NewEvent()
@@ -13,9 +19,4 @@
 SMEAR_SIZE_L = 0.00
 SMEAR_SIZE_H = 0.00
-
-from sas.sasgui.perspectives.fitting.basepage import PageInfoEvent
-from sas.sascalc.data_util.qsmearing import smear_selection
-from sas.sasgui.perspectives.fitting.fitpage import FitPage
-from sas.sasgui.perspectives.fitting.fitpage import check_data_validity
 
 class BatchFitPage(FitPage):
@@ -76,16 +77,16 @@
 #         """
 #         is_2Ddata = False
-#         
+#
 #         # Check if data is 2D
 #         if self.data.__class__.__name__ ==  "Data2D" or \
 #                         self.enable2D:
 #             is_2Ddata = True
-#             
-#         title = "Fitting"     
+#
+#         title = "Fitting"
 #         self._get_smear_info()
-#         
+#
 #         #Sizers
 #         box_description_range = wx.StaticBox(self, wx.ID_ANY, str(title))
-#         boxsizer_range = wx.StaticBoxSizer(box_description_range, wx.VERTICAL)      
+#         boxsizer_range = wx.StaticBoxSizer(box_description_range, wx.VERTICAL)
 #         self.sizer_set_smearer = wx.BoxSizer(wx.VERTICAL)
 #         #sizer_smearer = wx.BoxSizer(wx.HORIZONTAL)
@@ -93,5 +94,5 @@
 #         self.sizer_set_masking = wx.BoxSizer(wx.HORIZONTAL)
 #         sizer_chi2 = wx.BoxSizer(wx.VERTICAL)
-# 
+#
 #         sizer_fit = wx.GridSizer(2, 4, 2, 6)
 #         #Fit button
@@ -100,12 +101,12 @@
 #         self.btFit.Bind(wx.EVT_BUTTON, self._onFit, id= self.btFit.GetId())
 #         self.btFit.SetToolTipString("Start fitting.")
-# 
+#
 #         # Update and Draw button
 #         self.draw_button = wx.Button(self, self._ids.next(), 'Compute', size=(88, 24))
 #         self.draw_button.Bind(wx.EVT_BUTTON, \
 #                               self._onDraw,id=self.draw_button.GetId())
-#         self.draw_button.SetToolTipString("Compute and Draw.")  
+#         self.draw_button.SetToolTipString("Compute and Draw.")
 #         sizer_fit.Add(self.draw_button, 0, 0)
-#         sizer_fit.Add(self.btFit, 0, 0) 
+#         sizer_fit.Add(self.btFit, 0, 0)
 #         sizer_chi2.Add((-1, 5))
 #         # get smear_selection
@@ -114,77 +115,77 @@
 #          #2D data? default
 #         is_2Ddata = False
-#         
+#
 #         #check if it is 2D data
 #         if self.data.__class__.__name__ ==  "Data2D" or \
 #                         self.enable2D:
 #             is_2Ddata = True
-#             
+#
 #         self.sizer5.Clear(True)
-#      
+#
 #         self.qmin  = ModelTextCtrl(self, wx.ID_ANY, size=(_BOX_WIDTH, 20),
-#                                           style=wx.TE_PROCESS_ENTER, 
+#                                           style=wx.TE_PROCESS_ENTER,
 #                                     text_enter_callback = self._onQrangeEnter)
 #         self.qmin.SetValue(str(self.qmin_x))
 #         self.qmin.SetToolTipString("Minimun value of Q in linear scale.")
-#      
+#
 #         self.qmax  = ModelTextCtrl(self, wx.ID_ANY, size=(_BOX_WIDTH, 20),
-#                                           style=wx.TE_PROCESS_ENTER, 
+#                                           style=wx.TE_PROCESS_ENTER,
 #                                         text_enter_callback=self._onQrangeEnter)
 #         self.qmax.SetValue(str(self.qmax_x))
 #         self.qmax.SetToolTipString("Maximum value of Q in linear scale.")
-#         
+#
 #         id = self._ids.next()
 #         self.reset_qrange =wx.Button(self, id, 'Reset', size=(77, 20))
-#       
+#
 #         self.reset_qrange.Bind(wx.EVT_BUTTON, self.on_reset_clicked, id=id)
 #         self.reset_qrange.SetToolTipString(\
 #                                     "Reset Q range to the default values")
-#      
+#
 #         sizer_horizontal = wx.BoxSizer(wx.HORIZONTAL)
 #         sizer = wx.GridSizer(2, 4, 2, 6)
-# 
+#
 #         self.btEditMask = wx.Button(self, self._ids.next(),'Editor', size=(88, 23))
-#         self.btEditMask.Bind(wx.EVT_BUTTON, 
+#         self.btEditMask.Bind(wx.EVT_BUTTON,
 #                              self._onMask,id=self.btEditMask.GetId())
 #         self.btEditMask.SetToolTipString("Edit Mask.")
 #         self.EditMask_title = wx.StaticText(self, wx.ID_ANY, ' Masking(2D)')
-# 
+#
 #         sizer.Add(wx.StaticText(self, wx.ID_ANY, 'Q range'))
 #         sizer.Add(wx.StaticText(self, wx.ID_ANY, ' Min[1/A]'))
 #         sizer.Add(wx.StaticText(self, wx.ID_ANY, ' Max[1/A]'))
 #         sizer.Add(self.EditMask_title)
-#  
-#         sizer.Add(self.reset_qrange)   
+#
+#         sizer.Add(self.reset_qrange)
 #         sizer.Add(self.qmin)
 #         sizer.Add(self.qmax)
-# 
+#
 #         sizer.Add(self.btEditMask)
-#         boxsizer_range.Add(sizer_chi2) 
+#         boxsizer_range.Add(sizer_chi2)
 #         boxsizer_range.Add((10, 10))
 #         boxsizer_range.Add(sizer)
-#         
+#
 #         boxsizer_range.Add((10, 15))
 #         boxsizer_range.Add(sizer_fit)
 #         if is_2Ddata:
-#             self.btEditMask.Enable()  
-#             self.EditMask_title.Enable() 
+#             self.btEditMask.Enable()
+#             self.EditMask_title.Enable()
 #         else:
-#             self.btEditMask.Disable()  
+#             self.btEditMask.Disable()
 #             self.EditMask_title.Disable()
-# 
+#
 #         ## save state
 #         #self.save_current_state()
-# 
+#
 #         self.sizer5.Add(boxsizer_range, 0, wx.EXPAND | wx.ALL, 10)
 #         self.sizer5.Layout()
-#        
-#     def _on_select_model(self, event=None): 
+#
+#     def _on_select_model(self, event=None):
 #         """
 #         call back for model selection
-#         """  
-#         
-#         self.Show(False)    
-#         self._on_select_model_helper() 
-#         self.set_model_param_sizer(self.model)                   
+#         """
+#
+#         self.Show(False)
+#         self._on_select_model_helper()
+#         self.set_model_param_sizer(self.model)
 #         if self.model is None:
 #             self._set_bookmark_flag(False)
@@ -199,5 +200,5 @@
 #         self.state.structurecombobox = self.structurebox.GetCurrentSelection()
 #         self.state.formfactorcombobox = self.formfactorbox.GetCurrentSelection()
-#       
+#
 #         if self.model is not None:
 #             self._set_copy_flag(True)
@@ -206,27 +207,27 @@
 #                 self._set_bookmark_flag(False)
 #                 self._keep.Enable(False)
-#                 
+#
 #             temp_smear = None
 #             ## event to post model to fit to fitting plugins
 #             (ModelEventbox, _) = wx.lib.newevent.NewEvent()
-#          
-#             ## set smearing value whether or not 
+#
+#             ## set smearing value whether or not
 #             #    the data contain the smearing info
-#             evt = ModelEventbox(model=self.model, 
-#                                         smearer=temp_smear, 
+#             evt = ModelEventbox(model=self.model,
+#                                         smearer=temp_smear,
 #                                         qmin=float(self.qmin_x),
 #                                         uid=self.uid,
-#                                      qmax=float(self.qmax_x)) 
-#    
+#                                      qmax=float(self.qmax_x))
+#
 #             self._manager._on_model_panel(evt=evt)
 #             self.mbox_description.SetLabel("Model [%s]" % str(self.model.name))
 #             self.state.model = self.model.clone()
 #             self.state.model.name = self.model.name
-# 
-#             
+#
+#
 #         if event is not None:
 #             ## post state to fit panel
 #             new_event = PageInfoEvent(page = self)
-#             wx.PostEvent(self.parent, new_event) 
+#             wx.PostEvent(self.parent, new_event)
 #             #update list of plugins if new plugin is available
 #             if self.plugin_rbutton.GetValue():
@@ -243,6 +244,6 @@
 #             self._draw_model()
 #         self.SetupScrolling()
-#         self.Show(True)   
-#         
+#         self.Show(True)
+#
 #     def _update_paramv_on_fit(self):
 #         """
@@ -253,5 +254,5 @@
 #         self.fitrange = True
 #         is_modified = False
-# 
+#
 #         if self.model is not None:
 #             ##Check the values
@@ -259,8 +260,8 @@
 #             self._check_value_enter( self.fixed_param)
 #             self._check_value_enter( self.parameters)
-# 
-#             # If qmin and qmax have been modified, update qmin and qmax and 
+#
+#             # If qmin and qmax have been modified, update qmin and qmax and
 #              # Here we should check whether the boundaries have been modified.
-#             # If qmin and qmax have been modified, update qmin and qmax and 
+#             # If qmin and qmax have been modified, update qmin and qmax and
 #             # set the is_modified flag to True
 #             self.fitrange = self._validate_qrange(self.qmin, self.qmax)
@@ -273,16 +274,16 @@
 #                     self.qmax_x = tempmax
 #                 if tempmax == tempmin:
-#                     flag = False    
+#                     flag = False
 #                 #temp_smearer = None
 #                 if self._is_2D():
-#                     # only 2D case set mask  
+#                     # only 2D case set mask
 #                     flag = self._validate_Npts()
 #                     if not flag:
 #                         return flag
 #             else: flag = False
-#         else: 
+#         else:
 #             flag = False
-# 
-#         #For invalid q range, disable the mask editor and fit button, vs.    
+#
+#         #For invalid q range, disable the mask editor and fit button, vs.
 #         if not self.fitrange:
 #             #self.btFit.Disable()
@@ -293,18 +294,18 @@
 #             if self._is_2D() and  self.data is not None:
 #                 self.btEditMask.Enable(True)
-# 
+#
 #         if not flag:
 #             msg = "Cannot Plot or Fit :Must select a "
 #             msg += " model or Fitting range is not valid!!!  "
 #             wx.PostEvent(self.parent.parent, StatusEvent(status=msg))
-#         
+#
 #         self.save_current_state()
-#    
-#         return flag  
+#
+#         return flag
 #     def save_current_state(self):
 #         """
 #         Currently no save option implemented for batch page
 #         """
-#         pass 
+#         pass
 #     def save_current_state_fit(self):
 #         """
@@ -314,5 +315,5 @@
 #     def set_data(self, data):
 #         """
-#         reset the current data 
+#         reset the current data
 #         """
 #         #id = None
@@ -340,5 +341,5 @@
 #             self._set_save_flag(False)
 #             self._set_preview_flag(True)
-#   
+#
 #             self.formfactorbox.Enable()
 #             self.structurebox.Enable()
@@ -346,21 +347,21 @@
 #             #set maximum range for x in linear scale
 #             if not hasattr(self.data,"data"): #Display only for 1D data fit
-#                 # Minimum value of data   
+#                 # Minimum value of data
 #                 data_min = min(self.data.x)
-#                 # Maximum value of data  
+#                 # Maximum value of data
 #                 data_max = max(self.data.x)
-#                 self.btEditMask.Disable()  
+#                 self.btEditMask.Disable()
 #                 self.EditMask_title.Disable()
 #             else:
-#                 
-#                 ## Minimum value of data 
+#
+#                 ## Minimum value of data
 #                 data_min = 0
-#                 x = max(math.fabs(self.data.xmin), math.fabs(self.data.xmax)) 
+#                 x = max(math.fabs(self.data.xmin), math.fabs(self.data.xmax))
 #                 y = max(math.fabs(self.data.ymin), math.fabs(self.data.ymax))
-#                 ## Maximum value of data  
+#                 ## Maximum value of data
 #                 data_max = math.sqrt(x*x + y*y)
-#                 self.btEditMask.Enable()  
-#                 self.EditMask_title.Enable() 
-# 
+#                 self.btEditMask.Enable()
+#                 self.EditMask_title.Enable()
+#
 #         self.dataSource.SetValue(data_name)
 #         self.qmin_x = data_min
@@ -375,5 +376,5 @@
 #         self.state.qmin = self.qmin_x
 #         self.state.qmax = self.qmax_x
-#         
+#
 #         #update model plot with new data information
 #         if flag:
@@ -385,12 +386,12 @@
 #                 self.enable2D = False
 #                 self.model_view.SetLabel("1D Mode")
-#                 
+#
 #             self.model_view.Disable()
-#             
-#             wx.PostEvent(self._manager.parent, 
+#
+#             wx.PostEvent(self._manager.parent,
 #                              NewPlotEvent(group_id=group_id,
 #                                                action="delete"))
 #             #plot the current selected data
-#             wx.PostEvent(self._manager.parent, NewPlotEvent(plot=self.data, 
+#             wx.PostEvent(self._manager.parent, NewPlotEvent(plot=self.data,
 #                                                     title=str(self.data.title)))
 #             self._manager.store_data(uid=self.uid, data=data,
Index: src/sas/sasgui/perspectives/fitting/fit_thread.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fit_thread.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/perspectives/fitting/fit_thread.py	(revision ba8d326f0f4ceb55c77296a9b5af8bd013c7dd92)
@@ -29,9 +29,5 @@
                  worktime=0.03,
                  reset_flag=False):
-        CalcThread.__init__(self,
-                 completefn,
-                 updatefn,
-                 yieldtime,
-                 worktime)
+        CalcThread.__init__(self, completefn, updatefn, yieldtime, worktime)
         self.handler = handler
         self.fitter = fn
@@ -93,5 +89,5 @@
             # Real code should not print, but this is an example...
             #print "keyboard exception"
-            #Stop on exception during fitting. Todo: need to put 
+            #Stop on exception during fitting. Todo: need to put
             #some mssg and reset progress bar.
 
@@ -100,5 +96,5 @@
             if self.handler is not None:
                 self.handler.stop(msg=msg)
-        except:
+        except:  # catch-all: show every exception which stops the thread
             import traceback
             if self.handler is not None:
Index: src/sas/sasgui/perspectives/fitting/fitpage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitpage.py	(revision 48154abb52e9b741830a04b4000c9ad14563cd46)
+++ src/sas/sasgui/perspectives/fitting/fitpage.py	(revision 48154abb52e9b741830a04b4000c9ad14563cd46)
@@ -14,4 +14,6 @@
 from sasmodels.weights import MODELS as POLYDISPERSITY_MODELS
 
+from sas.sascalc.fit.qsmearing import smear_selection
+
 from sas.sasgui.guiframe.events import StatusEvent, NewPlotEvent, \
     PlotQrangeEvent
@@ -23,5 +25,4 @@
 from sas.sasgui.perspectives.fitting.basepage import PageInfoEvent as \
     PageInfoEvent
-from sas.sascalc.data_util.qsmearing import smear_selection
 from .basepage import ModelTextCtrl
 
@@ -1142,18 +1143,24 @@
             self.model.name = "M" + str(self.index_model)
 
-    def _on_select_model(self, event=None):
+    def _on_select_model(self, event=None, keep_pars=False):
         """
         call back for model selection
         """
         self.Show(False)
-        copy_flag = False
-        is_poly_enabled = None
         if event is not None:
-            if (event.GetEventObject() == self.formfactorbox
-                    and self.structurebox.GetLabel() != 'None')\
-                    or event.GetEventObject() == self.structurebox\
-                    or event.GetEventObject() == self.multifactorbox:
-                copy_flag = self.get_copy_params()
-                is_poly_enabled = self.enable_disp.GetValue()
+            control = event.GetEventObject()
+            if ((control == self.formfactorbox
+                 and self.structurebox.GetLabel() != 'None')
+                    or control == self.structurebox
+                    or control == self.multifactorbox):
+                keep_pars = True
+
+        if keep_pars:
+            saved_pars = self.get_copy_params()
+            is_poly_enabled = self.enable_disp.GetValue()
+        else:
+            saved_pars = None
+            is_poly_enabled = None
+
         try:
             self._on_select_model_helper()
@@ -1177,5 +1184,5 @@
         try:
             self.set_dispers_sizer()
-        except:
+        except Exception:
             pass
         self.state.enable_disp = self.enable_disp.GetValue()
@@ -1238,35 +1245,35 @@
             self.state.model.name = self.model.name
 
+        # when select a model only from guictr/button
+        if is_poly_enabled is not None:
+            self.enable_disp.SetValue(is_poly_enabled)
+            self.disable_disp.SetValue(not is_poly_enabled)
+            self._set_dipers_Param(event=None)
+            self.state.enable_disp = self.enable_disp.GetValue()
+            self.state.disable_disp = self.disable_disp.GetValue()
+
+        # Keep the previous param values
+        if saved_pars:
+            self.get_paste_params(saved_pars)
+
         if event is not None:
+            # update list of plugins if new plugin is available
+            # mod_cat = self.categorybox.GetStringSelection()
+            # if mod_cat == CUSTOM_MODEL:
+            #     temp = self.parent.update_model_list()
+            #     for v in self.parent.model_dictionary.values():
+            #         if v.id == self.model.id:
+            #             self.model = v()
+            #             break
+            #     if temp:
+            #         self.model_list_box = temp
+            #         current_val = self.formfactorbox.GetLabel()
+            #         pos = self.formfactorbox.GetSelection()
+            #         self._show_combox_helper()
+            #         self.formfactorbox.SetStringSelection(current_val)
+            #         self.formfactorbox.SetValue(current_val)
             # post state to fit panel
             new_event = PageInfoEvent(page=self)
             wx.PostEvent(self.parent, new_event)
-            # update list of plugins if new plugin is available
-            mod_cat = self.categorybox.GetStringSelection()
-            if mod_cat == CUSTOM_MODEL:
-                temp_id = self.model.id
-                temp = self.parent.update_model_list()
-                for v in self.parent.model_dictionary.values():
-                    if v.id == temp_id:
-                        self.model = v()
-                        break
-                if temp:
-                    self.model_list_box = temp
-                    current_val = self.formfactorbox.GetLabel()
-                    pos = self.formfactorbox.GetSelection()
-                    self._show_combox_helper()
-                    self.formfactorbox.SetSelection(pos)
-                    self.formfactorbox.SetValue(current_val)
-            # when select a model only from guictr/button
-            if is_poly_enabled is not None:
-                self.enable_disp.SetValue(is_poly_enabled)
-                self.disable_disp.SetValue(not is_poly_enabled)
-                self._set_dipers_Param(event=None)
-                self.state.enable_disp = self.enable_disp.GetValue()
-                self.state.disable_disp = self.disable_disp.GetValue()
-
-            # Keep the previous param values
-            if copy_flag:
-                self.get_paste_params(copy_flag)
             wx.CallAfter(self._onDraw, None)
 
@@ -1726,6 +1733,6 @@
         ind = 0
         while(ind < len(list)):
-            for key, val in list.iteritems():
-                if (val == ind):
+            for key, val in list.items():
+                if val == ind:
                     fun_box.Append(key, val)
                     break
Index: src/sas/sasgui/perspectives/fitting/fitpanel.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitpanel.py	(revision c9ecd1b562e38798001341ea1382b347f0cc1af9)
+++ src/sas/sasgui/perspectives/fitting/fitpanel.py	(revision c9ecd1b562e38798001341ea1382b347f0cc1af9)
@@ -9,11 +9,17 @@
 from wx.aui import AuiNotebook as nb
 
+from sas.sascalc.fit.models import ModelManager
+
 from sas.sasgui.guiframe.panel_base import PanelBase
 from sas.sasgui.guiframe.events import PanelOnFocusEvent, StatusEvent
 from sas.sasgui.guiframe.dataFitting import check_data_validity
-from sas.sasgui.perspectives.fitting.simfitpage import SimultaneousFitPage
-
-import basepage
-import models
+
+
+from . import basepage
+from .fitpage import FitPage
+from .simfitpage import SimultaneousFitPage
+from .batchfitpage import BatchFitPage
+from .fitting_widgets import BatchDataDialog
+
 _BOX_WIDTH = 80
 
@@ -46,5 +52,5 @@
         self.event_owner = None
         # dictionary of miodel {model class name, model class}
-        self.menu_mng = models.ModelManager()
+        self.menu_mng = ModelManager()
         self.model_list_box = self.menu_mng.get_model_list()
         # pageClosedEvent = nb.EVT_FLATNOTEBOOK_PAGE_CLOSING
@@ -114,5 +120,5 @@
         """
         temp = self.menu_mng.update()
-        if len(temp):
+        if temp:
             self.model_list_box = temp
         return temp
@@ -121,8 +127,6 @@
         """
         """
-        temp = self.menu_mng.plugins_reset()
-        if len(temp):
-            self.model_list_box = temp
-        return temp
+        self.model_list_box = self.menu_mng.plugins_reset()
+        return self.model_list_box
 
     def get_page_by_id(self, uid):
@@ -298,9 +302,9 @@
         self.model_list_box = dict
 
-    def set_model_dict(self, m_dict):
+    def set_model_dictionary(self, model_dictionary):
         """
         copy a dictionary of model name -> model object
 
-        :param m_dict: dictionary linking model name -> model object
+        :param model_dictionary: dictionary linking model name -> model object
         """
 
@@ -316,5 +320,4 @@
         Add the simultaneous fit page
         """
-        from simfitpage import SimultaneousFitPage
         page_finder = self._manager.get_page_finder()
         if caption == "Const & Simul Fit":
@@ -344,5 +347,4 @@
         """
         if self.batch_on:
-            from batchfitpage import BatchFitPage
             panel = BatchFitPage(parent=self)
             self.batch_page_index += 1
@@ -351,5 +353,4 @@
         else:
             # Increment index of fit page
-            from fitpage import FitPage
             panel = FitPage(parent=self)
             self.fit_page_index += 1
@@ -359,5 +360,5 @@
         panel._set_save_flag(not panel.batch_on)
         panel.set_model_dictionary(self.model_dictionary)
-        panel.populate_box(model_dict=self.model_list_box)
+        panel.populate_box(model_list_box=self.model_list_box)
         panel.formfactor_combo_init()
         panel.set_manager(self._manager)
@@ -445,6 +446,4 @@
         if data_1d_list and data_2d_list:
             # need to warning the user that this batch is a special case
-            from sas.sasgui.perspectives.fitting.fitting_widgets import \
-                BatchDataDialog
             dlg = BatchDataDialog(self)
             if dlg.ShowModal() == wx.ID_OK:
Index: src/sas/sasgui/perspectives/fitting/fitproblem.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitproblem.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/perspectives/fitting/fitproblem.py	(revision 251ef68424c8f373dfb82af3c159086491c5f975)
@@ -14,12 +14,43 @@
 ################################################################################
 import copy
-from sas.sascalc.data_util.qsmearing import smear_selection
-
-class FitProblemComponent(object):
+
+from sas.sascalc.fit.qsmearing import smear_selection
+
+class FitProblem(object):
     """
-    Inferface containing information to store data, model, range of data, etc...
-    and retreive this information. This is an inferface
-    for a fitProblem i.e relationship between data and model.
+    Define the relationship between data and model, including range, weights,
+    etc.
     """
+    def __init__(self):
+        """
+        contains information about data and model to fit
+        """
+        ## data used for fitting
+        self.fit_data = None
+        self.theory_data = None
+        self.residuals = None
+        # original data: should not be modified
+        self.original_data = None
+        ## the current model
+        self.model = None
+        ## if 1 this fit problem will be selected to fit , if 0
+        ## it will not be selected for fit
+        self.schedule = 0
+        ##list containing parameter name and value
+        self.list_param = []
+        self.list_param2fit = []
+        ## smear object to smear or not data1D
+        self.smearer_computed = False
+        self.smearer_enable = False
+        self.smearer_computer_value = None
+        ## fitting range
+        self.qmin = None
+        self.qmax = None
+        # fit weight
+        self.weight = None
+        self.result = None
+        self.fit_tab_caption = None
+        self.name_per_page = None
+
     def enable_smearing(self, flag=False):
         """
@@ -27,4 +58,14 @@
             flag is 0 ingore smear value.
         """
+        self.smearer_enable = flag
+
+    def set_smearer(self, smearer):
+        """
+        save reference of  smear object on fitdata
+
+        :param smear: smear object from DataLoader
+
+        """
+        self.smearer_computer_value = smearer
 
     def get_smearer(self):
@@ -32,11 +73,22 @@
         return smear object
         """
+        if not self.smearer_enable:
+            return None
+        if not self.smearer_computed:
+            #smeari_selection should be call only once per fitproblem
+            self.smearer_computer_value = smear_selection(self.fit_data,
+                                                          self.model)
+            self.smearer_computed = True
+        return self.smearer_computer_value
+
     def save_model_name(self, name):
         """
         """
+        self.name_per_page = name
 
     def get_name(self):
         """
         """
+        return self.name_per_page
 
     def set_model(self, model):
@@ -46,4 +98,8 @@
         :param name: name created for model
         """
+        self.model = model
+        self.smearer_computer_value = smear_selection(self.fit_data,
+                                                      self.model)
+        self.smearer_computed = True
 
     def get_model(self):
@@ -51,4 +107,5 @@
         :return: saved model
         """
+        return self.model
 
     def set_residuals(self, residuals):
@@ -57,4 +114,5 @@
         :param data: data selected
         """
+        self.residuals = residuals
 
     def get_residuals(self):
@@ -62,27 +120,79 @@
         :return: residuals
         """
+        return self.residuals
 
     def set_theory_data(self, data):
         """
         save a copy of the data select to fit
+
         :param data: data selected
-        """
+
+        """
+        self.theory_data = copy.deepcopy(data)
 
     def get_theory_data(self):
         """
-        :return: list of data dList
-        """
+        :return: theory generated with the current model and data of this class
+        """
+        return self.theory_data
 
     def set_fit_data(self, data):
         """
-        Store of list of data and create  by create new fitproblem of each data
-        id, if there was existing information about model, this information
-        get copy to the new fitproblem
+        Store data associated with this class
         :param data: list of data selected
         """
+        self.original_data = None
+        self.fit_data = None
+        # original data: should not be modified
+        self.original_data = data
+        # fit data: used for fit and can be modified for convenience
+        self.fit_data = copy.deepcopy(data)
+        self.smearer_computer_value = smear_selection(self.fit_data, self.model)
+        self.smearer_computed = True
+        self.result = None
 
     def get_fit_data(self):
         """
-        """
+        :return: data associate with this class
+        """
+        return self.fit_data
+
+    def get_origin_data(self):
+        """
+        """
+        return self.original_data
+
+    def set_weight(self, is2d, flag=None):
+        """
+        Received flag and compute error on data.
+        :param flag: flag to transform error of data.
+        :param is2d: flag to distinguish 1D to 2D Data
+        """
+        from sas.sasgui.perspectives.fitting.utils import get_weight
+        # send original data for weighting
+        self.weight = get_weight(data=self.original_data, is2d=is2d, flag=flag)
+        if is2d:
+            self.fit_data.err_data = self.weight
+        else:
+            self.fit_data.dy = self.weight
+
+    def get_weight(self):
+        """
+        returns weight array
+        """
+        return self.weight
+
+    def set_param2fit(self, list):
+        """
+        Store param names to fit (checked)
+        :param list: list of the param names
+        """
+        self.list_param2fit = list
+
+    def get_param2fit(self):
+        """
+        return the list param names to fit
+        """
+        return self.list_param2fit
 
     def set_model_param(self, name, value=None):
@@ -92,15 +202,5 @@
         :param value: value of that parameter
         """
-
-    def set_param2fit(self, list):
-        """
-        Store param names to fit (checked)
-        :param list: list of the param names
-        """
-
-    def get_param2fit(self):
-        """
-        return the list param names to fit
-        """
+        self.list_param.append([name, value])
 
     def get_model_param(self):
@@ -108,4 +208,5 @@
         return list of couple of parameter name and value
         """
+        return self.list_param
 
     def schedule_tofit(self, schedule=0):
@@ -113,4 +214,5 @@
         set schedule to true to decide if this fit  must be performed
         """
+        self.schedule = schedule
 
     def get_scheduled(self):
@@ -118,24 +220,21 @@
         return true or false if a problem as being schedule for fitting
         """
+        return self.schedule
 
     def set_range(self, qmin=None, qmax=None):
         """
         set fitting range
-        """
+        :param qmin: minimum value to consider for the fit range
+        :param qmax: maximum value to consider for the fit range
+        """
+        self.qmin = qmin
+        self.qmax = qmax
 
     def get_range(self):
         """
         :return: fitting range
-        """
-
-    def set_weight(self, flag=None):
-        """
-        set fitting range
-        """
-
-    def get_weight(self):
-        """
-        get fitting weight
-        """
+
+        """
+        return self.qmin, self.qmax
 
     def clear_model_param(self):
@@ -143,14 +242,15 @@
         clear constraint info
         """
+        self.list_param = []
 
     def set_fit_tab_caption(self, caption):
         """
-        store the caption of the page associated with object
-        """
+        """
+        self.fit_tab_caption = str(caption)
 
     def get_fit_tab_caption(self):
         """
-        Return the caption of the page associated with object
-        """
+        """
+        return self.fit_tab_caption
 
     def set_graph_id(self, id):
@@ -158,4 +258,5 @@
         Set graph id (from data_group_id at the time the graph produced)
         """
+        self.graph_id = id
 
     def get_graph_id(self):
@@ -163,8 +264,10 @@
         Get graph_id
         """
+        return self.graph_id
 
     def set_result(self, result):
         """
         """
+        self.result = result
 
     def get_result(self):
@@ -172,12 +275,12 @@
         get result
         """
-
-
-class FitProblemDictionary(FitProblemComponent, dict):
+        return self.result
+
+
+class FitProblemDictionary(dict):
     """
     This module implements a dictionary of fitproblem objects
     """
     def __init__(self):
-        FitProblemComponent.__init__(self)
         dict.__init__(self)
         ## the current model
@@ -206,9 +309,8 @@
         self._smear_on = flag
         if fid is None:
-            for value in self.itervalues():
+            for value in self.values():
                 value.enable_smearing(flag)
-        else:
-            if fid in self.iterkeys():
-                self[fid].enable_smearing(flag)
+        elif fid in self:
+            self[fid].enable_smearing(flag)
 
     def set_smearer(self, smearer, fid=None):
@@ -218,9 +320,8 @@
         """
         if fid is None:
-            for value in self.itervalues():
+            for value in self.values():
                 value.set_smearer(smearer)
-        else:
-            if fid in self.iterkeys():
-                self[fid].set_smearer(smearer)
+        elif fid in self:
+            self[fid].set_smearer(smearer)
 
     def get_smearer(self, fid=None):
@@ -228,5 +329,5 @@
         return smear object
         """
-        if fid in self.iterkeys():
+        if fid in self:
             return self[fid].get_smearer()
 
@@ -235,9 +336,8 @@
         """
         if fid is None:
-            for value in self.itervalues():
+            for value in self.values():
                 value.save_model_name(name)
-        else:
-            if fid in self.iterkeys():
-                self[fid].save_model_name(name)
+        elif fid in self:
+            self[fid].save_model_name(name)
 
     def get_name(self, fid=None):
@@ -246,9 +346,8 @@
         result = []
         if fid is None:
-            for value in self.itervalues():
+            for value in self.values():
                 result.append(value.get_name())
-        else:
-            if fid in self.iterkeys():
-                result.append(self[fid].get_name())
+        elif fid in self:
+            result.append(self[fid].get_name())
         return result
 
@@ -261,9 +360,8 @@
         self.model = model
         if fid is None:
-            for value in self.itervalues():
+            for value in self.values():
                 value.set_model(self.model)
-        else:
-            if fid in self.iterkeys():
-                self[fid].set_model(self.model)
+        elif fid in self:
+            self[fid].set_model(self.model)
 
     def get_model(self, fid):
@@ -271,5 +369,5 @@
         :return: saved model
         """
-        if fid in self.iterkeys():
+        if fid in self:
             return self[fid].get_model()
 
@@ -291,5 +389,5 @@
         :param data: data selected
         """
-        if fid in self.iterkeys():
+        if fid in self:
             self[fid].set_residuals(residuals)
 
@@ -298,5 +396,5 @@
         :return: residuals
         """
-        if fid in self.iterkeys():
+        if fid in self:
             return self[fid].get_residuals()
 
@@ -306,5 +404,5 @@
         :param data: data selected
         """
-        if fid in self.iterkeys():
+        if fid in self:
             self[fid].set_theory_data(data)
 
@@ -313,5 +411,5 @@
         :return: list of data dList
         """
-        if fid in self.iterkeys():
+        if fid in self:
             return self[fid].get_theory_data()
 
@@ -322,5 +420,5 @@
         :note: only data changes in the fit problem
         """
-        if data.id not in self.iterkeys():
+        if data.id not in self:
             self[data.id] = FitProblem()
         self[data.id].set_fit_data(data)
@@ -336,6 +434,6 @@
             data = []
         for d in data:
-            if (d is not None):
-                if (d.id not in self.iterkeys()):
+            if d is not None:
+                if d.id not in self:
                     self[d.id] = FitProblem()
                 self[d.id].set_fit_data(d)
@@ -348,5 +446,5 @@
         :param fid: key representing a fitproblem, usually extract from data id
         """
-        if fid in self.iterkeys():
+        if fid in self:
             return self[fid].get_fit_data()
 
@@ -358,9 +456,8 @@
         """
         if fid is None:
-            for value in self.itervalues():
+            for value in self.values():
                 value.set_model_param(name, value)
-        else:
-            if fid in self.iterkeys():
-                self[fid].set_model_param(name, value)
+        elif fid in self:
+            self[fid].set_model_param(name, value)
 
     def get_model_param(self, fid):
@@ -368,5 +465,5 @@
         return list of couple of parameter name and value
         """
-        if fid in self.iterkeys():
+        if fid in self:
             return self[fid].get_model_param()
 
@@ -389,5 +486,5 @@
         """
         self.scheduled = schedule
-        for value in self.itervalues():
+        for value in self.values():
             value.schedule_tofit(schedule)
 
@@ -405,9 +502,8 @@
         self.qmax = qmax
         if fid is None:
-            for value in self.itervalues():
+            for value in self.values():
                 value.set_range(self.qmin, self.qmax)
-        else:
-            if fid in self.iterkeys():
-                self[fid].value.set_range(self.qmin, self.qmax)
+        elif fid in self:
+            self[fid].value.set_range(self.qmin, self.qmax)
 
     def get_range(self, fid):
@@ -415,5 +511,5 @@
         :return: fitting range
         """
-        if fid in self.iterkeys():
+        if fid in self:
             return self[fid].get_range()
 
@@ -423,9 +519,8 @@
         """
         if fid is None:
-            for value in self.itervalues():
+            for value in self.values():
                 value.set_weight(flag=flag, is2d=is2d)
-        else:
-            if fid in self.iterkeys():
-                self[fid].set_weight(flag=flag, is2d=is2d)
+        elif fid in self:
+            self[fid].set_weight(flag=flag, is2d=is2d)
 
     def get_weight(self, fid=None):
@@ -433,5 +528,5 @@
         return fit weight
         """
-        if fid in self.iterkeys():
+        if fid in self:
             return self[fid].get_weight()
 
@@ -441,9 +536,8 @@
         """
         if fid is None:
-            for value in self.itervalues():
+            for value in self.values():
                 value.clear_model_param()
-        else:
-            if fid in self.iterkeys():
-                self[fid].clear_model_param()
+        elif fid in self:
+            self[fid].clear_model_param()
 
     def get_fit_problem(self):
@@ -451,10 +545,10 @@
         return fitproblem contained in this dictionary
         """
-        return self.itervalues()
+        return self.values()
 
     def set_result(self, result, fid):
         """
         """
-        if fid in self.iterkeys():
+        if fid in self:
             self[fid].set_result(result)
 
@@ -470,5 +564,5 @@
         get result
         """
-        if fid in self.iterkeys():
+        if fid in self:
             return self[fid].get_result()
 
@@ -490,263 +584,2 @@
         """
         return self.graph_id
-
-
-class FitProblem(FitProblemComponent):
-    """
-    FitProblem class allows to link a model with the new name created in _on_model,
-    a name theory created with that model  and the data fitted with the model.
-    FitProblem is mostly used  as value of the dictionary by fitting module.
-    """
-    def __init__(self):
-        FitProblemComponent.__init__(self)
-        """
-        contains information about data and model to fit
-        """
-        ## data used for fitting
-        self.fit_data = None
-        self.theory_data = None
-        self.residuals = None
-        # original data: should not be modified
-        self.original_data = None
-        ## the current model
-        self.model = None
-        ## if 1 this fit problem will be selected to fit , if 0
-        ## it will not be selected for fit
-        self.schedule = 0
-        ##list containing parameter name and value
-        self.list_param = []
-        ## smear object to smear or not data1D
-        self.smearer_computed = False
-        self.smearer_enable = False
-        self.smearer_computer_value = None
-        ## fitting range
-        self.qmin = None
-        self.qmax = None
-        # fit weight
-        self.weight = None
-        self.result = None
-
-    def enable_smearing(self, flag=False):
-        """
-        :param flag: bool.When flag is 1 get the computer smear value. When
-            flag is 0 ingore smear value.
-        """
-        self.smearer_enable = flag
-
-    def set_smearer(self, smearer):
-        """
-        save reference of  smear object on fitdata
-
-        :param smear: smear object from DataLoader
-
-        """
-        self.smearer_computer_value = smearer
-
-    def get_smearer(self):
-        """
-        return smear object
-        """
-        if not self.smearer_enable:
-            return None
-        if not self.smearer_computed:
-            #smeari_selection should be call only once per fitproblem
-            self.smearer_computer_value = smear_selection(self.fit_data,
-                                                           self.model)
-            self.smearer_computed = True
-        return self.smearer_computer_value
-
-    def save_model_name(self, name):
-        """
-        """
-        self.name_per_page = name
-
-    def get_name(self):
-        """
-        """
-        return self.name_per_page
-
-    def set_model(self, model):
-        """
-        associates each model with its new created name
-        :param model: model selected
-        :param name: name created for model
-        """
-        self.model = model
-        self.smearer_computer_value = smear_selection(self.fit_data,
-                                                           self.model)
-        self.smearer_computed = True
-
-    def get_model(self):
-        """
-        :return: saved model
-        """
-        return self.model
-
-    def set_residuals(self, residuals):
-        """
-        save a copy of residual
-        :param data: data selected
-        """
-        self.residuals = residuals
-
-    def get_residuals(self):
-        """
-        :return: residuals
-        """
-        return self.residuals
-
-    def set_theory_data(self, data):
-        """
-        save a copy of the data select to fit
-
-        :param data: data selected
-
-        """
-        self.theory_data = copy.deepcopy(data)
-
-    def get_theory_data(self):
-        """
-        :return: theory generated with the current model and data of this class
-        """
-        return self.theory_data
-
-    def set_fit_data(self, data):
-        """
-        Store data associated with this class
-        :param data: list of data selected
-        """
-        self.original_data = None
-        self.fit_data = None
-        # original data: should not be modified
-        self.original_data = data
-        # fit data: used for fit and can be modified for convenience
-        self.fit_data = copy.deepcopy(data)
-        self.smearer_computer_value = smear_selection(self.fit_data,
-                                                           self.model)
-        self.smearer_computed = True
-        self.result = None
-
-    def get_fit_data(self):
-        """
-        :return: data associate with this class
-        """
-        return self.fit_data
-
-    def get_origin_data(self):
-        """
-        """
-        return self.original_data
-
-    def set_weight(self, is2d, flag=None):
-        """
-        Received flag and compute error on data.
-        :param flag: flag to transform error of data.
-        :param is2d: flag to distinguish 1D to 2D Data
-        """
-        from sas.sasgui.perspectives.fitting.utils import get_weight
-        # send original data for weighting
-        self.weight = get_weight(data=self.original_data, is2d=is2d, flag=flag)
-        if is2d:
-            self.fit_data.err_data = self.weight
-        else:
-            self.fit_data.dy = self.weight
-
-    def get_weight(self):
-        """
-        returns weight array
-        """
-        return self.weight
-
-    def set_param2fit(self, list):
-        """
-        Store param names to fit (checked)
-        :param list: list of the param names
-        """
-        self.list_param2fit = list
-
-    def get_param2fit(self):
-        """
-        return the list param names to fit
-        """
-        return self.list_param2fit
-
-    def set_model_param(self, name, value=None):
-        """
-        Store the name and value of a parameter of this fitproblem's model
-        :param name: name of the given parameter
-        :param value: value of that parameter
-        """
-        self.list_param.append([name, value])
-
-    def get_model_param(self):
-        """
-        return list of couple of parameter name and value
-        """
-        return self.list_param
-
-    def schedule_tofit(self, schedule=0):
-        """
-        set schedule to true to decide if this fit  must be performed
-        """
-        self.schedule = schedule
-
-    def get_scheduled(self):
-        """
-        return true or false if a problem as being schedule for fitting
-        """
-        return self.schedule
-
-    def set_range(self, qmin=None, qmax=None):
-        """
-        set fitting range
-        :param qmin: minimum value to consider for the fit range
-        :param qmax: maximum value to consider for the fit range
-        """
-        self.qmin = qmin
-        self.qmax = qmax
-
-    def get_range(self):
-        """
-        :return: fitting range
-
-        """
-        return self.qmin, self.qmax
-
-    def clear_model_param(self):
-        """
-        clear constraint info
-        """
-        self.list_param = []
-
-    def set_fit_tab_caption(self, caption):
-        """
-        """
-        self.fit_tab_caption = str(caption)
-
-    def get_fit_tab_caption(self):
-        """
-        """
-        return self.fit_tab_caption
-
-    def set_graph_id(self, id):
-        """
-        Set graph id (from data_group_id at the time the graph produced)
-        """
-        self.graph_id = id
-
-    def get_graph_id(self):
-        """
-        Get graph_id
-        """
-        return self.graph_id
-
-    def set_result(self, result):
-        """
-        """
-        self.result = result
-
-    def get_result(self):
-        """
-        get result
-        """
-        return self.result
Index: src/sas/sasgui/perspectives/fitting/fitting.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/fitting.py	(revision 2504b5a1018b4b2aff653769b6785e73300296bf)
+++ src/sas/sasgui/perspectives/fitting/fitting.py	(revision 9706d88b71e719f590147b9914149630bce2f1d2)
@@ -23,5 +23,17 @@
 import traceback
 
+import bumps.options
+from bumps.gui.fit_dialog import show_fit_config
+try:
+    from bumps.gui.fit_dialog import EVT_FITTER_CHANGED
+except ImportError:
+    # CRUFT: bumps 0.7.5.8 and below
+    EVT_FITTER_CHANGED = None  # type: wx.PyCommandEvent
+
 from sas.sascalc.dataloader.loader import Loader
+from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
+from sas.sascalc.fit.pagestate import Reader, PageState, SimFitPageState
+from sas.sascalc.fit import models
+
 from sas.sasgui.guiframe.dataFitting import Data2D
 from sas.sasgui.guiframe.dataFitting import Data1D
@@ -34,20 +46,20 @@
 from sas.sasgui.guiframe.plugin_base import PluginBase
 from sas.sasgui.guiframe.data_processor import BatchCell
-from sas.sascalc.fit.BumpsFitting import BumpsFit as Fit
-from sas.sasgui.perspectives.fitting.console import ConsoleUpdate
-from sas.sasgui.perspectives.fitting.fitproblem import FitProblemDictionary
-from sas.sasgui.perspectives.fitting.fitpanel import FitPanel
-from sas.sasgui.perspectives.fitting.resultpanel import ResultPanel, PlotResultEvent
-
-from sas.sasgui.perspectives.fitting.fit_thread import FitThread
-from sas.sasgui.perspectives.fitting.pagestate import Reader
-from sas.sasgui.perspectives.fitting.fitpage import Chi2UpdateEvent
+from sas.sasgui.guiframe.gui_manager import MDIFrame
+from sas.sasgui.guiframe.documentation_window import DocumentationWindow
+
 from sas.sasgui.perspectives.calculator.model_editor import TextDialog
 from sas.sasgui.perspectives.calculator.model_editor import EditorWindow
-from sas.sasgui.guiframe.gui_manager import MDIFrame
-from sas.sasgui.guiframe.documentation_window import DocumentationWindow
-from sas.sasgui.perspectives.fitting.gpu_options import GpuOptions
-
-from . import models
+from sas.sasgui.perspectives.calculator.pyconsole import PyConsole
+
+from .fitting_widgets import DataDialog
+from .fit_thread import FitThread
+from .fitpage import Chi2UpdateEvent
+from .console import ConsoleUpdate
+from .fitproblem import FitProblemDictionary
+from .fitpanel import FitPanel
+from .model_thread import Calc1D, Calc2D
+from .resultpanel import ResultPanel, PlotResultEvent
+from .gpu_options import GpuOptions
 
 logger = logging.getLogger(__name__)
@@ -63,11 +75,4 @@
     ON_MAC = True
 
-import bumps.options
-from bumps.gui.fit_dialog import show_fit_config
-try:
-    from bumps.gui.fit_dialog import EVT_FITTER_CHANGED
-except ImportError:
-    # CRUFT: bumps 0.7.5.8 and below
-    EVT_FITTER_CHANGED = None  # type: wx.PyCommandEvent
 
 class Plugin(PluginBase):
@@ -240,5 +245,4 @@
         event_id = event.GetId()
         label = self.edit_menu.GetLabel(event_id)
-        from sas.sasgui.perspectives.calculator.pyconsole import PyConsole
         filename = os.path.join(models.find_plugins_dir(), label)
         frame = PyConsole(parent=self.parent, manager=self,
@@ -290,5 +294,5 @@
                         break
         except Exception:
-            import traceback; traceback.print_exc()
+            traceback.print_exc()
             msg = 'Delete Error: \nCould not delete the file; Check if in use.'
             wx.MessageBox(msg, 'Error')
@@ -300,5 +304,5 @@
         event_id = event.GetId()
         model_manager = models.ModelManager()
-        model_list = model_manager.get_model_name_list()
+        model_list = model_manager.composable_models()
         plug_dir = models.find_plugins_dir()
         textdial = TextDialog(None, self, wx.ID_ANY, 'Easy Sum/Multi(p1, p2) Editor',
@@ -340,33 +344,39 @@
             self.set_edit_menu_helper(self.parent, self.edit_custom_model)
             self.set_edit_menu_helper(self.parent, self.delete_custom_model)
-            temp = self.fit_panel.reset_pmodel_list()
-            if temp:
-                # Set the new plugin model list for all fit pages
-                for uid, page in self.fit_panel.opened_pages.iteritems():
-                    if hasattr(page, "formfactorbox"):
-                        page.model_list_box = temp
-                        current_val = page.formfactorbox.GetLabel()
-                        #if page.plugin_rbutton.GetValue():
-                        mod_cat = page.categorybox.GetStringSelection()
-                        if mod_cat == custom_model:
-                            #pos = page.formfactorbox.GetSelection()
-                            page._show_combox_helper()
-                            new_val = page.formfactorbox.GetLabel()
-                            if current_val != new_val and new_val != '':
-                                page.formfactorbox.SetLabel(new_val)
-                            else:
-                                page.formfactorbox.SetLabel(current_val)
-                        if hasattr(page, 'structurebox'):
-                            selected_name = page.structurebox.GetStringSelection()
-
-                            page.structurebox.Clear()
-                            page.initialize_combox()
-
-                            index = page.structurebox.FindString(selected_name)
-                            if index == -1:
-                                index = 0
-                            page.structurebox.SetSelection(index)
-                            page._on_select_model()
-        except:
+            new_pmodel_list = self.fit_panel.reset_pmodel_list()
+            if not new_pmodel_list:
+                return
+            # Set the new plugin model list for all fit pages
+            for uid, page in self.fit_panel.opened_pages.iteritems():
+                if hasattr(page, "formfactorbox"):
+                    page.model_list_box = new_pmodel_list
+                    mod_cat = page.categorybox.GetStringSelection()
+                    if mod_cat == custom_model:
+                        box = page.formfactorbox
+                        model_name = box.GetValue()
+                        model = (box.GetClientData(box.GetCurrentSelection())
+                                 if model_name else None)
+                        page._show_combox_helper()
+                        new_index = box.FindString(model_name)
+                        new_model = (box.GetClientData(new_index)
+                                     if new_index >= 0 else None)
+                        if new_index >= 0:
+                            box.SetStringSelection(model_name)
+                        else:
+                            box.SetStringSelection('')
+                        if model and new_model != model:
+                            page._on_select_model(keep_pars=True)
+                    if hasattr(page, "structurebox"):
+                        selected_name = page.structurebox.GetStringSelection()
+
+                        page.structurebox.Clear()
+                        page.initialize_combox()
+
+                        index = page.structurebox.FindString(selected_name)
+                        if index == -1:
+                            index = 0
+                        page.structurebox.SetSelection(index)
+                        page._on_select_model()
+        except Exception:
             logger.error("update_custom_combo: %s", sys.exc_value)
 
@@ -378,5 +388,5 @@
         #new_model_menu = wx.Menu()
         self.edit_model_menu.Append(wx_id, 'New Plugin Model',
-                                   'Add a new model function')
+                                    'Add a new model function')
         wx.EVT_MENU(owner, wx_id, self.make_new_model)
 
@@ -575,5 +585,4 @@
         else:
             if len(data_list) > MAX_NBR_DATA:
-                from fitting_widgets import DataDialog
                 dlg = DataDialog(data_list=data_list, nb_data=MAX_NBR_DATA)
                 if dlg.ShowModal() == wx.ID_OK:
@@ -620,11 +629,11 @@
         : param datainfo: data
         """
-        from pagestate import PageState
-        from simfitpage import SimFitPageState
         if isinstance(state, PageState):
             state = state.clone()
             self.temp_state.append(state)
         elif isinstance(state, SimFitPageState):
-            state.load_from_save_state(self)
+            if self.fit_panel.sim_page is None:
+                self.fit_panel.add_sim_page()
+            self.fit_panel.sim_page.load_from_save_state(state)
         else:
             self.temp_state = []
@@ -661,5 +670,5 @@
                 self.parent.add_data(data_list={data.id: data})
                 wx.PostEvent(self.parent, NewPlotEvent(plot=data,
-                                        title=data.title))
+                             title=data.title))
                 #need to be fix later make sure we are sendind guiframe.data
                 #to panel
@@ -672,9 +681,9 @@
                 self.parent.add_data(data_list={data.id: data})
                 wx.PostEvent(self.parent, NewPlotEvent(plot=data,
-                                        title=data.title))
+                             title=data.title))
                 page = self.add_fit_page([data])
                 caption = page.window_caption
                 self.store_data(uid=page.uid, data_list=page.get_data_list(),
-                        caption=caption)
+                                caption=caption)
                 self.mypanels.append(page)
 
@@ -793,5 +802,5 @@
         """
         if item.find(".") >= 0:
-            param_names = re.split("\.", item)
+            param_names = re.split(r"\.", item)
             model_name = param_names[0]
             ##Assume max len is 3; eg., M0.radius.width
@@ -896,6 +905,6 @@
 
             self.draw_model(model=model, data=data, page_id=uid, smearer=smear,
-                enable1D=enable1D, enable2D=enable2D,
-                qmin=qmin, qmax=qmax, weight=weight)
+                            enable1D=enable1D, enable2D=enable2D,
+                            qmin=qmin, qmax=qmax, weight=weight)
 
     def draw_model(self, model, page_id, data=None, smearer=None,
@@ -940,16 +949,16 @@
             ## draw model 2D with no initial data
             self._draw_model2D(model=model,
-                                page_id=page_id,
-                                data=data,
-                                enable2D=enable2D,
-                                smearer=smearer,
-                                qmin=qmin,
-                                qmax=qmax,
-                                fid=fid,
-                                weight=weight,
-                                state=state,
-                                toggle_mode_on=toggle_mode_on,
-                                update_chisqr=update_chisqr,
-                                source=source)
+                               page_id=page_id,
+                               data=data,
+                               enable2D=enable2D,
+                               smearer=smearer,
+                               qmin=qmin,
+                               qmax=qmax,
+                               fid=fid,
+                               weight=weight,
+                               state=state,
+                               toggle_mode_on=toggle_mode_on,
+                               update_chisqr=update_chisqr,
+                               source=source)
 
     def onFit(self, uid):
@@ -960,5 +969,6 @@
         :param uid: id related to the panel currently calling this fit function.
         """
-        if uid is None: raise RuntimeError("no page to fit") # Should never happen
+        if uid is None:
+            raise RuntimeError("no page to fit") # Should never happen
 
         sim_page_uid = getattr(self.sim_page, 'uid', None)
@@ -994,6 +1004,6 @@
                     page = self.fit_panel.get_page_by_id(page_id)
                     self.set_fit_weight(uid=page.uid,
-                                     flag=page.get_weight_flag(),
-                                     is2d=page._is_2D())
+                                        flag=page.get_weight_flag(),
+                                        is2d=page._is_2D())
                     if not page.param_toFit:
                         msg = "No fitting parameters for %s" % page.window_caption
@@ -1019,7 +1029,7 @@
                             fitter = sim_fitter
                         self._add_problem_to_fit(fitproblem=fitproblem,
-                                             pars=pars,
-                                             fitter=fitter,
-                                             fit_id=fit_id)
+                                                 pars=pars,
+                                                 fitter=fitter,
+                                                 fit_id=fit_id)
                         fit_id += 1
                     list_page_id.append(page_id)
@@ -1068,10 +1078,10 @@
             ## Perform more than 1 fit at the time
             calc_fit = FitThread(handler=handler,
-                                    fn=fitter_list,
-                                    batch_inputs=batch_inputs,
-                                    batch_outputs=batch_outputs,
-                                    page_id=list_page_id,
-                                    updatefn=handler.update_fit,
-                                    completefn=self._fit_completed)
+                                 fn=fitter_list,
+                                 batch_inputs=batch_inputs,
+                                 batch_outputs=batch_outputs,
+                                 page_id=list_page_id,
+                                 updatefn=handler.update_fit,
+                                 completefn=self._fit_completed)
         #self.fit_thread_list[current_page_id] = calc_fit
         self.fit_thread_list[uid] = calc_fit
@@ -1134,5 +1144,5 @@
                 evt = StatusEvent(status=msg, info="warning")
                 wx.PostEvent(self.parent, evt)
-        except:
+        except Exception:
             msg = "Creating Fit page: %s" % sys.exc_value
             wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
@@ -1158,8 +1168,8 @@
                     group_id = str(page.uid) + " Model1D"
                     wx.PostEvent(self.parent,
-                             NewPlotEvent(group_id=group_id,
-                                               action="delete"))
+                                 NewPlotEvent(group_id=group_id,
+                                              action="delete"))
                     self.parent.update_data(prev_data=theory_data,
-                                             new_data=data)
+                                            new_data=data)
             else:
                 if theory_data is not None:
@@ -1167,8 +1177,8 @@
                     data.group_id = theory_data.group_id
                     wx.PostEvent(self.parent,
-                             NewPlotEvent(group_id=group_id,
-                                               action="delete"))
+                                 NewPlotEvent(group_id=group_id,
+                                              action="delete"))
                     self.parent.update_data(prev_data=theory_data,
-                                             new_data=data)
+                                            new_data=data)
         self.store_data(uid=page.uid, data_list=page.get_data_list(),
                         caption=page.window_caption)
@@ -1479,5 +1489,4 @@
         if "Data" not in batch_outputs.keys():
             batch_outputs["Data"] = []
-        from sas.sasgui.guiframe.data_processor import BatchCell
         cell = BatchCell()
         cell.label = data.name
@@ -1579,14 +1588,14 @@
                     except KeyboardInterrupt:
                         fit_msg += "\nSingular point: Fitting stopped."
-                    except:
+                    except Exception:
                         fit_msg += "\nSingular point: Fitting error occurred."
                 if fit_msg:
-                   evt = StatusEvent(status=fit_msg, info="warning", type="stop")
-                   wx.PostEvent(self.parent, evt)
-
-        except:
+                    evt = StatusEvent(status=fit_msg, info="warning", type="stop")
+                    wx.PostEvent(self.parent, evt)
+
+        except Exception:
             msg = ("Fit completed but the following error occurred: %s"
                    % sys.exc_value)
-            #import traceback; msg = "\n".join((traceback.format_exc(), msg))
+            #msg = "\n".join((traceback.format_exc(), msg))
             evt = StatusEvent(status=msg, info="warning", type="stop")
             wx.PostEvent(self.parent, evt)
@@ -1743,5 +1752,5 @@
                                                   fid=data.id)
         self.parent.update_theory(data_id=data.id, theory=new_plot,
-                                   state=state)
+                                  state=state)
         return new_plot
 
@@ -1767,5 +1776,5 @@
         # Create the new theories
         if unsmeared_model is not None:
-            unsmeared_model_plot = self.create_theory_1D(x, unsmeared_model, 
+            unsmeared_model_plot = self.create_theory_1D(x, unsmeared_model,
                                   page_id, model, data, state,
                                   data_description=model.name + " unsmeared",
@@ -1774,5 +1783,5 @@
 
             if unsmeared_data is not None and unsmeared_error is not None:
-                unsmeared_data_plot = self.create_theory_1D(x, unsmeared_data, 
+                unsmeared_data_plot = self.create_theory_1D(x, unsmeared_data,
                                       page_id, model, data, state,
                                       data_description="Data unsmeared",
@@ -1792,5 +1801,5 @@
             plots_to_update.append(pq_plot)
         # Update the P(Q), S(Q) and unsmeared theory plots if they exist
-        wx.PostEvent(self.parent, NewPlotEvent(plots=plots_to_update, 
+        wx.PostEvent(self.parent, NewPlotEvent(plots=plots_to_update,
                                               action='update'))
 
@@ -1808,18 +1817,17 @@
 
         self.page_finder[page_id].set_theory_data(data=new_plot,
-                                                      fid=data.id)
+                                                  fid=data.id)
         if toggle_mode_on:
             wx.PostEvent(self.parent,
                          NewPlotEvent(group_id=str(page_id) + " Model2D",
-                                          action="Hide"))
+                                      action="Hide"))
         else:
             if update_chisqr:
-                wx.PostEvent(current_pg,
-                             Chi2UpdateEvent(output=self._cal_chisqr(
-                                                                data=data,
-                                                                fid=fid,
-                                                                weight=weight,
-                                                                page_id=page_id,
-                                                                index=index)))
+                output = self._cal_chisqr(data=data,
+                                          fid=fid,
+                                          weight=weight,
+                                          page_id=page_id,
+                                          index=index)
+                wx.PostEvent(current_pg, Chi2UpdateEvent(output=output))
             else:
                 self._plot_residuals(page_id=page_id, data=data, fid=fid,
@@ -1829,5 +1837,5 @@
             logger.error("Using the present parameters the model does not return any finite value. ")
             msg = "Computing Error: Model did not return any finite value."
-            wx.PostEvent(self.parent, StatusEvent(status = msg, info="error"))
+            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
         else:
             msg = "Computation  completed!"
@@ -1854,6 +1862,6 @@
 
     def _complete2D(self, image, data, model, page_id, elapsed, index, qmin,
-                qmax, fid=None, weight=None, toggle_mode_on=False, state=None,
-                     update_chisqr=True, source='model', plot_result=True):
+                    qmax, fid=None, weight=None, toggle_mode_on=False, state=None,
+                    update_chisqr=True, source='model', plot_result=True):
         """
         Complete get the result of modelthread and create model 2D
@@ -1896,32 +1904,31 @@
                                                   fid=data.id)
         self.parent.update_theory(data_id=data.id,
-                                       theory=new_plot,
-                                       state=state)
+                                  theory=new_plot,
+                                  state=state)
         current_pg = self.fit_panel.get_page_by_id(page_id)
         title = new_plot.title
         if not source == 'fit' and plot_result:
-            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot,
-                                               title=title))
+            wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title=title))
         if toggle_mode_on:
             wx.PostEvent(self.parent,
-                             NewPlotEvent(group_id=str(page_id) + " Model1D",
-                                               action="Hide"))
+                         NewPlotEvent(group_id=str(page_id) + " Model1D",
+                                      action="Hide"))
         else:
             # Chisqr in fitpage
             if update_chisqr:
-                wx.PostEvent(current_pg,
-                             Chi2UpdateEvent(output=self._cal_chisqr(data=data,
-                                                                    weight=weight,
-                                                                    fid=fid,
-                                                         page_id=page_id,
-                                                         index=index)))
+                output = self._cal_chisqr(data=data,
+                                          weight=weight,
+                                          fid=fid,
+                                          page_id=page_id,
+                                          index=index)
+                wx.PostEvent(current_pg, Chi2UpdateEvent(output=output))
             else:
                 self._plot_residuals(page_id=page_id, data=data, fid=fid,
-                                      index=index, weight=weight)
+                                     index=index, weight=weight)
 
         if not number_finite:
             logger.error("Using the present parameters the model does not return any finite value. ")
             msg = "Computing Error: Model did not return any finite value."
-            wx.PostEvent(self.parent, StatusEvent(status = msg, info="error"))
+            wx.PostEvent(self.parent, StatusEvent(status=msg, info="error"))
         else:
             msg = "Computation  completed!"
@@ -1939,5 +1946,5 @@
                       weight=None,
                       toggle_mode_on=False,
-                       update_chisqr=True, source='model'):
+                      update_chisqr=True, source='model'):
         """
         draw model in 2D
@@ -1954,5 +1961,4 @@
             return None
         try:
-            from model_thread import Calc2D
             ## If a thread is already started, stop it
             if (self.calc_2D is not None) and self.calc_2D.isrunning():
@@ -1986,9 +1992,7 @@
     def _draw_model1D(self, model, page_id, data,
                       qmin, qmax, smearer=None,
-                state=None,
-                weight=None,
-                fid=None,
-                toggle_mode_on=False, update_chisqr=True, source='model',
-                enable1D=True):
+                      state=None, weight=None, fid=None,
+                      toggle_mode_on=False, update_chisqr=True, source='model',
+                      enable1D=True):
         """
         Draw model 1D from loaded data1D
@@ -2001,5 +2005,4 @@
             return
         try:
-            from model_thread import Calc1D
             ## If a thread is already started, stop it
             if (self.calc_1D is not None) and self.calc_1D.isrunning():
Index: src/sas/sasgui/perspectives/fitting/model_thread.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/model_thread.py	(revision 0f9ea1cee80a92f71e4bec65998ca56a3de65fd6)
+++ src/sas/sasgui/perspectives/fitting/model_thread.py	(revision 0f9ea1cee80a92f71e4bec65998ca56a3de65fd6)
@@ -1,9 +1,11 @@
 """
-    Calculation thread for modeling
+Calculation thread for modeling
 """
 
 import time
+import math
+
 import numpy as np
-import math
+
 from sas.sascalc.data_util.calcthread import CalcThread
 from sas.sascalc.fit.MultiplicationModel import MultiplicationModel
@@ -28,5 +30,5 @@
                  worktime=0.04,
                  exception_handler=None,
-                 ):
+                ):
         CalcThread.__init__(self, completefn, updatefn, yieldtime, worktime,
                             exception_handler=exception_handler)
@@ -57,9 +59,7 @@
         if self.qmax is None:
             if self.data is not None:
-                newx = math.pow(max(math.fabs(self.data.xmax),
-                                   math.fabs(self.data.xmin)), 2)
-                newy = math.pow(max(math.fabs(self.data.ymax),
-                                   math.fabs(self.data.ymin)), 2)
-                self.qmax = math.sqrt(newx + newy)
+                newx = max(math.fabs(self.data.xmax), math.fabs(self.data.xmin))
+                newy = max(math.fabs(self.data.ymax), math.fabs(self.data.ymin))
+                self.qmax = math.sqrt(newx**2 + newy**2)
 
         if self.data is None:
@@ -68,12 +68,11 @@
 
         # Define matrix where data will be plotted
-        radius = np.sqrt((self.data.qx_data * self.data.qx_data) + \
-                    (self.data.qy_data * self.data.qy_data))
+        radius = np.sqrt(self.data.qx_data**2 + self.data.qy_data**2)
 
         # For theory, qmax is based on 1d qmax
         # so that must be mulitified by sqrt(2) to get actual max for 2d
         index_model = (self.qmin <= radius) & (radius <= self.qmax)
-        index_model = index_model & self.data.mask
-        index_model = index_model & np.isfinite(self.data.data)
+        index_model &= self.data.mask
+        index_model &= np.isfinite(self.data.data)
 
         if self.smearer is not None:
@@ -101,18 +100,18 @@
         elapsed = time.time() - self.starttime
         self.complete(image=output,
-                       data=self.data,
-                       page_id=self.page_id,
-                       model=self.model,
-                       state=self.state,
-                       toggle_mode_on=self.toggle_mode_on,
-                       elapsed=elapsed,
-                       index=index_model,
-                       fid=self.fid,
-                       qmin=self.qmin,
-                       qmax=self.qmax,
-                       weight=self.weight,
-                       #qstep=self.qstep,
-                       update_chisqr=self.update_chisqr,
-                       source=self.source)
+                      data=self.data,
+                      page_id=self.page_id,
+                      model=self.model,
+                      state=self.state,
+                      toggle_mode_on=self.toggle_mode_on,
+                      elapsed=elapsed,
+                      index=index_model,
+                      fid=self.fid,
+                      qmin=self.qmin,
+                      qmax=self.qmax,
+                      weight=self.weight,
+                      #qstep=self.qstep,
+                      update_chisqr=self.update_chisqr,
+                      source=self.source)
 
 
@@ -138,5 +137,5 @@
                  worktime=0.01,
                  exception_handler=None,
-                 ):
+                ):
         """
         """
@@ -193,7 +192,7 @@
                                                         * unsmeared_output[first_bin:last_bin+1]\
                                                         / output[first_bin:last_bin+1]
-                unsmeared_output=unsmeared_output[index]
-                unsmeared_data=unsmeared_data[index]
-                unsmeared_error=unsmeared_error
+                unsmeared_output = unsmeared_output[index]
+                unsmeared_data = unsmeared_data[index]
+                unsmeared_error = unsmeared_error
         else:
             output[index] = self.model.evalDistribution(self.data.x[index])
@@ -243,11 +242,11 @@
     class CalcCommandline:
         def __init__(self, n=20000):
-            #print thread.get_ident()
-            from sas.models.CylinderModel import CylinderModel
-
-            model = CylinderModel()
-
-
-            print model.runXY([0.01, 0.02])
+            #print(thread.get_ident())
+
+            from sasmodels.sasview_model import _make_standard_model
+            cylinder = _make_standard_model('cylinder')
+            model = cylinder()
+
+            print(model.runXY([0.01, 0.02]))
 
             qmax = 0.01
@@ -258,10 +257,9 @@
             y = numpy.arange(-qmax, qmax+qstep*0.01, qstep)
 
-
             calc_thread_2D = Calc2D(x, y, None, model.clone(),None,
                                     -qmax, qmax,qstep,
-                                            completefn=self.complete,
-                                            updatefn=self.update ,
-                                            yieldtime=0.0)
+                                    completefn=self.complete,
+                                    updatefn=self.update ,
+                                    yieldtime=0.0)
 
             calc_thread_2D.queue()
@@ -272,8 +270,8 @@
 
         def update(self,output):
-            print "update"
+            print("update")
 
         def complete(self, image, data, model, elapsed, qmin, qmax,index, qstep ):
-            print "complete"
+            print("complete")
             self.done = True
 
Index: src/sas/sasgui/perspectives/fitting/models.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/models.py	(revision 632fda90d2ac7346a8a5e823a4e5ad1fff44b05f)
+++ 	(revision )
@@ -1,505 +1,0 @@
-"""
-    Utilities to manage models
-"""
-from __future__ import print_function
-
-import traceback
-import os
-import sys
-import os.path
-# Time is needed by the log method
-import time
-import datetime
-import logging
-import py_compile
-import shutil
-from copy import copy
-
-from sasmodels.sasview_model import load_custom_model, load_standard_models
-
-# Explicitly import from the pluginmodel module so that py2exe
-# places it in the distribution. The Model1DPlugin class is used
-# as the base class of plug-in models.
-from sas.sasgui import get_user_dir
-from sas.sascalc.fit.pluginmodel import Model1DPlugin
-from sas.sasgui.guiframe.CategoryInstaller import CategoryInstaller
-from sas.sasgui.perspectives.fitting.fitpage import CUSTOM_MODEL
-
-logger = logging.getLogger(__name__)
-
-
-PLUGIN_DIR = 'plugin_models'
-PLUGIN_LOG = os.path.join(get_user_dir(), PLUGIN_DIR, "plugins.log")
-PLUGIN_NAME_BASE = '[plug-in] '
-
-def get_model_python_path():
-    """
-    Returns the python path for a model
-    """
-    return os.path.dirname(__file__)
-
-
-def plugin_log(message):
-    """
-    Log a message in a file located in the user's home directory
-    """
-    out = open(PLUGIN_LOG, 'a')
-    now = time.time()
-    stamp = datetime.datetime.fromtimestamp(now).strftime('%Y-%m-%d %H:%M:%S')
-    out.write("%s: %s\n" % (stamp, message))
-    out.close()
-
-
-def _check_plugin(model, name):
-    """
-    Do some checking before model adding plugins in the list
-
-    :param model: class model to add into the plugin list
-    :param name:name of the module plugin
-
-    :return model: model if valid model or None if not valid
-
-    """
-    #Check if the plugin is of type Model1DPlugin
-    if not issubclass(model, Model1DPlugin):
-        msg = "Plugin %s must be of type Model1DPlugin \n" % str(name)
-        plugin_log(msg)
-        return None
-    if model.__name__ != "Model":
-        msg = "Plugin %s class name must be Model \n" % str(name)
-        plugin_log(msg)
-        return None
-    try:
-        new_instance = model()
-    except:
-        msg = "Plugin %s error in __init__ \n\t: %s %s\n" % (str(name),
-                                                             str(sys.exc_type),
-                                                             sys.exc_info()[1])
-        plugin_log(msg)
-        return None
-
-    if hasattr(new_instance, "function"):
-        try:
-            value = new_instance.function()
-        except:
-            msg = "Plugin %s: error writing function \n\t :%s %s\n " % \
-                    (str(name), str(sys.exc_type), sys.exc_info()[1])
-            plugin_log(msg)
-            return None
-    else:
-        msg = "Plugin  %s needs a method called function \n" % str(name)
-        plugin_log(msg)
-        return None
-    return model
-
-
-def find_plugins_dir():
-    """
-    Find path of the plugins directory.
-    The plugin directory is located in the user's home directory.
-    """
-    dir = os.path.join(os.path.expanduser("~"), '.sasview', PLUGIN_DIR)
-
-    # If the plugin directory doesn't exist, create it
-    if not os.path.isdir(dir):
-        os.makedirs(dir)
-
-    # Find paths needed
-    try:
-        # For source
-        if os.path.isdir(os.path.dirname(__file__)):
-            p_dir = os.path.join(os.path.dirname(__file__), PLUGIN_DIR)
-        else:
-            raise
-    except:
-        # Check for data path next to exe/zip file.
-        #Look for maximum n_dir up of the current dir to find plugins dir
-        n_dir = 12
-        p_dir = None
-        f_dir = os.path.join(os.path.dirname(__file__))
-        for i in range(n_dir):
-            if i > 1:
-                f_dir, _ = os.path.split(f_dir)
-            plugin_path = os.path.join(f_dir, PLUGIN_DIR)
-            if os.path.isdir(plugin_path):
-                p_dir = plugin_path
-                break
-        if not p_dir:
-            raise
-    # Place example user models as needed
-    if os.path.isdir(p_dir):
-        for file in os.listdir(p_dir):
-            file_path = os.path.join(p_dir, file)
-            if os.path.isfile(file_path):
-                if file.split(".")[-1] == 'py' and\
-                    file.split(".")[0] != '__init__':
-                    if not os.path.isfile(os.path.join(dir, file)):
-                        shutil.copy(file_path, dir)
-
-    return dir
-
-
-class ReportProblem:
-    """
-    Class to check for problems with specific values
-    """
-    def __nonzero__(self):
-        type, value, tb = sys.exc_info()
-        if type is not None and issubclass(type, py_compile.PyCompileError):
-            print("Problem with", repr(value))
-            raise type, value, tb
-        return 1
-
-report_problem = ReportProblem()
-
-
-def compile_file(dir):
-    """
-    Compile a py file
-    """
-    try:
-        import compileall
-        compileall.compile_dir(dir=dir, ddir=dir, force=0,
-                               quiet=report_problem)
-    except:
-        return sys.exc_info()[1]
-    return None
-
-
-def _find_models():
-    """
-    Find custom models
-    """
-    # List of plugin objects
-    directory = find_plugins_dir()
-    # Go through files in plug-in directory
-    if not os.path.isdir(directory):
-        msg = "SasView couldn't locate Model plugin folder %r." % directory
-        logger.warning(msg)
-        return {}
-
-    plugin_log("looking for models in: %s" % str(directory))
-    # compile_file(directory)  #always recompile the folder plugin
-    logger.info("plugin model dir: %s" % str(directory))
-
-    plugins = {}
-    for filename in os.listdir(directory):
-        name, ext = os.path.splitext(filename)
-        if ext == '.py' and not name == '__init__':
-            path = os.path.abspath(os.path.join(directory, filename))
-            try:
-                model = load_custom_model(path)
-                if not model.name.count(PLUGIN_NAME_BASE):
-                    model.name = PLUGIN_NAME_BASE + model.name
-                plugins[model.name] = model
-            except Exception:
-                msg = traceback.format_exc()
-                msg += "\nwhile accessing model in %r" % path
-                plugin_log(msg)
-                logger.warning("Failed to load plugin %r. See %s for details"
-                               % (path, PLUGIN_LOG))
-
-    return plugins
-
-
-class ModelList(object):
-    """
-    Contains dictionary of model and their type
-    """
-    def __init__(self):
-        """
-        """
-        self.mydict = {}
-
-    def set_list(self, name, mylist):
-        """
-        :param name: the type of the list
-        :param mylist: the list to add
-
-        """
-        if name not in self.mydict.keys():
-            self.reset_list(name, mylist)
-
-    def reset_list(self, name, mylist):
-        """
-        :param name: the type of the list
-        :param mylist: the list to add
-        """
-        self.mydict[name] = mylist
-
-    def get_list(self):
-        """
-        return all the list stored in a dictionary object
-        """
-        return self.mydict
-
-
-class ModelManagerBase:
-    """
-    Base class for the model manager
-    """
-    ## external dict for models
-    model_combobox = ModelList()
-    ## Dictionary of form factor models
-    form_factor_dict = {}
-    ## dictionary of structure factor models
-    struct_factor_dict = {}
-    ##list of structure factors
-    struct_list = []
-    ##list of model allowing multiplication by a structure factor
-    multiplication_factor = []
-    ##list of multifunctional shapes (i.e. that have user defined number of levels
-    multi_func_list = []
-    ## list of added models -- currently python models found in the plugin dir.
-    plugins = []
-    ## Event owner (guiframe)
-    event_owner = None
-    last_time_dir_modified = 0
-
-    def __init__(self):
-        self.model_dictionary = {}
-        self.stored_plugins = {}
-        self._getModelList()
-
-    def findModels(self):
-        """
-        find  plugin model in directory of plugin .recompile all file
-        in the directory if file were modified
-        """
-        temp = {}
-        if self.is_changed():
-            temp =  _find_models()
-            self.last_time_dir_modified = time.time()
-            return temp
-        logger.info("plugin model : %s" % str(temp))
-        return temp
-
-    def _getModelList(self):
-        """
-        List of models we want to make available by default
-        for this application
-
-        :return: the next free event ID following the new menu events
-
-        """
-
-        # Regular model names only
-        self.model_name_list = []
-
-        # Build list automagically from sasmodels package
-        for model in load_standard_models():
-            self.model_dictionary[model.name] = model
-            if model.is_structure_factor:
-                self.struct_list.append(model)
-            if model.is_form_factor:
-                self.multiplication_factor.append(model)
-            if model.is_multiplicity_model:
-                self.multi_func_list.append(model)
-            else:
-                self.model_name_list.append(model.name)
-
-        # Looking for plugins
-        self.stored_plugins = self.findModels()
-        self.plugins = self.stored_plugins.values()
-        for name, plug in self.stored_plugins.iteritems():
-            self.model_dictionary[name] = plug
-            # TODO: Remove 'hasattr' statements when old style plugin models
-            # are no longer supported. All sasmodels models will have
-            # the required attributes.
-            if hasattr(plug, 'is_structure_factor') and plug.is_structure_factor:
-                self.struct_list.append(plug)
-                self.plugins.remove(plug)
-            elif hasattr(plug, 'is_form_factor') and plug.is_form_factor:
-                self.multiplication_factor.append(plug)
-            if hasattr(plug, 'is_multiplicity_model') and plug.is_multiplicity_model:
-                self.multi_func_list.append(plug)
-
-        self._get_multifunc_models()
-
-        return 0
-
-    def is_changed(self):
-        """
-        check the last time the plugin dir has changed and return true
-        is the directory was modified else return false
-        """
-        is_modified = False
-        plugin_dir = find_plugins_dir()
-        if os.path.isdir(plugin_dir):
-            temp = os.path.getmtime(plugin_dir)
-            if  self.last_time_dir_modified < temp:
-                is_modified = True
-                self.last_time_dir_modified = temp
-
-        return is_modified
-
-    def update(self):
-        """
-        return a dictionary of model if
-        new models were added else return empty dictionary
-        """
-        self.plugins = []
-        new_plugins = self.findModels()
-        if new_plugins:
-            for name, plug in  new_plugins.items():
-                self.stored_plugins[name] = plug
-                self.plugins.append(plug)
-                self.model_dictionary[name] = plug
-            self.model_combobox.set_list(CUSTOM_MODEL, self.plugins)
-            return self.model_combobox.get_list()
-        else:
-            return {}
-
-    def plugins_reset(self):
-        """
-        return a dictionary of model
-        """
-        self.plugins = []
-        self.stored_plugins = _find_models()
-        structure_names = [model.name for model in self.struct_list]
-        form_names = [model.name for model in self.multiplication_factor]
-
-        # Remove all plugin structure factors and form factors
-        for name in copy(structure_names):
-            if '[plug-in]' in name:
-                i = structure_names.index(name)
-                del self.struct_list[i]
-                structure_names.remove(name)
-        for name in copy(form_names):
-            if '[plug-in]' in name:
-                i = form_names.index(name)
-                del self.multiplication_factor[i]
-                form_names.remove(name)
-
-        # Add new plugin structure factors and form factors
-        for name, plug in self.stored_plugins.iteritems():
-            if plug.is_structure_factor:
-                if name in structure_names:
-                    # Delete the old model from self.struct list
-                    i = structure_names.index(name)
-                    del self.struct_list[i]
-                # Add the new model to self.struct_list
-                self.struct_list.append(plug)
-            elif plug.is_form_factor:
-                if name in form_names:
-                    # Delete the old model from self.multiplication_factor
-                    i = form_names.index(name)
-                    del self.multiplication_factor[i]
-                # Add the new model to self.multiplication_factor
-                self.multiplication_factor.append(plug)
-
-            # Add references to the updated model
-            self.stored_plugins[name] = plug
-            if not plug.is_structure_factor:
-                # Don't show S(Q) models in the 'Plugin Models' dropdown
-                self.plugins.append(plug)
-            self.model_dictionary[name] = plug
-
-        self.model_combobox.reset_list("Plugin Models", self.plugins)
-        self.model_combobox.reset_list("Structure Factors", self.struct_list)
-        self.model_combobox.reset_list("P(Q)*S(Q)", self.multiplication_factor)
-
-        return self.model_combobox.get_list()
-
-    def _on_model(self, evt):
-        """
-        React to a model menu event
-
-        :param event: wx menu event
-
-        """
-        if int(evt.GetId()) in self.form_factor_dict.keys():
-            from sasmodels.sasview_model import MultiplicationModel
-            self.model_dictionary[MultiplicationModel.__name__] = MultiplicationModel
-            model1, model2 = self.form_factor_dict[int(evt.GetId())]
-            model = MultiplicationModel(model1, model2)
-        else:
-            model = self.struct_factor_dict[str(evt.GetId())]()
-
-
-    def _get_multifunc_models(self):
-        """
-        Get the multifunctional models
-        """
-        items = [item for item in self.plugins if item.is_multiplicity_model]
-        self.multi_func_list = items
-
-    def get_model_list(self):
-        """
-        return dictionary of models for fitpanel use
-
-        """
-        ## Model_list now only contains attribute lists not category list.
-        ## Eventually this should be in one master list -- read in category
-        ## list then pull those models that exist and get attributes then add
-        ## to list ..and if model does not exist remove from list as now
-        ## and update json file.
-        ##
-        ## -PDB   April 26, 2014
-
-#        self.model_combobox.set_list("Shapes", self.shape_list)
-#        self.model_combobox.set_list("Shape-Independent",
-#                                     self.shape_indep_list)
-        self.model_combobox.set_list("Structure Factors", self.struct_list)
-        self.model_combobox.set_list("Plugin Models", self.plugins)
-        self.model_combobox.set_list("P(Q)*S(Q)", self.multiplication_factor)
-        self.model_combobox.set_list("multiplication",
-                                     self.multiplication_factor)
-        self.model_combobox.set_list("Multi-Functions", self.multi_func_list)
-        return self.model_combobox.get_list()
-
-    def get_model_name_list(self):
-        """
-        return regular model name list
-        """
-        return self.model_name_list
-
-    def get_model_dictionary(self):
-        """
-        return dictionary linking model names to objects
-        """
-        return self.model_dictionary
-
-
-class ModelManager(object):
-    """
-    implement model
-    """
-    __modelmanager = ModelManagerBase()
-    cat_model_list = [__modelmanager.model_dictionary[model_name] for model_name \
-                      in __modelmanager.model_dictionary.keys() \
-                      if model_name not in __modelmanager.stored_plugins.keys()]
-
-    CategoryInstaller.check_install(model_list=cat_model_list)
-    def findModels(self):
-        return self.__modelmanager.findModels()
-
-    def _getModelList(self):
-        return self.__modelmanager._getModelList()
-
-    def is_changed(self):
-        return self.__modelmanager.is_changed()
-
-    def update(self):
-        return self.__modelmanager.update()
-
-    def plugins_reset(self):
-        return self.__modelmanager.plugins_reset()
-
-    def populate_menu(self, modelmenu, event_owner):
-        return self.__modelmanager.populate_menu(modelmenu, event_owner)
-
-    def _on_model(self, evt):
-        return self.__modelmanager._on_model(evt)
-
-    def _get_multifunc_models(self):
-        return self.__modelmanager._get_multifunc_models()
-
-    def get_model_list(self):
-        return self.__modelmanager.get_model_list()
-
-    def get_model_name_list(self):
-        return self.__modelmanager.get_model_name_list()
-
-    def get_model_dictionary(self):
-        return self.__modelmanager.get_model_dictionary()
Index: src/sas/sasgui/perspectives/fitting/pagestate.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/pagestate.py	(revision 1fa4f7368e26981352615cbfb337a581ed97031f)
+++ 	(revision )
@@ -1,1453 +1,0 @@
-"""
-    Class that holds a fit page state
-"""
-# TODO: Refactor code so we don't need to use getattr/setattr
-################################################################################
-# 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 time
-import os
-import sys
-import wx
-import copy
-import logging
-import numpy as np
-import traceback
-
-import xml.dom.minidom
-from xml.dom.minidom import parseString
-from lxml import etree
-
-from sasmodels import convert
-import sasmodels.weights
-
-import sas.sascalc.dataloader
-from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
-from sas.sascalc.dataloader.readers.cansas_reader import get_content, write_node
-from sas.sascalc.dataloader.data_info import Data2D, Collimation, Detector
-from sas.sascalc.dataloader.data_info import Process, Aperture
-
-logger = logging.getLogger(__name__)
-
-# Information to read/write state as xml
-FITTING_NODE_NAME = 'fitting_plug_in'
-CANSAS_NS = "cansas1d/1.0"
-
-CUSTOM_MODEL = 'Plugin Models'
-CUSTOM_MODEL_OLD = 'Customized Models'
-
-LIST_OF_DATA_ATTRIBUTES = [["is_data", "is_data", "bool"],
-                           ["group_id", "data_group_id", "string"],
-                           ["data_name", "data_name", "string"],
-                           ["data_id", "data_id", "string"],
-                           ["name", "name", "string"],
-                           ["data_name", "data_name", "string"]]
-LIST_OF_STATE_ATTRIBUTES = [["qmin", "qmin", "float"],
-                            ["qmax", "qmax", "float"],
-                            ["npts", "npts", "float"],
-                            ["categorycombobox", "categorycombobox", "string"],
-                            ["formfactorcombobox", "formfactorcombobox",
-                             "string"],
-                            ["structurecombobox", "structurecombobox",
-                             "string"],
-                            ["multi_factor", "multi_factor", "float"],
-                            ["magnetic_on", "magnetic_on", "bool"],
-                            ["enable_smearer", "enable_smearer", "bool"],
-                            ["disable_smearer", "disable_smearer", "bool"],
-                            ["pinhole_smearer", "pinhole_smearer", "bool"],
-                            ["slit_smearer", "slit_smearer", "bool"],
-                            ["enable_disp", "enable_disp", "bool"],
-                            ["disable_disp", "disable_disp", "bool"],
-                            ["dI_noweight", "dI_noweight", "bool"],
-                            ["dI_didata", "dI_didata", "bool"],
-                            ["dI_sqrdata", "dI_sqrdata", "bool"],
-                            ["dI_idata", "dI_idata", "bool"],
-                            ["enable2D", "enable2D", "bool"],
-                            ["cb1", "cb1", "bool"],
-                            ["tcChi", "tcChi", "float"],
-                            ["smearer", "smearer", "float"],
-                            ["smear_type", "smear_type", "string"],
-                            ["dq_l", "dq_l", "float"],
-                            ["dq_r", "dq_r", "float"],
-                            ["dx_percent", "dx_percent", "float"],
-                            ["dxl", "dxl", "float"],
-                            ["dxw", "dxw", "float"]]
-
-LIST_OF_MODEL_ATTRIBUTES = [["values", "values"],
-                            ["weights", "weights"]]
-
-DISPERSION_LIST = [["disp_obj_dict", "_disp_obj_dict", "string"]]
-
-LIST_OF_STATE_PARAMETERS = [["parameters", "parameters"],
-                            ["str_parameters", "str_parameters"],
-                            ["orientation_parameters", "orientation_params"],
-                            ["dispersity_parameters",
-                             "orientation_params_disp"],
-                            ["fixed_param", "fixed_param"],
-                            ["fittable_param", "fittable_param"]]
-LIST_OF_DATA_2D_ATTR = [["xmin", "xmin", "float"],
-                        ["xmax", "xmax", "float"],
-                        ["ymin", "ymin", "float"],
-                        ["ymax", "ymax", "float"],
-                        ["_xaxis", "_xaxis", "string"],
-                        ["_xunit", "_xunit", "string"],
-                        ["_yaxis", "_yaxis", "string"],
-                        ["_yunit", "_yunit", "string"],
-                        ["_zaxis", "_zaxis", "string"],
-                        ["_zunit", "_zunit", "string"]]
-LIST_OF_DATA_2D_VALUES = [["qx_data", "qx_data", "float"],
-                          ["qy_data", "qy_data", "float"],
-                          ["dqx_data", "dqx_data", "float"],
-                          ["dqy_data", "dqy_data", "float"],
-                          ["data", "data", "float"],
-                          ["q_data", "q_data", "float"],
-                          ["err_data", "err_data", "float"],
-                          ["mask", "mask", "bool"]]
-
-
-def parse_entry_helper(node, item):
-    """
-    Create a numpy list from value extrated from the node
-
-    :param node: node from each the value is stored
-    :param item: list name of three strings.the two first are name of data
-        attribute and the third one is the type of the value of that
-        attribute. type can be string, float, bool, etc.
-
-    : return: numpy array
-    """
-    if node is not None:
-        if item[2] == "string":
-            return str(node.get(item[0]).strip())
-        elif item[2] == "bool":
-            try:
-                return node.get(item[0]).strip() == "True"
-            except Exception:
-                return None
-        else:
-            try:
-                return float(node.get(item[0]))
-            except Exception:
-                return None
-
-
-class PageState(object):
-    """
-    Contains information to reconstruct a page of the fitpanel.
-    """
-    def __init__(self, parent=None, model=None, data=None):
-        """
-        Initialize the current state
-
-        :param model: a selected model within a page
-        :param data:
-
-        """
-        self.file = None
-        # Time of state creation
-        self.timestamp = time.time()
-        # Data member to store the dispersion object created
-        self._disp_obj_dict = {}
-        # ------------------------
-        # Data used for fitting
-        self.data = data
-        # model data
-        self.theory_data = None
-        # Is 2D
-        self.is_2D = False
-        self.images = None
-
-        # save additional information on data that dataloader.reader
-        # does not read
-        self.is_data = None
-        self.data_name = ""
-
-        if self.data is not None:
-            self.data_name = self.data.name
-        self.data_id = None
-        if self.data is not None and hasattr(self.data, "id"):
-            self.data_id = self.data.id
-        self.data_group_id = None
-        if self.data is not None and hasattr(self.data, "group_id"):
-            self.data_group_id = self.data.group_id
-
-        # reset True change the state of existing button
-        self.reset = False
-
-        # flag to allow data2D plot
-        self.enable2D = False
-        # model on which the fit would be performed
-        self.model = model
-        self.m_name = None
-        # list of process done to model
-        self.process = []
-        # fit page manager
-        self.manager = None
-        # Store the parent of this panel parent
-        # For this application fitpanel is the parent
-        self.parent = parent
-        # Event_owner is the owner of model event
-        self.event_owner = None
-        # page name
-        self.page_name = ""
-        # Contains link between model, its parameters, and panel organization
-        self.parameters = []
-        # String parameter list that can not be fitted
-        self.str_parameters = []
-        # Contains list of parameters that cannot be fitted and reference to
-        # panel objects
-        self.fixed_param = []
-        # Contains list of parameters with dispersity and reference to
-        # panel objects
-        self.fittable_param = []
-        # orientation parameters
-        self.orientation_params = []
-        # orientation parameters for gaussian dispersity
-        self.orientation_params_disp = []
-        # smearer info
-        self.smearer = None
-        self.smear_type = None
-        self.dq_l = None
-        self.dq_r = None
-        self.dx_percent = None
-        self.dx_old = False
-        self.dxl = None
-        self.dxw = None
-        # list of dispersion parameters
-        self.disp_list = []
-        if self.model is not None:
-            self.disp_list = self.model.getDispParamList()
-
-        self.disp_cb_dict = {}
-        self.values = {}
-        self.weights = {}
-
-        # contains link between a model and selected parameters to fit
-        self.param_toFit = []
-        # dictionary of model type and model class
-        self.model_list_box = None
-        # save the state of the context menu
-        self.saved_states = {}
-        # save selection of combobox
-        self.formfactorcombobox = None
-        self.categorycombobox = None
-        self.structurecombobox = None
-
-        # radio box to select type of model
-        # self.shape_rbutton = False
-        # self.shape_indep_rbutton = False
-        # self.struct_rbutton = False
-        # self.plugin_rbutton = False
-        # the indice of the current selection
-        self.disp_box = 0
-        # Qrange
-        # Q range
-        self.qmin = 0.001
-        self.qmax = 0.1
-        # reset data range
-        self.qmax_x = None
-        self.qmin_x = None
-
-        self.npts = None
-        self.name = ""
-        self.multi_factor = None
-        self.magnetic_on = False
-        # enable smearering state
-        self.enable_smearer = False
-        self.disable_smearer = True
-        self.pinhole_smearer = False
-        self.slit_smearer = False
-        # weighting options
-        self.dI_noweight = False
-        self.dI_didata = True
-        self.dI_sqrdata = False
-        self.dI_idata = False
-        # disperity selection
-        self.enable_disp = False
-        self.disable_disp = True
-
-        # state of selected all check button
-        self.cb1 = False
-        # store value of chisqr
-        self.tcChi = None
-        self.version = (1,0,0)
-
-    def clone(self):
-        """
-        Create a new copy of the current object
-        """
-        model = None
-        if self.model is not None:
-            model = self.model.clone()
-            model.name = self.model.name
-        obj = PageState(self.parent, model=model)
-        obj.file = copy.deepcopy(self.file)
-        obj.data = copy.deepcopy(self.data)
-        if self.data is not None:
-            self.data_name = self.data.name
-        obj.data_name = self.data_name
-        obj.is_data = self.is_data
-        obj.model_list_box = copy.deepcopy(self.model_list_box)
-
-        obj.categorycombobox = self.categorycombobox
-        obj.formfactorcombobox = self.formfactorcombobox
-        obj.structurecombobox = self.structurecombobox
-
-        # obj.shape_rbutton = self.shape_rbutton
-        # obj.shape_indep_rbutton = self.shape_indep_rbutton
-        # obj.struct_rbutton = self.struct_rbutton
-        # obj.plugin_rbutton = self.plugin_rbutton
-
-        obj.manager = self.manager
-        obj.event_owner = self.event_owner
-        obj.disp_list = copy.deepcopy(self.disp_list)
-
-        obj.enable2D = copy.deepcopy(self.enable2D)
-        obj.parameters = copy.deepcopy(self.parameters)
-        obj.str_parameters = copy.deepcopy(self.str_parameters)
-        obj.fixed_param = copy.deepcopy(self.fixed_param)
-        obj.fittable_param = copy.deepcopy(self.fittable_param)
-        obj.orientation_params = copy.deepcopy(self.orientation_params)
-        obj.orientation_params_disp = \
-            copy.deepcopy(self.orientation_params_disp)
-        obj.enable_disp = copy.deepcopy(self.enable_disp)
-        obj.disable_disp = copy.deepcopy(self.disable_disp)
-        obj.tcChi = self.tcChi
-
-        if len(self._disp_obj_dict) > 0:
-            for k, v in self._disp_obj_dict.iteritems():
-                obj._disp_obj_dict[k] = v
-        if len(self.disp_cb_dict) > 0:
-            for k, v in self.disp_cb_dict.iteritems():
-                obj.disp_cb_dict[k] = v
-        if len(self.values) > 0:
-            for k, v in self.values.iteritems():
-                obj.values[k] = v
-        if len(self.weights) > 0:
-            for k, v in self.weights.iteritems():
-                obj.weights[k] = v
-        obj.enable_smearer = copy.deepcopy(self.enable_smearer)
-        obj.disable_smearer = copy.deepcopy(self.disable_smearer)
-        obj.pinhole_smearer = copy.deepcopy(self.pinhole_smearer)
-        obj.slit_smearer = copy.deepcopy(self.slit_smearer)
-        obj.smear_type = copy.deepcopy(self.smear_type)
-        obj.dI_noweight = copy.deepcopy(self.dI_noweight)
-        obj.dI_didata = copy.deepcopy(self.dI_didata)
-        obj.dI_sqrdata = copy.deepcopy(self.dI_sqrdata)
-        obj.dI_idata = copy.deepcopy(self.dI_idata)
-        obj.dq_l = copy.deepcopy(self.dq_l)
-        obj.dq_r = copy.deepcopy(self.dq_r)
-        obj.dx_percent = copy.deepcopy(self.dx_percent)
-        obj.dx_old = copy.deepcopy(self.dx_old)
-        obj.dxl = copy.deepcopy(self.dxl)
-        obj.dxw = copy.deepcopy(self.dxw)
-        obj.disp_box = copy.deepcopy(self.disp_box)
-        obj.qmin = copy.deepcopy(self.qmin)
-        obj.qmax = copy.deepcopy(self.qmax)
-        obj.multi_factor = self.multi_factor
-        obj.magnetic_on = self.magnetic_on
-        obj.npts = copy.deepcopy(self.npts)
-        obj.cb1 = copy.deepcopy(self.cb1)
-        obj.smearer = copy.deepcopy(self.smearer)
-        obj.version = copy.deepcopy(self.version)
-
-        for name, state in self.saved_states.iteritems():
-            copy_name = copy.deepcopy(name)
-            copy_state = state.clone()
-            obj.saved_states[copy_name] = copy_state
-        return obj
-
-    def _old_first_model(self):
-        """
-        Handle save states from 4.0.1 and before where the first item in the
-        selection boxes of category, formfactor and structurefactor were not
-        saved.
-        :return: None
-        """
-        if self.categorycombobox == CUSTOM_MODEL_OLD:
-            self.categorycombobox = CUSTOM_MODEL
-        if self.formfactorcombobox == '':
-            FIRST_FORM = {
-                'Shapes' : 'BCCrystalModel',
-                'Uncategorized' : 'LineModel',
-                'StructureFactor' : 'HardsphereStructure',
-                'Ellipsoid' : 'core_shell_ellipsoid',
-                'Lamellae' : 'lamellar',
-                'Paracrystal' : 'bcc_paracrystal',
-                'Parallelepiped' : 'core_shell_parallelepiped',
-                'Shape Independent' : 'be_polyelectrolyte',
-                'Sphere' : 'adsorbed_layer',
-                'Structure Factor' : 'hardsphere',
-                CUSTOM_MODEL : ''
-            }
-            if self.categorycombobox == '':
-                if len(self.parameters) == 3:
-                    self.categorycombobox = "Shape-Independent"
-                    self.formfactorcombobox = 'PowerLawAbsModel'
-                elif len(self.parameters) == 9:
-                    self.categorycombobox = 'Cylinder'
-                    self.formfactorcombobox = 'barbell'
-                else:
-                    msg = "Save state does not have enough information to load"
-                    msg += " the all of the data."
-                    logger.warning(msg=msg)
-            else:
-                self.formfactorcombobox = FIRST_FORM[self.categorycombobox]
-
-    @staticmethod
-    def param_remap_to_sasmodels_convert(params, is_string=False):
-        """
-        Remaps the parameters for sasmodels conversion
-
-        :param params: list of parameters (likely self.parameters)
-        :return: remapped dictionary of parameters
-        """
-        p = dict()
-        for fittable, name, value, _, uncert, lower, upper, units in params:
-            if not value:
-                value = np.nan
-            if not uncert or uncert[1] == '' or uncert[1] == 'None':
-                uncert[0] = False
-                uncert[1] = np.nan
-            if not upper or upper[1] == '' or upper[1] == 'None':
-                upper[0] = False
-                upper[1] = np.nan
-            if not lower or lower[1] == '' or lower[1] == 'None':
-                lower[0] = False
-                lower[1] = np.nan
-            if is_string:
-                p[name] = str(value)
-            else:
-                p[name] = float(value)
-            p[name + ".fittable"] = bool(fittable)
-            p[name + ".std"] = float(uncert[1])
-            p[name + ".upper"] = float(upper[1])
-            p[name + ".lower"] = float(lower[1])
-            p[name + ".units"] = units
-        return p
-
-    @staticmethod
-    def param_remap_from_sasmodels_convert(params):
-        """
-        Converts {name : value} map back to [] param list
-        :param params: parameter map returned from sasmodels
-        :return: None
-        """
-        p_map = []
-        for name, info in params.iteritems():
-            if ".fittable" in name or ".std" in name or ".upper" in name or \
-                            ".lower" in name or ".units" in name:
-                pass
-            else:
-                fittable = params.get(name + ".fittable", True)
-                std = params.get(name + ".std", '0.0')
-                upper = params.get(name + ".upper", 'inf')
-                lower = params.get(name + ".lower", '-inf')
-                units = params.get(name + ".units")
-                if std is not None and std is not np.nan:
-                    std = [True, str(std)]
-                else:
-                    std = [False, '']
-                if lower is not None and lower is not np.nan:
-                    lower = [True, str(lower)]
-                else:
-                    lower = [True, '-inf']
-                if upper is not None and upper is not np.nan:
-                    upper = [True, str(upper)]
-                else:
-                    upper = [True, 'inf']
-                param_list = [bool(fittable), str(name), str(info),
-                              "+/-", std, lower, upper, str(units)]
-                p_map.append(param_list)
-        return p_map
-
-    def _convert_to_sasmodels(self):
-        """
-        Convert parameters to a form usable by sasmodels converter
-
-        :return: None
-        """
-        # Create conversion dictionary to send to sasmodels
-        self._old_first_model()
-        p = self.param_remap_to_sasmodels_convert(self.parameters)
-        structurefactor, params = convert.convert_model(self.structurecombobox,
-                                                        p, False, self.version)
-        formfactor, params = convert.convert_model(self.formfactorcombobox,
-                                                   params, False, self.version)
-        if len(self.str_parameters) > 0:
-            str_pars = self.param_remap_to_sasmodels_convert(
-                self.str_parameters, True)
-            formfactor, str_params = convert.convert_model(
-                self.formfactorcombobox, str_pars, False, self.version)
-            for key, value in str_params.iteritems():
-                params[key] = value
-
-        if self.formfactorcombobox == 'SphericalSLDModel':
-            self.multi_factor += 1
-        self.formfactorcombobox = formfactor
-        self.structurecombobox = structurefactor
-        self.parameters = []
-        self.parameters = self.param_remap_from_sasmodels_convert(params)
-
-    def _repr_helper(self, list, rep):
-        """
-        Helper method to print a state
-        """
-        for item in list:
-            rep += "parameter name: %s \n" % str(item[1])
-            rep += "value: %s\n" % str(item[2])
-            rep += "selected: %s\n" % str(item[0])
-            rep += "error displayed : %s \n" % str(item[4][0])
-            rep += "error value:%s \n" % str(item[4][1])
-            rep += "minimum displayed : %s \n" % str(item[5][0])
-            rep += "minimum value : %s \n" % str(item[5][1])
-            rep += "maximum displayed : %s \n" % str(item[6][0])
-            rep += "maximum value : %s \n" % str(item[6][1])
-            rep += "parameter unit: %s\n\n" % str(item[7])
-        return rep
-
-    def __repr__(self):
-        """
-        output string for printing
-        """
-        rep = "\nState name: %s\n" % self.file
-        t = time.localtime(self.timestamp)
-        time_str = time.strftime("%b %d %Y %H;%M;%S ", t)
-
-        rep += "State created: %s\n" % time_str
-        rep += "State form factor combobox selection: %s\n" % \
-               self.formfactorcombobox
-        rep += "State structure factor combobox selection: %s\n" % \
-               self.structurecombobox
-        rep += "is data : %s\n" % self.is_data
-        rep += "data's name : %s\n" % self.data_name
-        rep += "data's id : %s\n" % self.data_id
-        if self.model is not None:
-            m_name = self.model.__class__.__name__
-            if m_name == 'Model':
-                m_name = self.m_name
-            rep += "model name : %s\n" % m_name
-        else:
-            rep += "model name : None\n"
-        rep += "multi_factor : %s\n" % str(self.multi_factor)
-        rep += "magnetic_on : %s\n" % str(self.magnetic_on)
-        rep += "model type (Category) selected: %s\n" % self.categorycombobox
-        rep += "data : %s\n" % str(self.data)
-        rep += "Plotting Range: min: %s, max: %s, steps: %s\n" % \
-               (str(self.qmin), str(self.qmax), str(self.npts))
-        rep += "Dispersion selection : %s\n" % str(self.disp_box)
-        rep += "Smearing enable : %s\n" % str(self.enable_smearer)
-        rep += "Smearing disable : %s\n" % str(self.disable_smearer)
-        rep += "Pinhole smearer enable : %s\n" % str(self.pinhole_smearer)
-        rep += "Slit smearer enable : %s\n" % str(self.slit_smearer)
-        rep += "Dispersity enable : %s\n" % str(self.enable_disp)
-        rep += "Dispersity disable : %s\n" % str(self.disable_disp)
-        rep += "Slit smearer enable: %s\n" % str(self.slit_smearer)
-
-        rep += "dI_noweight : %s\n" % str(self.dI_noweight)
-        rep += "dI_didata : %s\n" % str(self.dI_didata)
-        rep += "dI_sqrdata : %s\n" % str(self.dI_sqrdata)
-        rep += "dI_idata : %s\n" % str(self.dI_idata)
-
-        rep += "2D enable : %s\n" % str(self.enable2D)
-        rep += "All parameters checkbox selected: %s\n" % self.cb1
-        rep += "Value of Chisqr : %s\n" % str(self.tcChi)
-        rep += "Smear object : %s\n" % self.smearer
-        rep += "Smear type : %s\n" % self.smear_type
-        rep += "dq_l  : %s\n" % self.dq_l
-        rep += "dq_r  : %s\n" % self.dq_r
-        rep += "dx_percent  : %s\n" % str(self.dx_percent)
-        rep += "dxl  : %s\n" % str(self.dxl)
-        rep += "dxw : %s\n" % str(self.dxw)
-        rep += "model  : %s\n\n" % str(self.model)
-        temp_parameters = []
-        temp_fittable_param = []
-        if self.data.__class__.__name__ == "Data2D":
-            self.is_2D = True
-        else:
-            self.is_2D = False
-        if self.data is not None:
-            if not self.is_2D:
-                for item in self.parameters:
-                    if item not in self.orientation_params:
-                        temp_parameters.append(item)
-                for item in self.fittable_param:
-                    if item not in self.orientation_params_disp:
-                        temp_fittable_param.append(item)
-            else:
-                temp_parameters = self.parameters
-                temp_fittable_param = self.fittable_param
-
-            rep += "number parameters(self.parameters): %s\n" % \
-                   len(temp_parameters)
-            rep = self._repr_helper(list=temp_parameters, rep=rep)
-            rep += "number str_parameters(self.str_parameters): %s\n" % \
-                   len(self.str_parameters)
-            rep = self._repr_helper(list=self.str_parameters, rep=rep)
-            rep += "number fittable_param(self.fittable_param): %s\n" % \
-                   len(temp_fittable_param)
-            rep = self._repr_helper(list=temp_fittable_param, rep=rep)
-        return rep
-
-    def set_report_string(self):
-        """
-        Get the values (strings) from __str__ for report
-        """
-        # Dictionary of the report strings
-        repo_time = ""
-        model_name = ""
-        title = ""
-        title_name = ""
-        file_name = ""
-        param_string = ""
-        paramval_string = ""
-        chi2_string = ""
-        q_range = ""
-        strings = self.__repr__()
-        lines = strings.split('\n')
-
-        # get all string values from __str__()
-        for line in lines:
-            value = ""
-            content = line.split(":")
-            if line == '' or len(content) == 1:
-                continue
-            name = content[0]
-            try:
-                value = content[1]
-            except Exception:
-                msg = "Report string expected 'name: value' but got %r" % line
-                logger.error(msg)
-            if name.count("State created"):
-                repo_time = "" + value
-            if name.count("parameter name"):
-                val_name = value.split(".")
-                if len(val_name) > 1:
-                    if val_name[1].count("width"):
-                        param_string += value + ','
-                    else:
-                        continue
-                else:
-                    param_string += value + ','
-            if name == "value":
-                param_string += value + ','
-            fixed_parameter = False
-            if name == "selected":
-                if value == u' False':
-                    fixed_parameter = True
-            if name == "error value":
-                if fixed_parameter:
-                    param_string += '(fixed),'
-                else:
-                    param_string += value + ','
-            if name == "parameter unit":
-                param_string += value + ':'
-            if name == "Value of Chisqr ":
-                chi2 = ("Chi2/Npts = " + value)
-                chi2_string = CENTRE % chi2
-            if name == "Title":
-                if len(value.strip()) == 0:
-                    continue
-                title = value + " [" + repo_time + "]"
-                title_name = HEADER % title
-            if name == "data ":
-                try:
-                    file_value = ("File name:" + content[2])
-                    file_name = CENTRE % file_value
-                    if len(title) == 0:
-                        title = content[2] + " [" + repo_time + "]"
-                        title_name = HEADER % title
-                except Exception:
-                    msg = "While parsing 'data: ...'\n"
-                    logger.error(msg + traceback.format_exc())
-            if name == "model name ":
-                try:
-                    modelname = "Model name:" + content[1]
-                except:
-                    modelname = "Model name:" + " NAN"
-                model_name = CENTRE % modelname
-
-            if name == "Plotting Range":
-                try:
-                    q_range = content[1] + " = " + content[2] \
-                            + " = " + content[3].split(",")[0]
-                    q_name = ("Q Range:    " + q_range)
-                    q_range = CENTRE % q_name
-                except Exception:
-                    msg = "While parsing 'Plotting Range: ...'\n"
-                    logger.error(msg + traceback.format_exc())
-        paramval = ""
-        for lines in param_string.split(":"):
-            line = lines.split(",")
-            if len(lines) > 0:
-                param = line[0]
-                param += " = " + line[1]
-                if len(line[2].split()) > 0 and not line[2].count("None"):
-                    param += " +- " + line[2]
-                if len(line[3].split()) > 0 and not line[3].count("None"):
-                    param += " " + line[3]
-                if not paramval.count(param):
-                    paramval += param + "\n"
-                    paramval_string += CENTRE % param + "\n"
-
-        text_string = "\n\n\n%s\n\n%s\n%s\n%s\n\n%s" % \
-                      (title, file, q_name, chi2, paramval)
-
-        title_name = self._check_html_format(title_name)
-        file_name = self._check_html_format(file_name)
-        title = self._check_html_format(title)
-
-        html_string = title_name + "\n" + file_name + \
-                                   "\n" + model_name + \
-                                   "\n" + q_range + \
-                                   "\n" + chi2_string + \
-                                   "\n" + ELINE + \
-                                   "\n" + paramval_string + \
-                                   "\n" + ELINE + \
-                                   "\n" + FEET_1 % title + \
-                                   "\n" + FEET_2
-
-        return html_string, text_string, title
-
-    def _check_html_format(self, name):
-        """
-        Check string '%' for html format
-        """
-        if name.count('%'):
-            name = name.replace('%', '&#37')
-
-        return name
-
-    def report(self, figs=None, canvases=None):
-        """
-        Invoke report dialog panel
-
-        : param figs: list of pylab figures [list]
-        """
-        from sas.sasgui.perspectives.fitting.report_dialog import ReportDialog
-        # get the strings for report
-        html_str, text_str, title = self.set_report_string()
-        # Allow 2 figures to append
-        if len(figs) == 1:
-            add_str = FEET_3
-        elif len(figs) == 2:
-            add_str = ELINE
-            add_str += FEET_2 % ("%s")
-            add_str += ELINE
-            add_str += FEET_3
-        elif len(figs) > 2:
-            add_str = ELINE
-            add_str += FEET_2 % ("%s")
-            add_str += ELINE
-            add_str += FEET_2 % ("%s")
-            add_str += ELINE
-            add_str += FEET_3
-        else:
-            add_str = ""
-
-        # final report html strings
-        report_str = html_str % ("%s") + add_str
-
-        # make plot image
-        images = self.set_plot_state(figs, canvases)
-        report_list = [report_str, text_str, images]
-        dialog = ReportDialog(report_list, None, wx.ID_ANY, "")
-        dialog.Show()
-
-    def _to_xml_helper(self, thelist, element, newdoc):
-        """
-        Helper method to create xml file for saving state
-        """
-        for item in thelist:
-            sub_element = newdoc.createElement('parameter')
-            sub_element.setAttribute('name', str(item[1]))
-            sub_element.setAttribute('value', str(item[2]))
-            sub_element.setAttribute('selected_to_fit', str(item[0]))
-            sub_element.setAttribute('error_displayed', str(item[4][0]))
-            sub_element.setAttribute('error_value', str(item[4][1]))
-            sub_element.setAttribute('minimum_displayed', str(item[5][0]))
-            sub_element.setAttribute('minimum_value', str(item[5][1]))
-            sub_element.setAttribute('maximum_displayed', str(item[6][0]))
-            sub_element.setAttribute('maximum_value', str(item[6][1]))
-            sub_element.setAttribute('unit', str(item[7]))
-            element.appendChild(sub_element)
-
-    def to_xml(self, file="fitting_state.fitv", doc=None,
-               entry_node=None, batch_fit_state=None):
-        """
-        Writes the state of the fit panel to file, as XML.
-
-        Compatible with standalone writing, or appending to an
-        already existing XML document. In that case, the XML document is
-        required. An optional entry node in the XML document may also be given.
-
-        :param file: file to write to
-        :param doc: XML document object [optional]
-        :param entry_node: XML node within the XML document at which we
-                           will append the data [optional]
-        :param batch_fit_state: simultaneous fit state
-        """
-        from xml.dom.minidom import getDOMImplementation
-
-        # Check whether we have to write a standalone XML file
-        if doc is None:
-            impl = getDOMImplementation()
-            doc_type = impl.createDocumentType(FITTING_NODE_NAME, "1.0", "1.0")
-            newdoc = impl.createDocument(None, FITTING_NODE_NAME, doc_type)
-            top_element = newdoc.documentElement
-        else:
-            # We are appending to an existing document
-            newdoc = doc
-            try:
-                top_element = newdoc.createElement(FITTING_NODE_NAME)
-            except:
-                string = etree.tostring(doc, pretty_print=True)
-                newdoc = parseString(string)
-                top_element = newdoc.createElement(FITTING_NODE_NAME)
-            if entry_node is None:
-                newdoc.documentElement.appendChild(top_element)
-            else:
-                try:
-                    entry_node.appendChild(top_element)
-                except:
-                    node_name = entry_node.tag
-                    node_list = newdoc.getElementsByTagName(node_name)
-                    entry_node = node_list.item(0)
-                    entry_node.appendChild(top_element)
-
-        attr = newdoc.createAttribute("version")
-        from sas import sasview
-        attr.nodeValue = sasview.__version__
-        # attr.nodeValue = '1.0'
-        top_element.setAttributeNode(attr)
-
-        # File name
-        element = newdoc.createElement("filename")
-        if self.file is not None:
-            element.appendChild(newdoc.createTextNode(str(self.file)))
-        else:
-            element.appendChild(newdoc.createTextNode(str(file)))
-        top_element.appendChild(element)
-
-        element = newdoc.createElement("timestamp")
-        element.appendChild(newdoc.createTextNode(time.ctime(self.timestamp)))
-        attr = newdoc.createAttribute("epoch")
-        attr.nodeValue = str(self.timestamp)
-        element.setAttributeNode(attr)
-        top_element.appendChild(element)
-
-        # Inputs
-        inputs = newdoc.createElement("Attributes")
-        top_element.appendChild(inputs)
-
-        if self.data is not None and hasattr(self.data, "group_id"):
-            self.data_group_id = self.data.group_id
-        if self.data is not None and hasattr(self.data, "is_data"):
-            self.is_data = self.data.is_data
-        if self.data is not None:
-            self.data_name = self.data.name
-        if self.data is not None and hasattr(self.data, "id"):
-            self.data_id = self.data.id
-
-        for item in LIST_OF_DATA_ATTRIBUTES:
-            element = newdoc.createElement(item[0])
-            element.setAttribute(item[0], str(getattr(self, item[1])))
-            inputs.appendChild(element)
-
-        for item in LIST_OF_STATE_ATTRIBUTES:
-            element = newdoc.createElement(item[0])
-            element.setAttribute(item[0], str(getattr(self, item[1])))
-            inputs.appendChild(element)
-
-        # For self.values ={ disp_param_name: [vals,...],...}
-        # and for self.weights ={ disp_param_name: [weights,...],...}
-        for item in LIST_OF_MODEL_ATTRIBUTES:
-            element = newdoc.createElement(item[0])
-            value_list = getattr(self, item[1])
-            for key, value in value_list.iteritems():
-                sub_element = newdoc.createElement(key)
-                sub_element.setAttribute('name', str(key))
-                for val in value:
-                    sub_element.appendChild(newdoc.createTextNode(str(val)))
-
-                element.appendChild(sub_element)
-            inputs.appendChild(element)
-
-        # Create doc for the dictionary of self._disp_obj_dic
-        for tagname, varname, tagtype in DISPERSION_LIST:
-            element = newdoc.createElement(tagname)
-            value_list = getattr(self, varname)
-            for key, value in value_list.iteritems():
-                sub_element = newdoc.createElement(key)
-                sub_element.setAttribute('name', str(key))
-                sub_element.setAttribute('value', str(value))
-                element.appendChild(sub_element)
-            inputs.appendChild(element)
-
-        for item in LIST_OF_STATE_PARAMETERS:
-            element = newdoc.createElement(item[0])
-            self._to_xml_helper(thelist=getattr(self, item[1]),
-                                element=element, newdoc=newdoc)
-            inputs.appendChild(element)
-
-        # Combined and Simultaneous Fit Parameters
-        if batch_fit_state is not None:
-            batch_combo = newdoc.createElement('simultaneous_fit')
-            top_element.appendChild(batch_combo)
-
-            # Simultaneous Fit Number For Linking Later
-            element = newdoc.createElement('sim_fit_number')
-            element.setAttribute('fit_number', str(batch_fit_state.fit_page_no))
-            batch_combo.appendChild(element)
-
-            # Save constraints
-            constraints = newdoc.createElement('constraints')
-            batch_combo.appendChild(constraints)
-            for constraint in batch_fit_state.constraints_list:
-                if constraint.model_cbox.GetValue() != "":
-                    # model_cbox, param_cbox, egal_txt, constraint,
-                    # btRemove, sizer
-                    doc_cons = newdoc.createElement('constraint')
-                    doc_cons.setAttribute('model_cbox',
-                                          str(constraint.model_cbox.GetValue()))
-                    doc_cons.setAttribute('param_cbox',
-                                          str(constraint.param_cbox.GetValue()))
-                    doc_cons.setAttribute('egal_txt',
-                                          str(constraint.egal_txt.GetLabel()))
-                    doc_cons.setAttribute('constraint',
-                                          str(constraint.constraint.GetValue()))
-                    constraints.appendChild(doc_cons)
-
-            # Save all models
-            models = newdoc.createElement('model_list')
-            batch_combo.appendChild(models)
-            for model in batch_fit_state.model_list:
-                doc_model = newdoc.createElement('model_list_item')
-                doc_model.setAttribute('checked', str(model[0].GetValue()))
-                keys = model[1].keys()
-                doc_model.setAttribute('name', str(keys[0]))
-                values = model[1].get(keys[0])
-                doc_model.setAttribute('fit_number', str(model[2]))
-                doc_model.setAttribute('fit_page_source', str(model[3]))
-                doc_model.setAttribute('model_name', str(values.model.id))
-                models.appendChild(doc_model)
-
-            # Select All Checkbox
-            element = newdoc.createElement('select_all')
-            if batch_fit_state.select_all:
-                element.setAttribute('checked', 'True')
-            else:
-                element.setAttribute('checked', 'False')
-            batch_combo.appendChild(element)
-
-        # Save the file
-        if doc is None:
-            fd = open(file, 'w')
-            fd.write(newdoc.toprettyxml())
-            fd.close()
-            return None
-        else:
-            return newdoc
-
-    def _from_xml_helper(self, node, list):
-        """
-        Helper function to write state to xml
-        """
-        for item in node:
-            try:
-                name = item.get('name')
-            except:
-                name = None
-            try:
-                value = item.get('value')
-            except:
-                value = None
-            try:
-                selected_to_fit = (item.get('selected_to_fit') == "True")
-            except:
-                selected_to_fit = None
-            try:
-                error_displayed = (item.get('error_displayed') == "True")
-            except:
-                error_displayed = None
-            try:
-                error_value = item.get('error_value')
-            except:
-                error_value = None
-            try:
-                minimum_displayed = (item.get('minimum_displayed') == "True")
-            except:
-                minimum_displayed = None
-            try:
-                minimum_value = item.get('minimum_value')
-            except:
-                minimum_value = None
-            try:
-                maximum_displayed = (item.get('maximum_displayed') == "True")
-            except:
-                maximum_displayed = None
-            try:
-                maximum_value = item.get('maximum_value')
-            except:
-                maximum_value = None
-            try:
-                unit = item.get('unit')
-            except:
-                unit = None
-            list.append([selected_to_fit, name, value, "+/-",
-                         [error_displayed, error_value],
-                         [minimum_displayed, minimum_value],
-                         [maximum_displayed, maximum_value], unit])
-
-    def from_xml(self, file=None, node=None):
-        """
-        Load fitting state from a file
-
-        :param file: .fitv file
-        :param node: node of a XML document to read from
-        """
-        if file is not None:
-            msg = "PageState no longer supports non-CanSAS"
-            msg += " format for fitting files"
-            raise RuntimeError, msg
-
-        if node.get('version'):
-            # Get the version for model conversion purposes
-            self.version = tuple(int(e) for e in
-                                 str.split(node.get('version'), "."))
-            # The tuple must be at least 3 items long
-            while len(self.version) < 3:
-                ver_list = list(self.version)
-                ver_list.append(0)
-                self.version = tuple(ver_list)
-
-            # Get file name
-            entry = get_content('ns:filename', node)
-            if entry is not None:
-                self.file = entry.text.strip()
-
-            # Get time stamp
-            entry = get_content('ns:timestamp', node)
-            if entry is not None and entry.get('epoch'):
-                try:
-                    self.timestamp = float(entry.get('epoch'))
-                except:
-                    msg = "PageState.fromXML: Could not"
-                    msg += " read timestamp\n %s" % sys.exc_value
-                    logger.error(msg)
-
-            if entry is not None:
-                # Parse fitting attributes
-                entry = get_content('ns:Attributes', node)
-                for item in LIST_OF_DATA_ATTRIBUTES:
-                    node = get_content('ns:%s' % item[0], entry)
-                    setattr(self, item[0], parse_entry_helper(node, item))
-
-                dx_old_node = get_content('ns:%s' % 'dx_min', entry)
-                for item in LIST_OF_STATE_ATTRIBUTES:
-                    if item[0] == "dx_percent" and dx_old_node is not None:
-                        dxmin = ["dx_min", "dx_min", "float"]
-                        setattr(self, item[0], parse_entry_helper(dx_old_node,
-                                                                  dxmin))
-                        self.dx_old = True
-                    else:
-                        node = get_content('ns:%s' % item[0], entry)
-                        setattr(self, item[0], parse_entry_helper(node, item))
-
-                for item in LIST_OF_STATE_PARAMETERS:
-                    node = get_content("ns:%s" % item[0], entry)
-                    self._from_xml_helper(node=node,
-                                          list=getattr(self, item[1]))
-
-                # Recover _disp_obj_dict from xml file
-                self._disp_obj_dict = {}
-                for tagname, varname, tagtype in DISPERSION_LIST:
-                    node = get_content("ns:%s" % tagname, entry)
-                    for attr in node:
-                        parameter = str(attr.get('name'))
-                        value = attr.get('value')
-                        if value.startswith("<"):
-                            try:
-                                # <path.to.NamedDistribution object/instance...>
-                                cls_name = value[1:].split()[0].split('.')[-1]
-                                cls = getattr(sasmodels.weights, cls_name)
-                                value = cls.type
-                            except Exception:
-                                base = "unable to load distribution %r for %s"
-                                logger.error(base % (value, parameter))
-                                continue
-                        _disp_obj_dict = getattr(self, varname)
-                        _disp_obj_dict[parameter] = value
-
-                # get self.values and self.weights dic. if exists
-                for tagname, varname in LIST_OF_MODEL_ATTRIBUTES:
-                    node = get_content("ns:%s" % tagname, entry)
-                    dic = {}
-                    value_list = []
-                    for par in node:
-                        name = par.get('name')
-                        values = par.text.split()
-                        # Get lines only with numbers
-                        for line in values:
-                            try:
-                                val = float(line)
-                                value_list.append(val)
-                            except Exception:
-                                # pass if line is empty (it happens)
-                                msg = ("Error reading %r from %s %s\n"
-                                       % (line, tagname, name))
-                                logger.error(msg + traceback.format_exc())
-                        dic[name] = np.array(value_list)
-                    setattr(self, varname, dic)
-
-    def set_plot_state(self, figs, canvases):
-        """
-        Build image state that wx.html understand
-        by plotting, putting it into wx.FileSystem image object
-
-        """
-        images = []
-
-        # Reset memory
-        self.imgRAM = None
-        wx.MemoryFSHandler()
-
-        # For no figures in the list, prepare empty plot
-        if figs is None or len(figs) == 0:
-            figs = [None]
-
-        # Loop over the list of figures
-        # use wx.MemoryFSHandler
-        self.imgRAM = wx.MemoryFSHandler()
-        for fig in figs:
-            if fig is not None:
-                ind = figs.index(fig)
-                canvas = canvases[ind]
-
-            # store the image in wx.FileSystem Object
-            wx.FileSystem.AddHandler(wx.MemoryFSHandler())
-
-            # index of the fig
-            ind = figs.index(fig)
-
-            # AddFile, image can be retrieved with 'memory:filename'
-            self.imgRAM.AddFile('img_fit%s.png' % ind,
-                                canvas.bitmap, wx.BITMAP_TYPE_PNG)
-
-            # append figs
-            images.append(fig)
-
-        return images
-
-
-class Reader(CansasReader):
-    """
-    Class to load a .fitv fitting file
-    """
-    # File type
-    type_name = "Fitting"
-
-    # Wildcards
-    type = ["Fitting files (*.fitv)|*.fitv"
-            "SASView file (*.svs)|*.svs"]
-    # List of allowed extensions
-    ext = ['.fitv', '.FITV', '.svs', 'SVS']
-
-    def __init__(self, call_back=None, cansas=True):
-        CansasReader.__init__(self)
-        """
-        Initialize the call-back method to be called
-        after we load a file
-
-        :param call_back: call-back method
-        :param cansas:  True = files will be written/read in CanSAS format
-                        False = write CanSAS format
-
-        """
-        # Call back method to be executed after a file is read
-        self.call_back = call_back
-        # CanSAS format flag
-        self.cansas = cansas
-        self.state = None
-        # batch fitting params for saving
-        self.batchfit_params = []
-
-    def get_state(self):
-        return self.state
-
-    def read(self, path):
-        """
-        Load a new P(r) inversion state from file
-
-        :param path: file path
-
-        """
-        if self.cansas:
-            return self._read_cansas(path)
-
-    def _parse_state(self, entry):
-        """
-        Read a fit result from an XML node
-
-        :param entry: XML node to read from
-        :return: PageState object
-        """
-        # Create an empty state
-        state = None
-        # Locate the P(r) node
-        try:
-            nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
-                                namespaces={'ns': CANSAS_NS})
-            if nodes:
-                # Create an empty state
-                state = PageState()
-                state.from_xml(node=nodes[0])
-
-        except:
-            logger.info("XML document does not contain fitting information.\n"
-                         + traceback.format_exc())
-
-        return state
-
-    def _parse_simfit_state(self, entry):
-        """
-        Parses the saved data for a simultaneous fit
-        :param entry: XML object to read from
-        :return: XML object for a simultaneous fit or None
-        """
-        nodes = entry.xpath('ns:%s' % FITTING_NODE_NAME,
-                            namespaces={'ns': CANSAS_NS})
-        if nodes:
-            simfitstate = nodes[0].xpath('ns:simultaneous_fit',
-                                         namespaces={'ns': CANSAS_NS})
-            if simfitstate:
-                from simfitpage import SimFitPageState
-                sim_fit_state = SimFitPageState()
-                simfitstate_0 = simfitstate[0]
-                all = simfitstate_0.xpath('ns:select_all',
-                                          namespaces={'ns': CANSAS_NS})
-                atts = all[0].attrib
-                checked = atts.get('checked')
-                sim_fit_state.select_all = bool(checked)
-                model_list = simfitstate_0.xpath('ns:model_list',
-                                                 namespaces={'ns': CANSAS_NS})
-                model_list_items = model_list[0].xpath('ns:model_list_item',
-                                                       namespaces={'ns':
-                                                                    CANSAS_NS})
-                for model in model_list_items:
-                    attrs = model.attrib
-                    sim_fit_state.model_list.append(attrs)
-
-                constraints = simfitstate_0.xpath('ns:constraints',
-                                                namespaces={'ns': CANSAS_NS})
-                constraint_list = constraints[0].xpath('ns:constraint',
-                                               namespaces={'ns': CANSAS_NS})
-                for constraint in constraint_list:
-                    attrs = constraint.attrib
-                    sim_fit_state.constraints_list.append(attrs)
-
-                return sim_fit_state
-            else:
-                return None
-
-    def _parse_save_state_entry(self, dom):
-        """
-        Parse a SASentry
-
-        :param node: SASentry node
-
-        :return: Data1D/Data2D object
-
-        """
-        node = dom.xpath('ns:data_class', namespaces={'ns': CANSAS_NS})
-        return_value, _ = self._parse_entry(dom)
-        return return_value, _
-
-    def _read_cansas(self, path):
-        """
-        Load data and fitting information from a CanSAS XML file.
-
-        :param path: file path
-        :return: Data1D object if a single SASentry was found,
-                    or a list of Data1D objects if multiple entries were found,
-                    or None of nothing was found
-        :raise RuntimeError: when the file can't be opened
-        :raise ValueError: when the length of the data vectors are inconsistent
-        """
-        output = []
-        simfitstate = None
-        basename = os.path.basename(path)
-        root, extension = os.path.splitext(basename)
-        ext = extension.lower()
-        try:
-            if os.path.isfile(path):
-                if ext in self.ext or ext == '.xml':
-                    tree = etree.parse(path, parser=etree.ETCompatXMLParser())
-                    # Check the format version number
-                    # Specifying the namespace will take care of the file
-                    # format version
-                    root = tree.getroot()
-                    entry_list = root.xpath('ns:SASentry',
-                                            namespaces={'ns': CANSAS_NS})
-                    for entry in entry_list:
-                        fitstate = self._parse_state(entry)
-                        # state could be None when .svs file is loaded
-                        # in this case, skip appending to output
-                        if fitstate is not None:
-                            try:
-                                sas_entry, _ = self._parse_save_state_entry(
-                                    entry)
-                            except:
-                                raise
-                            sas_entry.meta_data['fitstate'] = fitstate
-                            sas_entry.filename = fitstate.file
-                            output.append(sas_entry)
-
-            else:
-                self.call_back(format=ext)
-                raise RuntimeError, "%s is not a file" % path
-
-            # Return output consistent with the loader's api
-            if len(output) == 0:
-                self.call_back(state=None, datainfo=None, format=ext)
-                return None
-            else:
-                for ind in range(len(output)):
-                    # Call back to post the new state
-                    state = output[ind].meta_data['fitstate']
-                    t = time.localtime(state.timestamp)
-                    time_str = time.strftime("%b %d %H:%M", t)
-                    # Check that no time stamp is already appended
-                    max_char = state.file.find("[")
-                    if max_char < 0:
-                        max_char = len(state.file)
-                    original_fname = state.file[0:max_char]
-                    state.file = original_fname + ' [' + time_str + ']'
-
-                    if state is not None and state.is_data is not None:
-                        output[ind].is_data = state.is_data
-
-                    output[ind].filename = state.file
-                    state.data = output[ind]
-                    state.data.name = output[ind].filename  # state.data_name
-                    state.data.id = state.data_id
-                    if state.is_data is not None:
-                        state.data.is_data = state.is_data
-                    if output[ind].run_name is not None\
-                         and len(output[ind].run_name) != 0:
-                        if isinstance(output[ind].run_name, dict):
-                            name = output[ind].run_name.keys()[0]
-                        else:
-                            name = output[ind].run_name
-                    else:
-                        name = original_fname
-                    state.data.group_id = name
-                    state.version = fitstate.version
-                    # store state in fitting
-                    self.call_back(state=state,
-                                   datainfo=output[ind], format=ext)
-                    self.state = state
-                simfitstate = self._parse_simfit_state(entry)
-                if simfitstate is not None:
-                    self.call_back(state=simfitstate)
-
-                return output
-        except:
-            self.call_back(format=ext)
-            raise
-
-    def write(self, filename, datainfo=None, fitstate=None):
-        """
-        Write the content of a Data1D as a CanSAS XML file only for standalone
-
-        :param filename: name of the file to write
-        :param datainfo: Data1D object
-        :param fitstate: PageState object
-
-        """
-        # Sanity check
-        if self.cansas:
-            # Add fitting information to the XML document
-            doc = self.write_toXML(datainfo, fitstate)
-            # Write the XML document
-        else:
-            doc = fitstate.to_xml(file=filename)
-
-        # Save the document no matter the type
-        fd = open(filename, 'w')
-        fd.write(doc.toprettyxml())
-        fd.close()
-
-    def write_toXML(self, datainfo=None, state=None, batchfit=None):
-        """
-        Write toXML, a helper for write(),
-        could be used by guimanager._on_save()
-
-        : return: xml doc
-        """
-
-        self.batchfit_params = batchfit
-        if state.data is None or not state.data.is_data:
-            return None
-        # make sure title and data run are filled.
-        if state.data.title is None or state.data.title == '':
-            state.data.title = state.data.name
-        if state.data.run_name is None or state.data.run_name == {}:
-            state.data.run = [str(state.data.name)]
-            state.data.run_name[0] = state.data.name
-
-        data = state.data
-        doc, sasentry = self._to_xml_doc(data)
-
-        if state is not None:
-            doc = state.to_xml(doc=doc, file=data.filename, entry_node=sasentry,
-                               batch_fit_state=self.batchfit_params)
-
-        return doc
-
-# Simple html report templet
-HEADER = "<html>\n"
-HEADER += "<head>\n"
-HEADER += "<meta http-equiv=Content-Type content='text/html; "
-HEADER += "charset=windows-1252'> \n"
-HEADER += "<meta name=Generator >\n"
-HEADER += "</head>\n"
-HEADER += "<body lang=EN-US>\n"
-HEADER += "<div class=WordSection1>\n"
-HEADER += "<p class=MsoNormal><b><span ><center><font size='4' >"
-HEADER += "%s</font></center></span></center></b></p>"
-HEADER += "<p class=MsoNormal>&nbsp;</p>"
-PARA = "<p class=MsoNormal><font size='4' > %s \n"
-PARA += "</font></p>"
-CENTRE = "<p class=MsoNormal><center><font size='4' > %s \n"
-CENTRE += "</font></center></p>"
-FEET_1 = \
-"""
-<p class=MsoNormal>&nbsp;</p>
-<br>
-<p class=MsoNormal><b><span ><center> <font size='4' > Graph
-</font></span></center></b></p>
-<p class=MsoNormal>&nbsp;</p>
-<center>
-<br><font size='4' >Model Computation</font>
-<br><font size='4' >Data: "%s"</font><br>
-"""
-FEET_2 = \
-"""
-<img src="%s" >
-</img>
-"""
-FEET_3 = \
-"""
-</center>
-</div>
-</body>
-</html>
-"""
-ELINE = "<p class=MsoNormal>&nbsp;</p>"
Index: src/sas/sasgui/perspectives/fitting/report_dialog.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/report_dialog.py	(revision 7432acb919f4013119ca918a73350297e0d4c93f)
+++ src/sas/sasgui/perspectives/fitting/report_dialog.py	(revision 69a68979fe94b8f135a602e7f02b162e34616d53)
@@ -38,22 +38,5 @@
         # number of images of plot
         self.nimages = len(self.report_list[2])
-
-        if self.report_list[2] is not None:
-            # put image path in the report string
-            if len(self.report_list[2]) == 1:
-                self.report_html = self.report_list[0] % \
-                                    "memory:img_fit0.png"
-            elif len(self.report_list[2]) == 2:
-                self.report_html = self.report_list[0] % \
-                                    ("memory:img_fit0.png",
-                                     "memory:img_fit1.png")
-            # allows up to three images
-            else:
-                self.report_html = self.report_list[0] % \
-                                    ("memory:img_fit0.png",
-                                     "memory:img_fit1.png",
-                                     "memory:img_fit2.png")
-        else:
-            self.report_html = self.report_list[0]
+        self.report_html = self.report_list[0]
         # layout
         self._setup_layout()
@@ -74,100 +57,58 @@
 
         fName = dlg.GetPath()
+        basename = os.path.splitext(fName)[0]
         ext_num = dlg.GetFilterIndex()
+        dlg.Destroy()
+
+        if ext_num == 0 and self.index_offset == 0:  # has pdf
+            ext = ".pdf"
+        elif ext_num == 1 - self.index_offset:
+            ext = ".html"
+        elif ext_num == 2 - self.index_offset:
+            ext = ".txt"
+        else:
+            logger.warn("unknown export format in report dialog")
+            return
+        filename = basename + ext
+
+        # save figures
+        pictures = []
+        for num in range(self.nimages):
+            pic_name = basename + '_img%s.png' % num
+            # save the image for use with pdf writer
+            self.report_list[2][num].savefig(pic_name)
+            pictures.append(pic_name)
+
+        # translate png references int html from in-memory name to on-disk name
+        html = self.report_html.replace("memory:img_fit", basename+'_img')
 
         #set file extensions
         img_ext = []
-        pic_fname = []
-        #PDF
-        if ext_num == (0 + 2 * self.index_offset):
-            # TODO: Sort this case out
-            ext = '.pdf'
+        if ext == ".pdf":
+            # write pdf as a pdf file
+            pdf = self.HTML2PDF(data=html, filename=filename)
 
-            fName = os.path.splitext(fName)[0] + ext
-            dlg.Destroy()
-            #pic (png) file path/name
-            for num in range(self.nimages):
-                im_ext = '_img%s.png' % num
-                #img_ext.append(im_ext)
-                pic_name = os.path.splitext(fName)[0] + im_ext
-                pic_fname.append(pic_name)
-                # save the image for use with pdf writer
-                self.report_list[2][num].savefig(pic_name)
+            # delete images used to create the pdf
+            for pic_name in pictures:
+                os.remove(pic_name)
 
-            #put the image path in html string
-            report_frame = self.report_list[0]
-            #put image name strings into the html file
-            #Note:The str for pic_fname shouldn't be removed.
-            if self.nimages == 1:
-                html = report_frame % str(pic_fname[0])
-            elif self.nimages == 2:
-                html = report_frame % (str(pic_fname[0]), str(pic_fname[1]))
-            elif self.nimages == 3:
-                html = report_frame % (str(pic_fname[0]), str(pic_fname[1]),
-                                          str(pic_fname[2]))
-
-            # make/open file in case of absence
-            f = open(fName, 'w')
-            f.close()
-            # write pdf as a pdf file
-            pdf = self.HTML2PDF(data=html, filename=fName)
-
-            #open pdf
+            #open pdf viewer
             if pdf:
                 try:
-                    #Windows
-                    os.startfile(str(fName))
-                except:
-                    try:
-                        #Mac
+                    if os.name == 'nt':  # Windows
+                        os.startfile(fName)
+                    elif sys.platform == "darwin":  # Mac
                         os.system("open %s" % fName)
-                    except:
-                        #DO not open
-                        pass
-            #delete image file
-            for num in range(self.nimages):
-                os.remove(pic_fname[num])
-            return
-        #HTML + png(graph)
-        elif ext_num == (1 - self.index_offset):
-            ext = '.html'
-            for num in range(self.nimages):
-                img_ext.append('_img4html%s.png' % num)
-            report_frame = self.report_list[0]
-        #TEXT + pdf(graph)
-        elif ext_num == (2 - self.index_offset):
-            ext = '.txt'
-            # changing the image extension actually changes the image
-            # format on saving
-            for num in range(self.nimages):
-                img_ext.append('_img4txt%s.pdf' % num)
-            report = self.report_list[1]
-        else:
-            return
+                except Exception as exc:
+                    # cannot open pdf
+                    logging.error(str(exc))
 
-        #file name
-        fName = os.path.splitext(fName)[0] + ext
-        dlg.Destroy()
+        elif ext == ".html":
+            with open(filename, 'w') as f:
+                f.write(html)
 
-        #pic (png) file path/name
-        for num in range(self.nimages):
-            pic_name = os.path.splitext(fName)[0] + img_ext[num]
-            pic_fname.append(pic_name)
-        #put the image path in html string
-        if ext_num == (1 - self.index_offset):
-            if self.nimages == 1:
-                report = report_frame % os.path.basename(pic_fname[0])
-            elif self.nimages == 2:
-                report = report_frame % (os.path.basename(pic_fname[0]),
-                                         os.path.basename(pic_fname[1]))
-            elif self.nimages == 3:
-                report = report_frame % (os.path.basename(pic_fname[0]),
-                                         os.path.basename(pic_fname[1]),
-                                         os.path.basename(pic_fname[2]))
-        f = open(fName, 'w')
-        f.write(report)
-        f.close()
+        elif ext == ".txt":
+            with open(filename, 'w') as f:
+                f.write(self.report_list[1])
+
         self.Update()
-        #save png file using pic_fname
-        for num in range(self.nimages):
-            self.report_list[2][num].savefig(pic_fname[num])
Index: src/sas/sasgui/perspectives/fitting/simfitpage.py
===================================================================
--- src/sas/sasgui/perspectives/fitting/simfitpage.py	(revision f73b47cd2a0a7689c4369b24fd8891a9063b98fb)
+++ src/sas/sasgui/perspectives/fitting/simfitpage.py	(revision 24d9e84d007438874423eff4087eeb96df86663d)
@@ -2,7 +2,7 @@
     Simultaneous or Batch fit page
 """
-# Note that this is used for both Simultaneous/Constrained fit AND for 
+# Note that this is used for both Simultaneous/Constrained fit AND for
 # combined batch fit.  This is done through setting of the batch_on parameter.
-# There are the a half dozen or so places where an if statement is used as in 
+# There are the a half dozen or so places where an if statement is used as in
 # if not batch_on:
 #     xxxx
@@ -11,5 +11,5 @@
 # This is just wrong but dont have time to fix this go. Proper approach would be
 # to strip all parts of the code that depend on batch_on and create the top
-# level class from which a contrained/simultaneous fit page and a combined 
+# level class from which a contrained/simultaneous fit page and a combined
 # batch page inherit.
 #
@@ -23,4 +23,5 @@
 from wx.lib.scrolledpanel import ScrolledPanel
 
+from sas.sascalc.fit.pagestate import SimFitPageState
 from sas.sasgui.guiframe.events import StatusEvent, PanelOnFocusEvent
 from sas.sasgui.guiframe.panel_base import PanelBase
@@ -60,5 +61,4 @@
 
     return fittable_param
-
 
 class SimultaneousFitPage(ScrolledPanel, PanelBase):
@@ -157,8 +157,73 @@
         return self.state
 
+    def load_from_save_state(self, sim_state):
+        """
+        Load in a simultaneous/constrained fit from a save state
+        :param fit: Fitpanel object
+        :return: None
+        """
+        init_map = {}
+        final_map = {}
+        # Process each model and associate old M# with new M#
+        i = 0
+        for model in self.model_list:
+            model_id = self._format_id(model[1].keys()[0])
+            for saved_model in sim_state.model_list:
+                save_id = saved_model.pop('name')
+                saved_model['name'] = save_id
+                save_id = self._format_id(save_id)
+                if save_id == model_id:
+                    model_map[saved_model.pop('fit_page_source')] = \
+                        model[3].name
+                    check = bool(saved_model.pop('checked'))
+                    self.model_list[i][0].SetValue(check)
+                    inter_id = str(i)*5
+                    init_map[saved_model.pop('fit_page_source')] = inter_id
+                    final_map[inter_id] = model[3].name
+                    check = bool(saved_model.pop('checked'))
+                    self.model_list[i][0].SetValue(check)
+                    break
+            i += 1
+
+        self.check_model_name(None)
+
+        if len(sim_state.constraints_list) > 0:
+            self.hide_constraint.SetValue(False)
+            self.show_constraint.SetValue(True)
+            self._display_constraint(None)
+
+        for index, item in enumerate(sim_state.constraints_list):
+            model_cbox = item.pop('model_cbox')
+            if model_cbox != "":
+                constraint_value = item.pop('constraint')
+                param = item.pop('param_cbox')
+                equality = item.pop('egal_txt')
+                for key, value in init_map.items():
+                    model_cbox = model_cbox.replace(key, value)
+                    constraint_value = constraint_value.replace(key, value)
+                for key, value in final_map.items():
+                    model_cbox = model_cbox.replace(key, value)
+                    constraint_value = constraint_value.replace(key, value)
+
+                self.constraints_list[index][0].SetValue(model_cbox)
+                self._on_select_model(None)
+                self.constraints_list[index][1].SetValue(param)
+                self.constraints_list[index][2].SetLabel(equality)
+                self.constraints_list[index][3].SetValue(constraint_value)
+                self._on_add_constraint(None)
+                self._manager.sim_page = self
+
+    def _format_id(self, original_id):
+        original_id = original_id.rstrip('1234567890.')
+        new_id_list = original_id.split()
+        new_id = ' '.join(new_id_list)
+        return new_id
+
+
+
     def draw_page(self):
         """
         Construct the Simultaneous/Constrained fit page. fills the first
-        region (sizer1) with the list of available fit page pairs of data 
+        region (sizer1) with the list of available fit page pairs of data
         and models.  Then fills sizer2 with the checkbox for adding
         constraints, and finally fills sizer3 with the fit button and
@@ -1043,87 +1108,2 @@
         cbox.Append(name, value)
     cbox.SetStringSelection(selected)
-
-
-class SimFitPageState:
-    """
-    State of the simultaneous fit page for saving purposes
-    """
-
-    def __init__(self):
-        # Sim Fit Page Number
-        self.fit_page_no = None
-        # Select all data
-        self.select_all = False
-        # Data sets sent to fit page
-        self.model_list = []
-        # Data sets to be fit
-        self.model_to_fit = []
-        # Number of constraints
-        self.no_constraint = 0
-        # Dictionary of constraints
-        self.constraint_dict = {}
-        # List of constraints
-        self.constraints_list = []
-
-    def load_from_save_state(self, fit):
-        """
-        Load in a simultaneous/constrained fit from a save state
-        :param fit: Fitpanel object
-        :return: None
-        """
-
-        init_map = {}
-        final_map = {}
-        if fit.fit_panel.sim_page is None:
-            fit.fit_panel.add_sim_page()
-        sim_page = fit.fit_panel.sim_page
-
-        # Process each model and associate old M# with new M#
-        i = 0
-        for model in sim_page.model_list:
-            model_id = self._format_id(model[1].keys()[0])
-            for saved_model in self.model_list:
-                save_id = saved_model.pop('name')
-                saved_model['name'] = save_id
-                save_id = self._format_id(save_id)
-                if save_id == model_id:
-                    inter_id = str(i) + str(i) + str(i) + str(i) + str(i)
-                    init_map[saved_model.pop('fit_page_source')] = inter_id
-                    final_map[inter_id] = model[3].name
-                    check = bool(saved_model.pop('checked'))
-                    sim_page.model_list[i][0].SetValue(check)
-                    break
-            i += 1
-        sim_page.check_model_name(None)
-
-        if len(self.constraints_list) > 0:
-            sim_page.hide_constraint.SetValue(False)
-            sim_page.show_constraint.SetValue(True)
-            sim_page._display_constraint(None)
-
-        for index, item in enumerate(self.constraints_list):
-            model_cbox = item.pop('model_cbox')
-            if model_cbox != "":
-                constraint_value = item.pop('constraint')
-                param = item.pop('param_cbox')
-                equality = item.pop('egal_txt')
-                for key, value in init_map.items():
-                    model_cbox = model_cbox.replace(key, value)
-                    constraint_value = constraint_value.replace(key, value)
-                for key, value in final_map.items():
-                    model_cbox = model_cbox.replace(key, value)
-                    constraint_value = constraint_value.replace(key, value)
-
-                sim_page.constraints_list[index][0].SetValue(model_cbox)
-                sim_page._on_select_model(None)
-                sim_page.constraints_list[index][1].SetValue(param)
-                sim_page.constraints_list[index][2].SetLabel(equality)
-                sim_page.constraints_list[index][3].SetValue(constraint_value)
-                sim_page._on_add_constraint(None)
-                sim_page._manager.sim_page = sim_page
-
-    def _format_id(self, original_id):
-        original_id = original_id.rstrip('1234567890.')
-        new_id_list = original_id.split()
-        new_id = ' '.join(new_id_list)
-        return new_id
