Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update model editor (rebase) #3135

Draft
wants to merge 39 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b713e9e
Adds ER and VR functions to non-polydisperse model and fixes whitespace
tsole0 Jun 18, 2024
9a5eb5e
Create Form Volume Function text editing box in UI
tsole0 Jun 18, 2024
7625d37
allows user to edit form volume function in GUI if polydisperse param…
tsole0 Jun 20, 2024
77e376f
polydisperse-only functions are omitted from model text if Form Volum…
tsole0 Jun 20, 2024
c50987f
Fixes pre-existing bug forcing user to re-enter default text for mode…
tsole0 Jun 20, 2024
9cfe27d
"smart" example text now automatically appears in Form Volume functio…
tsole0 Jun 20, 2024
0b73ba6
Change tooltips, GUI discriptions, and sample functions for clarity
tsole0 Jun 21, 2024
c62ecad
fixes bug preventing applying model changes from model editor
tsole0 Jun 21, 2024
5ccaa42
Adds all python flags to the model template with default values
tsole0 Jun 24, 2024
15d43c6
Add checkboxes for generating Python and C code in PluginDefinition UI
tsole0 Jun 24, 2024
8517588
Connect checkboxes to logic that creates new tab for displaying C mod…
tsole0 Jun 24, 2024
31309b8
Add error message QMessageBox if user does not specify which model la…
tsole0 Jun 25, 2024
a15b081
Update error text for clarity and readability
tsole0 Jun 25, 2024
dc5a311
linting within updateFromPlugin(), create C_TEMPLATE for use in creat…
tsole0 Jun 25, 2024
bb83f69
fix bug preventing c template generation if no polydisperse params we…
tsole0 Jun 26, 2024
e87d9d1
add `source = []` statement to python model if user creates both pyth…
tsole0 Jun 26, 2024
582af96
add descriptive comments to C model template variable, include have_f…
tsole0 Jun 27, 2024
9bf4127
Cherry-pick bug-fix branch to gain unit tests for model checking on p…
tsole0 Jun 24, 2024
1e2d643
remove isModelCorrect method whose functionality was incorporated int…
tsole0 Jun 24, 2024
985e6fb
Update comments and remove unnecessary flag from method
tsole0 Jun 24, 2024
11c7ae1
when saving C model, run model check and highlight C window if error …
tsole0 Jun 26, 2024
ac17f67
fix bug that deletes .py file after a failed model check on a .c file…
tsole0 Jun 27, 2024
a6fdee9
remove references to deprecated self.filename variable; in both cases…
tsole0 Jun 27, 2024
5bcd5a6
add overridewarning() function that allows user to save model even if…
tsole0 Jun 28, 2024
3196d58
add noModelCheckWarning() method to show popup if user generates only…
tsole0 Jun 28, 2024
a43dafa
retain error formatting if the user decides to override bad model war…
tsole0 Jun 28, 2024
078b2cc
expand QTableWidget horizontally to fit area, and ensure that text do…
tsole0 Jun 28, 2024
4ffa597
simplify and centralize tab creation methods into addTab() and remove…
tsole0 Jun 28, 2024
726eb1d
update tab names if user changes model name
tsole0 Jun 28, 2024
778bffc
fix bug preventing editing models in model editor because editorModel…
tsole0 Jul 2, 2024
b9e43f8
force user to generate Python model if no Python model is detected in…
tsole0 Jul 3, 2024
8ab80a8
cause modelModified signal to be emitted when text changed instead of…
tsole0 Jul 3, 2024
0fc91ff
Adds comment explaining built-in functions to C template
tsole0 Jul 3, 2024
b369d56
edits previously mentioned comment for clarity
tsole0 Jul 3, 2024
2f1d5da
reflect user changes to model editor in plugin editor. use ast module…
tsole0 Jul 5, 2024
3551a1f
fix indentation in auto-generated c models
tsole0 Jul 5, 2024
50962a4
make comments multi-line to improve readability in editor window
tsole0 Jul 5, 2024
c0591a0
add checkboxes for boolean flags in model editor
tsole0 Sep 9, 2024
b6c4a63
update C template and "generate C model checkbox" for clarity
tsole0 Sep 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 168 additions & 18 deletions src/sas/qtgui/Utilities/PluginDefinition.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import os.path
import logging

from PySide6 import QtCore
from PySide6 import QtGui
from PySide6 import QtWidgets

from sas.qtgui.Utilities.UI.PluginDefinitionUI import Ui_PluginDefinition
from sas.qtgui.Utilities import GuiUtils
from sas.sascalc.fit.models import find_plugins_dir

