Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
781f6d9
use equal aspect ratio in shape preview
klytje Jun 19, 2025
c8aa64f
separated models out into separate files
klytje Jun 19, 2025
5772f8d
re-enabled plugin model button
klytje Jun 19, 2025
be08519
separated out structure factors
klytje Jun 19, 2025
e08e4d3
separated out Experimental & Theoretical scattering
klytje Jun 19, 2025
eeeb86c
separated out DataClasses from Shape2SAS main script; should be the e…
klytje Jun 19, 2025
9ab0dec
removed external references to WeightedPairDistribution
klytje Jun 19, 2025
4c533ee
changed scattering calc from gui
klytje Jun 19, 2025
4833b53
disabled shadows on preview plot
klytje Jun 19, 2025
99dd2d3
added initial support for forwarding debye calc to ausaxs
klytje Jun 19, 2025
6621209
improved ausaxs support
klytje Jun 19, 2025
69279fe
moved genPlugin from qtgui to sascalc; changed signatures to match
klytje Jul 2, 2025
47648d6
'create plugin model' is now available without specifying constraints
klytje Jul 2, 2025
27ae524
simplified constraint script parsing
klytje Jul 13, 2025
8a2f25b
defined dX pars in plugin model
klytje Jul 13, 2025
1ea2782
added param update functionality to plugin
klytje Jul 13, 2025
17bc110
constraint window now opens on top
klytje Jul 13, 2025
339ec00
'create plugin' button is now enabled upon modifying the constraint text
klytje Jul 13, 2025
06b695d
update
klytje Jul 13, 2025
419e4c2
plugin script seems finished; fitter complains about negative vals
klytje Jul 13, 2025
6ac755a
fixed plugin generation; seems to work now
klytje Jul 26, 2025
f6b33c1
live update of plot
klytje Jul 26, 2025
c6a2ebd
added COM support
klytje Jul 26, 2025
edfd253
fixed minor plugin gen issue
klytje Jul 26, 2025
0c7094f
embedded log is now being used
klytje Jul 26, 2025
0840db8
support intermediate variables
klytje Jul 26, 2025
cf0178b
improved log warnings with linenos
klytje Jul 26, 2025
b8a9a12
[pre-commit.ci lite] apply automatic fixes for ruff linting errors
pre-commit-ci-lite[bot] Jul 27, 2025
118b90b
compatibility update after rebase
klytje Aug 22, 2025
644ee6e
[pre-commit.ci lite] apply automatic fixes for ruff linting errors
pre-commit-ci-lite[bot] Aug 22, 2025
b5ea2c8
fixed errors introduced by ruff
klytje Aug 22, 2025
1a9fd3a
explicit euler convention; bugfixing
klytje Oct 3, 2025
ce7d819
[pre-commit.ci lite] apply automatic fixes for ruff linting errors
pre-commit-ci-lite[bot] Oct 3, 2025
7acfa36
removed wildcard imports; enforced usage of new transform method
klytje Oct 3, 2025
09fbd6d
[pre-commit.ci lite] apply automatic fixes for ruff linting errors
pre-commit-ci-lite[bot] Oct 3, 2025
a7dd042
disabled plugin model button
klytje Nov 14, 2025
4b2353d
added missing fittingwidget import
klytje Nov 14, 2025
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
515 changes: 280 additions & 235 deletions src/sas/qtgui/Calculators/Shape2SAS/Constraints.py

Large diffs are not rendered by default.

61 changes: 41 additions & 20 deletions src/sas/qtgui/Calculators/Shape2SAS/DesignWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

from sas.qtgui.Calculators.Shape2SAS.ButtonOptions import ButtonOptions
from sas.qtgui.Calculators.Shape2SAS.Constraints import Constraints, logger
from sas.qtgui.Calculators.Shape2SAS.genPlugin import generatePlugin
from sas.qtgui.Calculators.Shape2SAS.PlotAspects.plotAspects import Canvas, ViewerPlotDesign
from sas.qtgui.Calculators.Shape2SAS.Tables.subunitTable import OptionLayout, SubunitTable
from sas.qtgui.Calculators.Shape2SAS.UI.DesignWindowUI import Ui_Shape2SAS
Expand All @@ -28,6 +27,7 @@

