diff --git a/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py b/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py index 085c249b25..fcee175c81 100644 --- a/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py +++ b/src/sas/qtgui/Perspectives/Fitting/FittingUtilities.py @@ -882,19 +882,23 @@ def formatParametersExcel(parameters: list): check = "" for parameter in parameters: names += parameter[0]+tab - # Add the error column if fitted - if parameter[1] == "True" and parameter[3] is not None: - names += parameter[0]+"_err"+tab - - values += parameter[2]+tab - check += parameter[1]+tab - if parameter[1] == "True" and parameter[3] is not None: - values += parameter[3]+tab - # add .npts and .nsigmas when necessary - if parameter[0][-6:] == ".width": - names += parameter[0].replace('.width', '.nsigmas') + tab - names += parameter[0].replace('.width', '.npts') + tab - values += parameter[5] + tab + parameter[4] + tab + if len(parameter) > 3: + # Add the error column if fitted + if parameter[1] == "True" and parameter[3] is not None: + names += parameter[0]+"_err"+tab + + values += parameter[2]+tab + check += str(parameter[1])+tab + if parameter[1] == "True" and parameter[3] is not None: + values += parameter[3]+tab + # add .npts and .nsigmas when necessary + if parameter[0][-6:] == ".width": + names += parameter[0].replace('.width', '.nsigmas') + tab + names += parameter[0].replace('.width', '.npts') + tab + values += parameter[5] + tab + parameter[4] + tab + else: + # Empty statement for debugging purposes + pass output_string = names + crlf + values + crlf + check return output_string @@ -913,46 +917,43 @@ def formatParametersLatex(parameters: list): output_string += r'}\hline' output_string += crlf + names = "" + values = "" + for index, parameter in enumerate(parameters): name = parameter[0] # Parameter name - output_string += name.replace('_', r'\_') # Escape underscores - # Add the error column if fitted - if parameter[1] == "True" and parameter[3] is not None: - output_string += ' & ' - output_string += parameter[0]+r'\_err' - - if index < len(parameters) - 1: - output_string += ' & ' - - # add .npts and .nsigmas when necessary - if parameter[0][-6:] == ".width": - output_string += parameter[0].replace('.width', '.nsigmas') + ' & ' - output_string += parameter[0].replace('.width', '.npts') + names += name.replace('_', r'\_') # Escape underscores + if len(parameter) > 3: + values += f" {parameter[2]}" + # Add the error column if fitted + if parameter[1] == "True" and parameter[3] is not None: + names += f" & {parameter[0]} " + r'\_err' + values += f' & {parameter[3]}' if index < len(parameters) - 1: - output_string += ' & ' + names += ' & ' + values += ' & ' + + # add .npts and .nsigmas when necessary + if parameter[0][-6:] == ".width": + names += parameter[0].replace('.width', '.nsigmas') + ' & ' + names += parameter[0].replace('.width', '.npts') + values += parameter[5] + ' & ' + values += parameter[4] + + if index < len(parameters) - 1: + names += ' & ' + values += ' & ' + elif len(parameter) > 2: + values += f' & {parameter[2]} &' + else: + values += f' & {parameter[1]} &' + output_string += names output_string += r'\\ \hline' output_string += crlf - # Construct row of values and errors - for index, parameter in enumerate(parameters): - output_string += parameter[2] - if parameter[1] == "True" and parameter[3] is not None: - output_string += ' & ' - output_string += parameter[3] - - if index < len(parameters) - 1: - output_string += ' & ' - - # add .npts and .nsigmas when necessary - if parameter[0][-6:] == ".width": - output_string += parameter[5] + ' & ' - output_string += parameter[4] - - if index < len(parameters) - 1: - output_string += ' & ' - + output_string += values output_string += r'\\ \hline' output_string += crlf output_string += r'\end{tabular}' diff --git a/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py b/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py index 9f251b33d5..d0234aca0e 100644 --- a/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py +++ b/src/sas/qtgui/Perspectives/Fitting/FittingWidget.py @@ -1,6 +1,6 @@ import json import os -import sys +import re from collections import defaultdict from typing import Any, Tuple, Optional from pathlib import Path @@ -63,6 +63,12 @@ DEFAULT_POLYDISP_FUNCTION = 'gaussian' +# A list of models that are known to not work with how the GUI handles models from sasmodels +# NOTE: These models are correct when used directly through the sasmodels package, but how qtgui handles them is wrong +SUPPRESSED_MODELS = ['rpa'] +# Layered models that have integer parameters are often treated differently. Maintain a list of these models. +LAYERED_MODELS = ['unified_power_Rg', 'core_multi_shell', 'onion', 'spherical_sld'] + # CRUFT: remove when new release of sasmodels is available # https://github.com/SasView/sasview/pull/181#discussion_r218135162 if not hasattr(SasviewModel, 'get_weights'): @@ -1509,7 +1515,8 @@ def onSelectModel(self): # disable polydispersity if the model does not support it has_poly = self._poly_model.rowCount() != 0 self.chkPolydispersity.setEnabled(has_poly) - self.tabFitting.setTabEnabled(TAB_POLY, has_poly) + if has_poly: + self.togglePoly(self.chkPolydispersity.isChecked()) # set focus so it doesn't move up self.cbModel.setFocus() @@ -1740,7 +1747,7 @@ def onSelectCategory(self): # Populate the models combobox self.cbModel.blockSignals(True) self.cbModel.addItem(MODEL_DEFAULT) - models_to_show = [m[0] for m in model_list if m[0] != 'rpa' and m[1]] + models_to_show = [m[0] for m in model_list if m[0] not in SUPPRESSED_MODELS and m[1]] self.cbModel.addItems(sorted(models_to_show)) self.cbModel.blockSignals(False) @@ -2212,8 +2219,6 @@ def updateModelFromList(self, param_dict): Update the model with new parameters, create the errors column """ assert isinstance(param_dict, dict) - if not dict: - return def updateFittedValues(row): # Utility function for main model update @@ -2238,7 +2243,7 @@ def updatePolyValues(row): param_repr = GuiUtils.formatNumber(param_dict[param_name][0], high=True) self._model_model.item(row, 0).child(0).child(0,1).setText(param_repr) # modify the param error - if self.has_error_column: + if self.has_poly_error_column: error_repr = GuiUtils.formatNumber(param_dict[param_name][1], high=True) self._model_model.item(row, 0).child(0).child(0,2).setText(error_repr) @@ -2318,8 +2323,6 @@ def updatePolyModelFromList(self, param_dict): Update the polydispersity model with new parameters, create the errors column """ assert isinstance(param_dict, dict) - if not dict: - return def updateFittedValues(row_i): # Utility function for main model update @@ -2384,8 +2387,6 @@ def updateMagnetModelFromList(self, param_dict): Update the magnetic model with new parameters, create the errors column """ assert isinstance(param_dict, dict) - if not dict: - return if self._magnet_model.rowCount() == 0: return @@ -2930,7 +2931,8 @@ def onMainParamsChange(self, top, bottom): # don't try to update multiplicity counters if they aren't there. # Note that this will fail for proper bad update where the model # doesn't contain multiplicity parameter - self.kernel_module.setParam(parameter_name, value) + if self.kernel_module.params.get(parameter_name, None): + self.kernel_module.setParam(parameter_name, value) elif model_column == min_column: # min/max to be changed in self.kernel_module.details[parameter_name] = ['Ang', 0.0, inf] self.kernel_module.details[parameter_name][1] = value @@ -3484,12 +3486,12 @@ def setPolyModelParameters(self, i, param): for ishell in range(1, self.current_shell_displayed+1): # Remove [n] and add the shell numeral name = param_name[0:param_name.index('[')] + str(ishell) - self.addNameToPolyModel(i, name) + self.addNameToPolyModel(name) else: # Just create a simple param entry - self.addNameToPolyModel(i, param_name) + self.addNameToPolyModel(param_name) - def addNameToPolyModel(self, i, param_name): + def addNameToPolyModel(self, param_name): """ Creates a checked row in the poly model with param_name """ @@ -3521,9 +3523,9 @@ def addNameToPolyModel(self, i, param_name): func.addItems([str(name_disp) for name_disp in POLYDISPERSITY_MODELS.keys()]) # Set the default index func.setCurrentIndex(func.findText(DEFAULT_POLYDISP_FUNCTION)) - ind = self._poly_model.index(i,self.lstPoly.itemDelegate().poly_function) + ind = self._poly_model.index(all_items-1,self.lstPoly.itemDelegate().poly_function) self.lstPoly.setIndexWidget(ind, func) - func.currentIndexChanged.connect(lambda: self.onPolyComboIndexChange(str(func.currentText()), i)) + func.currentIndexChanged.connect(lambda: self.onPolyComboIndexChange(str(func.currentText()), all_items-1)) def onPolyFilenameChange(self, row_index): """ @@ -3549,7 +3551,7 @@ def onPolyComboIndexChange(self, combo_string, row_index): Modify polydisp. defaults on function choice """ # Get npts/nsigs for current selection - param = self.model_parameters.form_volume_parameters[row_index] + param_name = str(self._poly_model.item(row_index, 0).text()).split()[-1] file_index = self._poly_model.index(row_index, self.lstPoly.itemDelegate().poly_function) combo_box = self.lstPoly.indexWidget(file_index) try: @@ -3562,16 +3564,16 @@ def updateFunctionCaption(row): # Utility function for update of polydispersity function name in the main model if not self.isCheckable(row): return - param_name = self._model_model.item(row, 0).text() - if param_name != param.name: + par_name = self._model_model.item(row, 0).text() + if par_name != param_name: return # Modify the param value self._model_model.blockSignals(True) - if self.has_error_column: - # err column changes the indexing - self._model_model.item(row, 0).child(0).child(0,5).setText(combo_string) - else: - self._model_model.item(row, 0).child(0).child(0,4).setText(combo_string) + n = 5 if self.has_error_column else 4 + # Add an extra safety check to be sure this parameter has the polydisperse table row + poly_row = self._model_model.item(row, 0).child(0) + if poly_row: + poly_row.child(0, n).setText(combo_string) self._model_model.blockSignals(False) if combo_string == 'array': @@ -3584,7 +3586,7 @@ def updateFunctionCaption(row): self.loadPolydispArray(row_index) # Update main model for display self.iterateOverModel(updateFunctionCaption) - self.kernel_module.set_dispersion(param.name, self.disp_model) + self.kernel_module.set_dispersion(param_name, self.disp_model) # uncheck the parameter self._poly_model.item(row_index, 0).setCheckState(QtCore.Qt.Unchecked) # disable the row @@ -3599,7 +3601,7 @@ def updateFunctionCaption(row): # Pass for cancel/bad read pass else: - self.kernel_module.set_dispersion(param.name, self.disp_model) + self.kernel_module.set_dispersion(param_name, self.disp_model) # Enable the row in case it was disabled by Array self._poly_model.blockSignals(True) @@ -3763,10 +3765,17 @@ def addExtraShells(self): # set the cell to be non-editable item4.setFlags(item4.flags() ^ QtCore.Qt.ItemIsEditable) - # cell 4: SLD button + # cell 5: SLD button item5 = QtGui.QStandardItem() - button = QtWidgets.QPushButton() - button.setText("Show SLD Profile") + button = None + for p in self.kernel_module.params.keys(): + if re.search(r'^[\w]{0,3}sld.*[1-9]$', p): + # Only display the SLD Profile button for models with SLD parameters + button = QtWidgets.QPushButton() + button.setText("Show SLD Profile") + # Respond to button press + button.clicked.connect(self.onShowSLDProfile) + break self._model_model.appendRow([item1, item2, item3, item4, item5]) @@ -3818,8 +3827,6 @@ def addExtraShells(self): ## Respond to index change #func.currentTextChanged.connect(self.modifyShellsInList) - # Respond to button press - button.clicked.connect(self.onShowSLDProfile) # Available range of shells displayed in the combobox func.addItems([str(i) for i in range(shell_min, shell_max+1)]) @@ -3845,6 +3852,8 @@ def modifyShellsInList(self, text): index = 0 logger.error("Multiplicity incorrect! Setting to 0") self.kernel_module.multiplicity = index + # Copy existing param values before removing rows to retain param values when changing n-shells + self.clipboard_copy() if remove_rows > 1: self._model_model.removeRows(first_row, remove_rows) @@ -3872,6 +3881,8 @@ def modifyShellsInList(self, text): if self.canHaveMagnetism(): self.setMagneticModel() + self.clipboard_paste() + def onShowSLDProfile(self): """ Show a quick plot of SLD profile @@ -4339,32 +4350,18 @@ def gatherParams(row): Create list of main parameters based on _model_model """ param_name = str(self._model_model.item(row, 0).text()) - current_list = self.tabToList[self.tabFitting.currentIndex()] model = self._model_model if model.item(row, 0) is None: return # Assure this is a parameter - must contain a checkbox if not model.item(row, 0).isCheckable(): - # maybe it is a combobox item (multiplicity) - try: - index = model.index(row, 1) - widget = current_list.indexWidget(index) - if widget is None: - return - if isinstance(widget, QtWidgets.QComboBox): - # find the index of the combobox - current_index = widget.currentIndex() - param_list.append([param_name, 'None', str(current_index)]) - except Exception as ex: - pass - return - - param_checked = str(model.item(row, 0).checkState() == QtCore.Qt.Checked) + param_checked = None + else: + param_checked = str(model.item(row, 0).checkState() == QtCore.Qt.Checked) # Value of the parameter. In some cases this is the text of the combobox choice. param_value = str(model.item(row, 1).text()) param_error = None - param_min = None - param_max = None + _, param_min, param_max = self.kernel_module.details.get(param_name, ('', None, None)) column_offset = 0 if self.has_error_column: column_offset = 1 @@ -4372,7 +4369,7 @@ def gatherParams(row): try: param_min = str(model.item(row, 2+column_offset).text()) param_max = str(model.item(row, 3+column_offset).text()) - except: + except Exception: pass # Do we have any constraints on this parameter? constraint = self.getConstraintForRow(row, model_key="standard") @@ -4485,10 +4482,9 @@ def updatePageWithParameters(self, line_dict, warn_user=True): self.chk2DView.setChecked(line_dict['2D_params'][0]=='True') # Create the context dictionary for parameters + # Exclude multiplicity and number of shells params from context + context = {k: v for (k, v) in line_dict.items() if len(v) > 3 and k != model} context['model_name'] = model - for key, value in line_dict.items(): - if len(value) > 2: - context[key] = value if warn_user and str(self.cbModel.currentText()) != str(context['model_name']): msg = QtWidgets.QMessageBox() @@ -4555,29 +4551,14 @@ def updateFullModel(self, param_dict): Update the model with new parameters """ assert isinstance(param_dict, dict) - if not dict: - return def updateFittedValues(row): # Utility function for main model update # internal so can use closure for param_dict param_name = str(self._model_model.item(row, 0).text()) - if param_name not in list(param_dict.keys()): + if param_name not in list(param_dict.keys()) or row == self._n_shells_row: + # Skip magnetic, polydisperse (.pd), and shell parameters - they are handled elsewhere return - # Special case of combo box in the cell (multiplicity) - param_line = param_dict[param_name] - if len(param_line) == 1: - # modify the shells value - try: - combo_index = int(param_line[0]) - except ValueError: - # quietly pass - return - index = self._model_model.index(row, 1) - widget = self.lstParams.indexWidget(index) - if widget is not None and isinstance(widget, QtWidgets.QComboBox): - #widget.setCurrentIndex(combo_index) - return # checkbox state param_checked = QtCore.Qt.Checked if param_dict[param_name][0] == "True" else QtCore.Qt.Unchecked self._model_model.item(row, 0).setCheckState(param_checked) @@ -4619,8 +4600,6 @@ def updateFullPolyModel(self, param_dict): Update the polydispersity model with new parameters, create the errors column """ assert isinstance(param_dict, dict) - if not dict: - return def updateFittedValues(row): # Utility function for main model update @@ -4667,8 +4646,6 @@ def updateFullMagnetModel(self, param_dict): Update the magnetism model with new parameters, create the errors column """ assert isinstance(param_dict, dict) - if not dict: - return def updateFittedValues(row): # Utility function for main model update diff --git a/src/sas/qtgui/Perspectives/Fitting/media/fitting_help.rst b/src/sas/qtgui/Perspectives/Fitting/media/fitting_help.rst index ebedbf36ab..0fd73a3bcb 100755 --- a/src/sas/qtgui/Perspectives/Fitting/media/fitting_help.rst +++ b/src/sas/qtgui/Perspectives/Fitting/media/fitting_help.rst @@ -288,30 +288,41 @@ displays the *Easy Add/Multiply Editor* dialog. .. image:: sum_model.png -This option creates a custom Plugin Model of the form:: +This editor allows the creation of combined custom Plugin Models. +Give the new model a name (which will appear in the list of plugin models on the *FitPage*) +and brief description (to appear under the *Details* button on the *FitPage*). The model name must not contain +spaces (use underscores to separate words if necessary) and if it is longer +than ~25 characters the name will not display in full in the list of models. +Now select two models, as model_1 (or p1) and model_2 (or p2), and the +required operator, '+', '*', or '@' between them. Finally, click the *Apply* button +to generate and test the model. + +The `+` operator sums the individual I(Q) calculations and introduces a third scale factor:: Plugin Model = scale_factor * {(scale_1 * model_1) +/- (scale_2 * model_2)} + background -or:: +the `*` operator multiplies the individual I(Q) calculations:: Plugin Model = scale_factor * (model1 * model2) + background -In the *Easy Add/Multiply Editor* give the new model a name (which will appear -in the list of plugin models on the *FitPage*) and brief description (to appear -under the *Details* button on the *FitPage*). The model name must not contain -spaces (use underscores to separate words if necessary) and if it is longer -than ~25 characters the name will not display in full in the list of models. -Now select two built-in models, as model_1 (or p1) and model_2 (or p2), and the -required operator, '+' or '*' between them. Finally, click the *Apply* button -to generate and test the model, and then click *Close*. +and the `@` operator treats the combination as a form factor [F(Q)] for model_1 and a structure factor [S(Q)] for +model_2. The scale and background for F(Q) and S(Q) are set to 1 and 0 respectively and the combined model should +support the beta approximation:: + + Plugin Model = scale_factor * vol_fraction * * S(Q) + background :: No beta + Plugin Model = scale_factor * (vol_fraction / form_volume) * ( + ^2 * (S(Q) - 1)) + background :: beta + +**All Versions** Changes made to a plugin model are not applied to models actively in use on fit pages. +To apply plugin model changes, re-select the model from the drop-down menu on the FitPage. -Any changes to a plugin model generated in this way only become effective -*after* it is re-selected from the plugin models drop-down menu on the FitPage. +**In SasView 6.x**, multiplicity models cannot be combined. If a model with any layer or conditional parameter is +selected, similar models are removed from the other combo box. -**In SasView 4.x**, if the model is not listed you can try and force a +**In SasView 4.x**, if the model is not listed on a fit page you can try and force a recompilation of the plugins by selecting *Fitting* > *Plugin Model Operations* -> *Load Plugin Models*. **In SasView 5.x**, you may need to restart the -program. +> *Load Plugin Models*. **In SasView 5.0.2 and earlier**, you may need to restart the +program. **In SasView 5.0.3 and later**, the new model should appear in the list as soon as +the model is saved. .. warning:: diff --git a/src/sas/qtgui/Perspectives/Fitting/media/sum_model.png b/src/sas/qtgui/Perspectives/Fitting/media/sum_model.png index c4e4e10e89..3817dacca8 100755 Binary files a/src/sas/qtgui/Perspectives/Fitting/media/sum_model.png and b/src/sas/qtgui/Perspectives/Fitting/media/sum_model.png differ diff --git a/src/sas/qtgui/Utilities/AddMultEditor.py b/src/sas/qtgui/Utilities/AddMultEditor.py index 12e1b23761..8ba56c3d0e 100644 --- a/src/sas/qtgui/Utilities/AddMultEditor.py +++ b/src/sas/qtgui/Utilities/AddMultEditor.py @@ -20,6 +20,7 @@ from sas.sascalc.fit import models import sas.qtgui.Utilities.GuiUtils as GuiUtils +from sas.qtgui.Perspectives.Fitting.FittingWidget import SUPPRESSED_MODELS # Local UI from sas.qtgui.Utilities.UI.AddMultEditorUI import Ui_AddMultEditorUI @@ -39,6 +40,10 @@ BG_WHITE = "background-color: rgb(255, 255, 255);" BG_RED = "background-color: rgb(244, 170, 164);" +# Default model names for combo boxes +CB1_DEFAULT = 'sphere' +CB2_DEFAULT = 'cylinder' + class AddMultEditor(QtWidgets.QDialog, Ui_AddMultEditorUI): """ @@ -67,14 +72,17 @@ def __init__(self, parent=None): # Flag for correctness of resulting name self.good_name = False - self.setupSignals() - + # Create a base list of layered models that will include plugin models + self.layered_models = [] + # Create base model lists self.list_models = self.readModels() self.list_standard_models = self.readModels(std_only=True) - - # Fill models' comboboxes + # Fill models combo boxes self.setupModels() + # Set signals after model combo boxes are populated + self.setupSignals() + self.setFixedSize(self.minimumSizeHint()) # Name and directory for saving new plugin model @@ -93,32 +101,23 @@ def setupModels(self): self.cbModel2.addItems(self.list_standard_models) # set the default initial value of Model1 and Model2 - index_ini_model1 = self.cbModel1.findText('sphere', QtCore.Qt.MatchFixedString) - - if index_ini_model1 >= 0: - self.cbModel1.setCurrentIndex(index_ini_model1) - else: - self.cbModel1.setCurrentIndex(0) - - index_ini_model2 = self.cbModel2.findText('cylinder', - QtCore.Qt.MatchFixedString) - if index_ini_model2 >= 0: - self.cbModel2.setCurrentIndex(index_ini_model2) - else: - self.cbModel2.setCurrentIndex(0) + index_ini_model1 = self.cbModel1.findText(CB1_DEFAULT, QtCore.Qt.MatchFixedString) + self.cbModel1.setCurrentIndex(index_ini_model1 if index_ini_model1 >= 0 else 0) + index_ini_model2 = self.cbModel2.findText(CB2_DEFAULT, QtCore.Qt.MatchFixedString) + self.cbModel2.setCurrentIndex(index_ini_model2 if index_ini_model2 >= 0 else 0) def readModels(self, std_only=False): """ Generate list of all models """ s_models = load_standard_models() models_dict = {} for model in s_models: - if model.category is None: - continue - if std_only and 'custom' in model.category: + # Check if plugin model is a layered model + self._checkIfLayered(model) + # Do not include uncategorized models or suppressed models + if model.category is None or (std_only and 'custom' in model.category) or model.name in SUPPRESSED_MODELS: continue models_dict[model.name] = model - - return sorted([model_name for model_name in models_dict]) + return sorted(models_dict) def setupSignals(self): """ Signals from various elements """ @@ -131,6 +130,10 @@ def setupSignals(self): self.buttonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self.onHelp) self.buttonBox.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self.close) + # Update model lists when new model selected in case one of the items selected is in self.layered_models + self.cbModel1.currentIndexChanged.connect(self.updateModels) + self.cbModel2.currentIndexChanged.connect(self.updateModels) + # change displayed equation when changing operator self.cbOperator.currentIndexChanged.connect(self.onOperatorChange) @@ -233,8 +236,6 @@ def onApply(self): # Update list of models in FittingWidget and AddMultEditor self.parent.communicate.customModelDirectoryChanged.emit() - # Re-read the model list so the new model is included - self.list_models = self.readModels() self.updateModels() # Notify the user @@ -243,7 +244,6 @@ def onApply(self): self.parent.communicate.statusBarUpdateSignal.emit(msg) logging.info(msg) - def write_new_model_to_file(self, fname, model1_name, model2_name, operator): """ Write and Save file """ @@ -268,29 +268,51 @@ def write_new_model_to_file(self, fname, model1_name, model2_name, operator): out_f.write(output) def updateModels(self): - """ Update contents of comboboxes with new plugin models """ + """ Update contents of combo boxes with new plugin models """ + # Supress signals to prevent infinite loop + self.cbModel1.blockSignals(True) + self.cbModel2.blockSignals(True) - # Keep pointers to the current indices so we can show the comboboxes with - # original selection - model_1 = self.cbModel1.currentText() - model_2 = self.cbModel2.currentText() + # Re-read the model list so the new model is included + self.list_models = self.readModels() + self._updateModelLists() - self.cbModel1.blockSignals(True) - self.cbModel1.clear() + self.cbModel2.blockSignals(False) self.cbModel1.blockSignals(False) - self.cbModel2.blockSignals(True) + def _updateModelLists(self): + """Update the combo boxes for both lists of models. The models in layered_models can only be included a single + time in a plugin model. The two combo boxes could be different if a layered model is selected.""" + # Keep pointers to the current indices, so we can show the combo boxes with original selection + model_1 = self.cbModel1.currentText() + model_2 = self.cbModel2.currentText() + self.cbModel1.clear() self.cbModel2.clear() - self.cbModel2.blockSignals(False) # Retrieve the list of models - model_list = self.readModels(std_only=True) - # Populate the models comboboxes - self.cbModel1.addItems(model_list) - self.cbModel2.addItems(model_list) - - # Scroll back to the user chosen models - self.cbModel1.setCurrentIndex(self.cbModel1.findText(model_1)) - self.cbModel2.setCurrentIndex(self.cbModel2.findText(model_2)) + no_layers_list = [model for model in self.list_models if model not in self.layered_models] + # Make copies of the original list to allow for list-specific changes + model_list_1 = no_layers_list if model_2 in self.layered_models else self.list_models + model_list_2 = no_layers_list if model_1 in self.layered_models else self.list_models + + # Populate the models combo boxes + self.cbModel1.addItems(model_list_1) + self.cbModel2.addItems(model_list_2) + + # Reset the model position + model1_index = self.cbModel1.findText(model_1) + model1_default = self.cbModel1.findText(CB1_DEFAULT) + model2_index = self.cbModel2.findText(model_2) + model2_default = self.cbModel2.findText(CB2_DEFAULT) + index1 = model1_index if model1_index >= 0 else model1_default if model1_default >= 0 else 0 + index2 = model2_index if model2_index >= 0 else model2_default if model2_default >= 0 else 0 + + self.cbModel1.setCurrentIndex(index1) + self.cbModel2.setCurrentIndex(index2) + + def _checkIfLayered(self, model): + """Check models for layered or conditional parameters. Add them to self.layered_models if criteria is met.""" + if model.is_multiplicity_model: + self.layered_models.append(model.name) def onHelp(self): """ Display related help section """