# txtName
# txtDescription
Expand All @@ -19,16 +23,28 @@ class PluginDefinition(QtWidgets.QDialog, Ui_PluginDefinition):
model form and parameters.
"""
modelModified = QtCore.Signal()
omitPolydisperseFuncsSignal = QtCore.Signal()
includePolydisperseFuncsSignal = QtCore.Signal()
enablePyCheckboxSignal = QtCore.Signal()
sendNewParamSignal = QtCore.Signal(list)
sendNewDescriptionSignal = QtCore.Signal(str)
sendNewIqSignal = QtCore.Signal(str)
sendNewFormVolumeSignal = QtCore.Signal(str)

def __init__(self, parent=None):
super(PluginDefinition, self).__init__(parent)

self.setupUi(self)

self.infoLabel.setVisible(False)

# globals
self.initializeModel()
# internal representation of the parameter list
# {<row>: (<parameter>, <value>)}
self.parameter_dict = {}
self.pd_parameter_dict = {}
self.displayed_default_form_volume = False

# Initialize widgets
self.addWidgets()
Expand All @@ -39,33 +55,43 @@ def __init__(self, parent=None):
# Initialize signals
self.addSignals()

def addTooltip(self):
def addTooltips(self):
"""
Add the default tooltip to the text field
Add the default tooltips to the Iq and form_volume function text boxes
"""
hint_function = "#Example:\n\n"
hint_function += "if x <= 0:\n"
hint_function += " y = A + B\n"
hint_function = "This function returns the scattering intensity for a given q.\n"
hint_function += "Example:\n\n"
hint_function += "if q <= 0:\n"
hint_function += " intensity = A + B\n"
hint_function += "else:\n"
hint_function += " y = A + B * cos(2 * pi * x)\n"
hint_function += "return y\n"
hint_function += " intensity = A + B * cos(2 * pi * q)\n"
hint_function += "return intensity\n"
self.txtFunction.setToolTip(hint_function)
hint_function = "This function returns the volume of the particle.\n"
hint_function += "Example:\n\n"
hint_function += "volume = (4 / 3) * pi * R**3\n"
hint_function += "return volume\n"
self.txtFormVolumeFunction.setToolTip(hint_function)


def addWidgets(self):
"""
Initialize various widgets in the dialog
"""
self.addTooltip()
self.addTooltips()

# Initial text in the function table
text = \
"""y = x
"""intensity = q

return y
return intensity
"""
self.model['func_text'] = text
self.txtFunction.insertPlainText(text)
self.txtFunction.setFont(GuiUtils.getMonospaceFont())

self.txtFormVolumeFunction.setFont(GuiUtils.getMonospaceFont())

# Validators
rx = QtCore.QRegularExpression("^[A-Za-z0-9_]*$")

Expand All @@ -75,7 +101,8 @@ def addWidgets(self):
# importing QSyntaxHighlighter
# DO NOT MOVE TO TOP
from sas.qtgui.Utilities.PythonSyntax import PythonHighlighter
self.highlight = PythonHighlighter(self.txtFunction.document())
self.highlightFunction = PythonHighlighter(self.txtFunction.document())
self.highlightFormVolumeFunction = PythonHighlighter(self.txtFormVolumeFunction.document())

def initializeModel(self):
"""
Expand All @@ -85,29 +112,50 @@ def initializeModel(self):
self.model = {
'filename':'',
'overwrite':False,
'gen_python':True,
'gen_c':False,
'description':'',
'parameters':{},
'pd_parameters':{},
'text':''}
'func_text':'',
'form_volume_text':''
}

def addSignals(self):
"""
Define slots for widget signals
"""
self.txtName.editingFinished.connect(self.onPluginNameChanged)
self.txtDescription.editingFinished.connect(self.onDescriptionChanged)
self.txtName.textChanged.connect(self.onPluginNameChanged)
self.txtDescription.textChanged.connect(self.onDescriptionChanged)
self.tblParams.cellChanged.connect(self.onParamsChanged)
self.tblParamsPD.cellChanged.connect(self.onParamsPDChanged)
# QTextEdit doesn't have a signal for edit finish, so we respond to text changed.
# Possibly a slight overkill.
self.txtFunction.textChanged.connect(self.onFunctionChanged)
self.txtFormVolumeFunction.textChanged.connect(self.onFormVolumeFunctionChanged)
self.chkOverwrite.toggled.connect(self.onOverwrite)
self.chkGenPython.toggled.connect(self.onGenPython)
self.chkGenC.toggled.connect(self.onGenC)
self.enablePyCheckboxSignal.connect(lambda: self.checkPyModelExists(self.model['filename']))
self.sendNewParamSignal.connect(self.updateParamTableFromEditor)
self.sendNewDescriptionSignal.connect(lambda description: self.txtDescription.setText(description))
self.sendNewIqSignal.connect(lambda iq: self.txtFunction.setPlainText(iq))
self.sendNewFormVolumeSignal.connect(lambda form_volume: self.txtFormVolumeFunction.setPlainText(form_volume))

#Boolean flags
self.chkSingle.clicked.connect(self.modelModified.emit)
self.chkOpenCL.clicked.connect(self.modelModified.emit)
self.chkStructure.clicked.connect(self.modelModified.emit)
self.chkFQ.clicked.connect(self.modelModified.emit)

def onPluginNameChanged(self):
"""
Respond to changes in plugin name
"""
self.model['filename'] = self.txtName.text()

self.checkPyModelExists(self.model['filename'])

self.modelModified.emit()

def onDescriptionChanged(self):
Expand Down Expand Up @@ -139,7 +187,7 @@ def onParamsChanged(self, row, column):

def onParamsPDChanged(self, row, column):
"""
Respond to changes in non-polydisperse parameter table
Respond to changes in polydisperse parameter table
"""
param = value = None
if self.tblParamsPD.item(row, 0):
Expand All @@ -152,9 +200,40 @@ def onParamsPDChanged(self, row, column):
self.model['pd_parameters'] = self.pd_parameter_dict

# Check if the update was Value for last row. If so, add a new row
if column == 1 and row == self.tblParamsPD.rowCount()-1:
if column == 1 and row == self.tblParamsPD.rowCount() - 1:
# Add a row
self.tblParamsPD.insertRow(self.tblParamsPD.rowCount())

# Check to see if there is any polydisperse parameter text present
any_text_present = False
for row in range(self.tblParamsPD.rowCount()):
for column in range(self.tblParamsPD.columnCount()):
table_cell_contents = self.tblParamsPD.item(row, column)
if table_cell_contents and table_cell_contents.text():
# There is text in at least one cell
any_text_present = True
break
if any_text_present:
# Display the Form Function box because there are polydisperse parameters
# First insert the first user-specified parameter into sample form volume function
if not self.displayed_default_form_volume:
text = \
"""volume = {0} * 0.0