# Local SasView
from sas.qtgui.Utilities.ModelEditors.TabbedEditor.TabbedModelEditor import TabbedModelEditor
from sas.sascalc.shape2sas.PluginGenerator import generate_plugin
from sas.sascalc.shape2sas.Shape2SAS import (
ModelProfile,
ModelSystem,
Expand Down Expand Up @@ -91,9 +91,8 @@ def __init__(self, parent=None):
self.plugin.setEnabled(False)
self.modelTabButtonOptions.horizontalLayout_5.insertWidget(1, self.plugin)

# TODO: Remove these lines to enable the plugin model generation window - hidden for v6.1.0
self.line2.setHidden(True)
self.plugin.setHidden(True)
self.line2.setHidden(True)

#connect buttons
self.modelTabButtonOptions.reset.clicked.connect(self.onSubunitTableReset)
Expand All @@ -103,7 +102,7 @@ def __init__(self, parent=None):

#create layout for build model tab
self.viewerModel = ViewerModel()
self.subunitTable = SubunitTable()
self.subunitTable = SubunitTable(self.onClickingPlot)

modelVbox = QVBoxLayout()
modelHbox = QHBoxLayout()
Expand Down Expand Up @@ -164,7 +163,8 @@ def __init__(self, parent=None):
#TODO: implement in a future project

###Building Constraint window
self.constraint = Constraints()
self.constraint = Constraints(parent=self)
self.constraint.setWindowFlags(Qt.Window | Qt.Tool)
self.subunitTable.add.clicked.connect(self.addToVariableTable)
self.subunitTable.deleteButton.clicked.connect(self.deleteFromVariableTable)
self.subunitTable.table.clicked.connect(self.updateDeleteButton)
Expand All @@ -181,6 +181,8 @@ def __init__(self, parent=None):
def showConstraintWindow(self):
"""Get the Constraint window"""

self.constraint.setScreen(self.screen())
self.constraint.move(self.pos().x()+50, self.pos().y()+50)
self.constraint.show()

def checkedVariables(self):
Expand Down Expand Up @@ -255,21 +257,21 @@ def checkStateOfConstraints(self, fitPar: list[str], modelPars: list[list[str]],

#Has anything been written to the text editor
if constraintsStr:
#TODO: print to GUI output texteditor
return self.constraint.getConstraints(constraintsStr, fitPar, modelPars, modelVals, checkedPars)
self.constraint.log_embedded("Parsing constraints...")
return self.constraint.parseConstraintsText(constraintsStr, fitPar, modelPars, modelVals, checkedPars)

#Did the user only check parameters and click generate plugin
elif fitPar:
#Get default constraints
fitParLists = self.getConstraintsToTextEditor()
defaultConstraintsStr = self.constraint.getConstraintText(fitParLists)
#TODO: print to GUI output texteditor
self.constraint.log_embedded("No constraints text found. Creating unconstrained model")
return self.constraint.getConstraints(defaultConstraintsStr, fitPar, modelPars, modelVals, checkedPars)

#If not, return empty
else:
#all parameters are constant
#TODO: print to GUI output texteditor
self.constraint.log_embedded("Creating unconstrained model.")
return "", "", "", checkedPars

def enableButtons(self, toggle: bool):
Expand Down Expand Up @@ -487,6 +489,8 @@ def onClickingPlot(self):
self.plugin.setEnabled(columns > 0)

if not self.subunitTable.model.item(1, columns - 1):
self.viewerModel.setClearScatteringPlot()
self.viewerModel.setClearModelPlot()
return

modelProfile = self.getModelProfile(self.ifEmptyValue)
Expand Down Expand Up @@ -637,20 +641,30 @@ def getPluginModel(self):
#get chosen fit parameters
fitPar = self.getFitParameters()

logger.info("Retrieving and verifying constraints. . .")
logger.info("Retrieving and verifying constraints.")
#get parameters constraints
importStatement, parameters, translation, checkedPars = self.checkStateOfConstraints(fitPar, parNames, parVals, checkedPars)
usertext, checkedPars = self.checkStateOfConstraints(fitPar, parNames, parVals, checkedPars)

logger.info("Retrieving Model. . .")
logger.info("Retrieving Model.")
#conditional subunit table parameters
modelProfile = self.getModelProfile(self.ifFitPar, conditionBool=checkedPars, conditionFitPar=parNames)

model_str, full_path = generatePlugin(modelProfile, [importStatement, parameters, translation], fitPar, Npoints, prPoints, modelName)
model_str, full_path = generate_plugin(
modelProfile,
[parNames, parVals],
usertext,
fitPar,
Npoints,
prPoints,
modelName
)

#Write file to plugin model folder
TabbedModelEditor.writeFile(full_path, model_str)
self.communicator.customModelDirectoryChanged.emit()
logger.info(f"Successfully generated model {modelName}!")
self.constraint.log_embedded(f"Plugin model {modelName} has been generated and is now available in the Fit panel.")
self.constraint.createPlugin.setEnabled(False)

def onCheckingInput(self, input: str, default: str) -> str:
"""Check if the input not None. Otherwise, return default value"""
Expand Down Expand Up @@ -699,13 +713,20 @@ def getSimulatedSAXSData(self):

Distr = getPointDistribution(Profile, N)

Theo_calc = TheoreticalScatteringCalculation(System=ModelSystem(PointDistribution=Distr,
Stype=Stype, par=par,
polydispersity=polydispersity,
conc=conc,
sigma_r=sigma_r),
Calculation=Sim_par)
Theo_I = getTheoreticalScattering(Theo_calc)
model = ModelSystem(
PointDistribution=Distr,
Stype=Stype, par=par,
polydispersity=polydispersity,
conc=conc,
sigma_r=sigma_r
)

Theo_I = getTheoreticalScattering(
TheoreticalScatteringCalculation(
System=model,
Calculation=Sim_par
)
)
Sim_calc = SimulateScattering(q=Theo_I.q, I0=Theo_I.I0, I=Theo_I.I, exposure=exposure)
Sim_SAXS = getSimulatedScattering(Sim_calc)

Expand Down
25 changes: 18 additions & 7 deletions src/sas/qtgui/Calculators/Shape2SAS/Tables/subunitTable.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,11 +333,12 @@ class CustomStandardItem(QStandardItem):
"""Custom QStandardItem to set initial values, roles
and take care of subunit and colour case"""

def __init__(self, prefix="", unit="", tooltip="", default_value=None):
def __init__(self, prefix="", unit="", tooltip="", default_value=None, plot_callback=None):
super().__init__(str(default_value))
self.prefix = prefix
self.tooltip = tooltip
self.unit = unit
self.plot_callback = plot_callback

self.setData(default_value, Qt.EditRole)

Expand All @@ -350,7 +351,6 @@ def data(self, role=Qt.DisplayRole):
return f"{self.prefix}{value}{self.unit}"
elif role == Qt.ToolTipRole:
return self.tooltip

return value


Expand All @@ -366,6 +366,8 @@ def setData(self, value, role=Qt.EditRole):
super().setData(value, Qt.DisplayRole)
else:
super().setData(value, role)
if self.plot_callback:
self.plot_callback()


class CustomDelegate(QStyledItemDelegate):
Expand Down Expand Up @@ -433,13 +435,14 @@ def __init__(self, parent=None):
class SubunitTable(QWidget, Ui_SubunitTableController):
"""Subunit table functionality and design for the model tab"""

def __init__(self):
def __init__(self, updatePlotCallback=None):
super(SubunitTable, self).__init__()
self.setupUi(self)

self.columnEyeKeeper = []
self.restrictedRowsPos = []

self.updatePlotCallback = updatePlotCallback
self.initializeModel()
self.initializeSignals()
self.setSubunitOptions()
Expand Down Expand Up @@ -508,8 +511,11 @@ def onAdding(self):
#if row is contained in the subunit
if row in subunitName.keys():
paintedName = subunitName[row] + f"{to_column_name}" + " = "
item = CustomStandardItem(paintedName, subunitUnits[row],
subunitTooltip[row], subunitDefault_value[row])
item = CustomStandardItem(
paintedName, subunitUnits[row],
subunitTooltip[row], subunitDefault_value[row],
plot_callback=self.updatePlotCallback
)
else:
#no input for this row
item = CustomStandardItem("", "", "", "")
Expand All @@ -529,8 +535,11 @@ def onAdding(self):
attr = getattr(OptionLayout, row.value)
method = MethodType(attr, OptionLayout)
name, defaultVal, units, tooltip, _, _ = method()
item = CustomStandardItem(name[row] + f"{to_column_name}" + " = ", units[row],
tooltip[row], defaultVal[row])
item = CustomStandardItem(
name[row] + f"{to_column_name}" + " = ", units[row],
tooltip[row], defaultVal[row],
plot_callback=self.updatePlotCallback
)

items.append(item)

Expand All @@ -539,6 +548,7 @@ def onAdding(self):
self.setSubunitRestriction(subunitName.keys())
self.table.resizeColumnsToContents()
self.setButtonSpinboxBounds()
self.updatePlotCallback() # Update the plot after adding a subunit


def onDeleting(self):
Expand All @@ -554,6 +564,7 @@ def onDeleting(self):
#clear the table if no columns are left
if not self.model.columnCount():
self.onClearSubunitTable()
self.updatePlotCallback() # Update the plot after removing a subunit


def setButtonSpinboxBounds(self):
Expand Down
6 changes: 6 additions & 0 deletions src/sas/qtgui/Calculators/Shape2SAS/Tables/variableTable.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ def __init__(self):

self.initializeVariableModel()
self.setDefaultLayout()
self.on_item_changed_callback = None


def initializeVariableModel(self):
Expand Down Expand Up @@ -99,8 +100,13 @@ def setVariableTableData(self, names: list[str], column: int):
itemNum.setTextAlignment(Qt.AlignCenter)
itemNum.setFont(font)
self.variableModel.insertRow(numrow, [itemName, itemNum])
self.variableModel.itemChanged.connect(self.onItemChanged)
itemNum.setFlags(itemNum.flags() & ~Qt.ItemIsSelectable & ~Qt.ItemIsEditable)

def onItemChanged(self, item):
"""Handle item changes in the variable table"""
if self.on_item_changed_callback:
self.on_item_changed_callback(item)

def removeTableData(self, row):
"""Remove data from table"""
Expand Down
31 changes: 23 additions & 8 deletions src/sas/qtgui/Calculators/Shape2SAS/ViewerModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

# Local Perspectives
from sas.qtgui.Calculators.Shape2SAS.ViewerAllOptions import ViewerButtons, ViewerModelRadius
from sas.sascalc.shape2sas.Shape2SAS import ModelPointDistribution, TheoreticalScattering
from sas.sascalc.shape2sas.Models import ModelPointDistribution
from sas.sascalc.shape2sas.TheoreticalScattering import TheoreticalScattering


class ViewerModel(QWidget):
Expand All @@ -22,6 +23,9 @@ def __init__(self, parent=None):
###3D plot view of model
self.scatter = Q3DScatter()

# remove shadows
self.scatter.setShadowQuality(Q3DScatter.ShadowQuality.ShadowQualityNone)

"""
NOTE: Orignal intend was to create
QScatter3DSeries() in setPlot() method. However,
Expand Down Expand Up @@ -143,13 +147,24 @@ def initialiseAxis(self):
self.scatter.setAxisZ(self.Z_ax)

def setAxis(self, x_range: (float, float), y_range: (float, float), z_range: (float, float)):
"""Set axis for the model"""

#FIXME: even if min and max are the same for X, Y, Z, a sphere still looks like an ellipsoid
#Tried with global min and max, and by centering the model, but no success.
self.X_ax.setRange(*x_range)
self.Y_ax.setRange(*y_range)
self.Z_ax.setRange(*z_range)
"""Set axis for the model with equal aspect ratio"""

# Calculate the overall range to ensure equal aspect ratio
x_min, x_max = x_range
y_min, y_max = y_range
z_min, z_max = z_range
x_center = (x_min + x_max) / 2
y_center = (y_min + y_max) / 2
z_center = (z_min + z_max) / 2
max_range = max(x_max - x_min, y_max - y_min, z_max - z_min)

# Add some padding
half_range = (max_range*1.1) / 2

# Set equal ranges for all axes centered on their respective centers
self.X_ax.setRange(x_center - half_range, x_center + half_range)
self.Y_ax.setRange(y_center - half_range, y_center + half_range)
self.Z_ax.setRange(z_center - half_range, z_center + half_range)

self.scatter.setAxisX(self.X_ax)
self.scatter.setAxisY(self.Y_ax)
Expand Down
Loading