return volume
""".format(self.model['pd_parameters'][0][0])
self.model['form_volume_text'] = text
self.txtFormVolumeFunction.insertPlainText(text)
self.displayed_default_form_volume = True

self.formFunctionBox.setVisible(True)
self.includePolydisperseFuncsSignal.emit()
break
else:
# Hide the Form Function box because there are no polydisperse parameters
self.formFunctionBox.setVisible(False)
self.omitPolydisperseFuncsSignal.emit()

self.modelModified.emit()


Expand All @@ -166,8 +245,20 @@ def onFunctionChanged(self):
# mind the performance!
#self.addTooltip()
new_text = self.txtFunction.toPlainText().lstrip().rstrip()
if new_text != self.model['text']:
self.model['text'] = new_text
if new_text != self.model['func_text']:
self.model['func_text'] = new_text
self.modelModified.emit()

def onFormVolumeFunctionChanged(self):
"""
Respond to changes in form volume function body
"""
# keep in mind that this is called every time the text changes.
# mind the performance!
#self.addTooltip()
new_text = self.txtFormVolumeFunction.toPlainText().lstrip().rstrip()
if new_text != self.model['form_volume_text']:
self.model['form_volume_text'] = new_text
self.modelModified.emit()

def onOverwrite(self):
Expand All @@ -176,6 +267,65 @@ def onOverwrite(self):
"""
self.model['overwrite'] = self.chkOverwrite.isChecked()
self.modelModified.emit()

def onGenPython(self):
"""
Respond to change in generate Python checkbox
"""
self.model['gen_python'] = self.chkGenPython.isChecked()
self.modelModified.emit()

def onGenC(self):
"""
Respond to change in generate C checkbox
"""
self.model['gen_c'] = self.chkGenC.isChecked()
self.modelModified.emit()

def checkPyModelExists(self, filename):
"""
Checks if a Python model exists in the user plugin directory and forces enabling Python checkbox if not
:param filename: name of the file (without extension)
"""
if not os.path.exists(os.path.join(find_plugins_dir(), filename + '.py')):
# If the user has not yet created a Python file for a specific filename, then force them to create one
self.chkGenPython.setChecked(True)
self.chkGenPython.setEnabled(False)
self.infoLabel.setText("No Python model of the same name detected. Generating Python model is required.")
self.infoLabel.setVisible(True)
else:
self.infoLabel.setVisible(False)
self.chkGenPython.setEnabled(True)
return os.path.exists(os.path.join(find_plugins_dir(), filename + '.py'))

def updateParamTableFromEditor(self, param_list):
"""
Add parameters sent from model editor to the parameter tables
:param param_list: list of parameters to add to the parameter tables [name, default_value, type]
"""
updated_params_non_pd = [param for param in param_list if param[2] != 'volume']
updated_params_pd = [param for param in param_list if param[2] == 'volume']

# Prepare the table for updating
self.tblParams.blockSignals(True)
self.tblParamsPD.blockSignals(True)
self.tblParams.clearContents()
self.tblParamsPD.clearContents()
self.tblParams.setRowCount(len(updated_params_non_pd) + 1)
self.tblParamsPD.setRowCount(len(updated_params_pd) + 1)

# Iterate over cells and add the new parameters to them
for table, params in [[self.tblParams, updated_params_non_pd], [self.tblParamsPD, updated_params_pd]]:
for row, param in enumerate(params):
for column in range(2):
if column < len(param): # Check if the column index is within the bounds of param length
item = QtWidgets.QTableWidgetItem(str(param[column]))
table.setItem(row, column, item)
else:
logging.info(f"Missing data for Row {row}, Column {column}")

self.tblParams.blockSignals(False)
self.tblParamsPD.blockSignals(False)

def getModel(self):
"""
Expand Down
Loading
Loading