diff --git a/EasyReflectometryApp/Logic/Proxies/Calculator.py b/EasyReflectometryApp/Logic/Proxies/Calculator.py index b3fa77a7..6ae38513 100644 --- a/EasyReflectometryApp/Logic/Proxies/Calculator.py +++ b/EasyReflectometryApp/Logic/Proxies/Calculator.py @@ -43,7 +43,7 @@ def currentCalculatorIndex(self, new_index: int): model = self.parent._data_proxy._data[self.parent._data_proxy.currentDataIndex].model model.switch_interface(new_name) - self.parent._fitter_proxy.eFitter.easy_f.initialize(self.parent._model_proxy._model, + self.parent._fitter_proxy.eFitter.easy_science_multi_fitter.initialize(self.parent._model_proxy._model, self.parent._interface.fit_func) self.calculatorChanged.emit() diff --git a/EasyReflectometryApp/Logic/Proxies/Fitter.py b/EasyReflectometryApp/Logic/Proxies/Fitter.py index 5d657089..d15229e5 100644 --- a/EasyReflectometryApp/Logic/Proxies/Fitter.py +++ b/EasyReflectometryApp/Logic/Proxies/Fitter.py @@ -8,7 +8,7 @@ from easyscience import global_object -from easyreflectometry.fitting import Fitter as easyFitter +from easyreflectometry.fitting import MultiFitter as easyFitter class Fitter(QThread): @@ -148,7 +148,7 @@ def nonthreaded_fit(self): weights = [1 / i.ye for i in exp_data] method = self.parent.minimizer._current_minimizer.method - res = self.eFitter.easy_f.fit(x, y, weights=weights, method=method) + res = self.eFitter.easy_science_multi_fitter.fit(x, y, weights=weights, method=method) self._setFitResults(res) # def threaded_fit(self): diff --git a/EasyReflectometryApp/Logic/Proxies/Minimizer.py b/EasyReflectometryApp/Logic/Proxies/Minimizer.py index ebf9ffec..2359d2e9 100644 --- a/EasyReflectometryApp/Logic/Proxies/Minimizer.py +++ b/EasyReflectometryApp/Logic/Proxies/Minimizer.py @@ -32,7 +32,7 @@ def minimizerNames(self): @Property(int, notify=currentMinimizerChanged) def currentMinimizerIndex(self): - current_name = self.parent._fitter_proxy.eFitter.easy_f.minimizer.name + current_name = self.parent._fitter_proxy.eFitter.easy_science_multi_fitter.minimizer.name return self.minimizerNames.index(current_name) @currentMinimizerIndex.setter @@ -41,7 +41,7 @@ def currentMinimizerIndex(self, new_index: int): if self.currentMinimizerIndex == new_index: return new_name = self.minimizerNames[new_index] - self.parent._fitter_proxy.eFitter.easy_f.switch_minimizer(new_name) + self.parent._fitter_proxy.eFitter.easy_science_multi_fitter.switch_minimizer(new_name) self.currentMinimizerChanged.emit() @Property(int, notify=currentMinimizerMethodChanged) diff --git a/EasyReflectometryApp/Logic/Proxies/Parameter.py b/EasyReflectometryApp/Logic/Proxies/Parameter.py index e1722a86..ad64751b 100644 --- a/EasyReflectometryApp/Logic/Proxies/Parameter.py +++ b/EasyReflectometryApp/Logic/Proxies/Parameter.py @@ -303,7 +303,7 @@ def toggleConstraintByIndex(self, index, enabled): self.parent.sampleChanged.emit() def removeAllConstraints(self): - for _ in range(len(self.eFitter.easy_f.fit_constraints())): + for _ in range(len(self.eFitter.easy_science_multi_fitter.fit_constraints())): self.removeConstraintByIndex(0) self.constraintsRemoved.emit() self.sampleChanged.emit() diff --git a/EasyReflectometryApp/Logic/Proxies/State.py b/EasyReflectometryApp/Logic/Proxies/State.py index b838fe3a..87335698 100644 --- a/EasyReflectometryApp/Logic/Proxies/State.py +++ b/EasyReflectometryApp/Logic/Proxies/State.py @@ -49,7 +49,7 @@ def statusModelAsObj(self): "calculation": self.parent._interface.current_interface_name, "minimization": - f'{self.parent._fitter_proxy.eFitter.easy_f.minimizer._method}' + f'{self.parent._fitter_proxy.eFitter.easy_science_multi_fitter.minimizer._method}' } self._status_model = obj return obj @@ -63,7 +63,7 @@ def statusModelAsXml(self): "label": "Minimization", "value": - f'{self.parent._fitter_proxy.eFitter.easy_f.minimizer._method}' + f'{self.parent._fitter_proxy.eFitter.easy_science_multi_fitter.minimizer._method}' }] xml = XMLSerializer().encode({"item":model}, data_only=True) return xml diff --git a/src_qt6/EasyReflectometryApp/Backends/Mock/Analysis.qml b/src_qt6/EasyReflectometryApp/Backends/Mock/Analysis.qml index 1d24afd4..e368da97 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Mock/Analysis.qml +++ b/src_qt6/EasyReflectometryApp/Backends/Mock/Analysis.qml @@ -3,7 +3,101 @@ pragma Singleton import QtQuick QtObject { + readonly property var minimizersAvailable: ['minimizer_1', 'minimizer_2', 'minimizer_3'] + readonly property int minimizerCurrentIndex: 0 + readonly property var calculatorsAvailable: ['calculator_1', 'calculator_2', 'calculator_3'] + readonly property int calculatorCurrentIndex: 1 + + readonly property var experimentsAvailable: ['experiment_1', 'experiment_2', 'experiment_3'] + readonly property int experimentCurrentIndex: 2 + + // Minimizer + readonly property double minimizerTolerance: 1.0 + readonly property int minimizerMaxIterations: 2 + + // Fitting + readonly property string fittingStatus: ''//undefined //'Success' readonly property bool isFitFinished: true + readonly property bool fittingRunning: false + + // Parameters + property int currentParameterIndex: 0 + readonly property int modelParametersCount: 10 + readonly property int experimentParametersCount: 20 + readonly property int freeParametersCount: 100 + readonly property int fixedParametersCount: 200 + readonly property var fitableParameters: [ + { + 'name': 'name 1', + 'value': 1.0, + 'error': -1.23456, + 'max': 100.0, + 'min': -100.0, + 'units': 'u1', + 'fit': true, + 'from': -10.0, + 'to': 10.0, + }, + { + 'name': 'name 2', + 'value': 2.0, + 'error': -2.34567, + 'max': 200.0, + 'min': -200.0, + 'units': 'u2', + 'fit': false, + 'from': -20.0, + 'to': 20.0, + }, + { + 'name': 'name 3', + 'value': 3.0, + 'error': -3.45678, + 'max': 300.0, + 'min': -300.0, + 'units': 'u3', + 'fit': true, + 'from': -30.0, + 'to': 30.0, + }, + ] + function setCurrentParameterMin(value) { + console.debug(`setCurrentParameterMin ${value}`) + } + function setCurrentParameterMax(value) { + console.debug(`setCurrentParameterMax ${value}`) + } + function setCurrentParameterValue(value) { + console.debug(`setCurrentParameterValue ${value}`) + } + function setCurrentParameterFit(value) { + console.debug(`setCurrentParameterFit ${value}`) + } + + // Setters + function setCurrentParameterIndex(value) { + currentParameterIndex = value + console.debug(`setCurrentParameterIndex ${value}`) + } + function setCalculatorCurrentIndex(value) { + console.debug(`setCalculatorCurrentIndex ${value}`) + } + function setExperimentCurrentIndex(value) { + console.debug(`setExperimentCurrentIndex ${value}`) + } + function setMinimizerCurrentIndex(value) { + console.debug(`setMinimizer ${value}`) + } + function setMinimizerTolerance(value) { + console.debug(`setMinimizerTolerance ${value}`) + } + function setMinimizerMaxIterations(value) { + console.debug(`setMinimizerMaxIterations ${value}`) + } + //Actions + function fittingStartStop() { + console.debug('fittingStartStop') + } } diff --git a/src_qt6/EasyReflectometryApp/Backends/Mock/Experiment.qml b/src_qt6/EasyReflectometryApp/Backends/Mock/Experiment.qml index 9fd8382e..6bdbb428 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Mock/Experiment.qml +++ b/src_qt6/EasyReflectometryApp/Backends/Mock/Experiment.qml @@ -8,10 +8,6 @@ QtObject { property double background: 2. property string resolution: '3.00' - property double q_min: 4. - property double q_max: 5. - property int q_resolution: 6 - // Setters function setScaling(value) { console.debug(`setScaling ${value}`) @@ -23,16 +19,6 @@ QtObject { console.debug(`setResolution ${value}`) } - function setQMin(value) { - console.debug(`setQMin ${value}`) - } - function setQMax(value) { - console.debug(`setQMax ${value}`) - } - function setQElements(value) { - console.debug(`setQElements ${value}`) - } - function load(path) { console.debug(`Loading experiment from ${path}`) } diff --git a/src_qt6/EasyReflectometryApp/Backends/Mock/Plotting.qml b/src_qt6/EasyReflectometryApp/Backends/Mock/Plotting.qml index cb1f2ce5..9317594a 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Mock/Plotting.qml +++ b/src_qt6/EasyReflectometryApp/Backends/Mock/Plotting.qml @@ -12,12 +12,25 @@ QtObject { property double sldMaxX: 2. property double sldMinY: -20. property double sldMaxY: 20. - property double experimentMinX: -2. - property double experimentMaxX: 2. - property double experimentMinY: -20. - property double experimentMaxY: 20. + property double experimentMinX: -3. + property double experimentMaxX: 3. + property double experimentMinY: -30. + property double experimentMaxY: 30. + property double analysisMinX: -4. + property double analysisMaxX: 4. + property double analysisMinY: -40. + property double analysisMaxY: 40. function setQtChartsSerieRef(value1, value2, value3) { console.debug(`setQtChartsSerieRef ${value1}, ${value2}, ${value3}`) } + + function drawCalculatedOnSampleChart(){ + console.debug(`drawCalculatedOnSampleChart`) + } + + function drawCalculatedOnSldChart(){ + console.debug(`drawCalculatedOnSldChart`) + } + } diff --git a/src_qt6/EasyReflectometryApp/Backends/Mock/Sample.qml b/src_qt6/EasyReflectometryApp/Backends/Mock/Sample.qml index 34994d7a..8f280388 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Mock/Sample.qml +++ b/src_qt6/EasyReflectometryApp/Backends/Mock/Sample.qml @@ -4,7 +4,7 @@ import QtQuick QtObject { // MATERIALS - property int currentMaterialIndex: -1 + readonly property int currentMaterialIndex: -1 // Getters readonly property var materials: [ @@ -264,4 +264,33 @@ QtObject { function moveSelectedLayerDown() { console.debug(`moveSelectedLayerDown ${currentLayerIndex}`) } + + // Constraints + readonly property var parameterNames: [ + 'parameter 1', + 'parameter 2', + 'parameter 3' + ] + readonly property var relationOperators: ['=', '<', '>'] + readonly property var arithmicOperators: ['', '*', '/', '+', '-'] + + function addConstraint(value1, value2, value3, value4, value5) { + console.debug(`addConstraint ${value1} ${value2} ${value3} ${value4} ${value5}`) + } + + // Q Range + property double q_min: 4. + property double q_max: 5. + property int q_resolution: 6 + + // Setters + function setQMin(value) { + console.debug(`setQMin ${value}`) + } + function setQMax(value) { + console.debug(`setQMax ${value}`) + } + function setQElements(value) { + console.debug(`setQElements ${value}`) + } } diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/analysis.py b/src_qt6/EasyReflectometryApp/Backends/Py/analysis.py index ba7781a7..2ea01b4e 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/analysis.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/analysis.py @@ -3,21 +3,170 @@ from PySide6.QtCore import Slot from PySide6.QtCore import Property +from typing import List +from typing import Optional + from easyreflectometry import Project as ProjectLib -#from .logic.Analysis import Analysis as AnalysisLogic +from .logic.parameters import Parameters as ParametersLogic +from .logic.fitting import Fitting as FittingLogic +from .logic.calculators import Calculators as CalculatorsLogic +from .logic.experiments import Experiments as ExperimentLogic +from .logic.minimizers import Minimizers as MinimizersLogic class Analysis(QObject): - fitFinishedChanged = Signal() + minimizerChanged = Signal() + calculatorChanged = Signal() + experimentsChanged = Signal() + parametersChanged = Signal() + parametersIndexChanged = Signal() + fittingChanged = Signal() + externalMinimizerChanged = Signal() + externalParametersChanged = Signal() + externalCalculatorChanged = Signal() + externalFittingChanged = Signal() def __init__(self, project_lib: ProjectLib, parent=None): super().__init__(parent) -# self._logic = AnalysisLogic(project_lib) + self._paramters_logic = ParametersLogic(project_lib) + self._fitting_logic = FittingLogic(project_lib) + self._calculators_logic = CalculatorsLogic(project_lib) + self._experiments_logic = ExperimentLogic(project_lib) + self._minimizers_logic = MinimizersLogic(project_lib) + self._chached_paramters = None + + ######################## + ## Fitting + @Property(str, notify=fittingChanged) + def fittingStatus(self) -> str: + return self._fitting_logic.status - # Setters and getters + @Property(bool, notify=fittingChanged) + def fittingRunning(self) -> bool: + return self._fitting_logic.running - @Property(bool, notify=fitFinishedChanged) + @Property(bool, notify=fittingChanged) def isFitFinished(self) -> bool: - return True + return self._fitting_logic.fit_finished + + @Slot(None) + def fittingStartStop(self) -> None: + self._fitting_logic.start_stop() + self.fittingChanged.emit() + self._clearCacheAndEmitParametersChanged() + self.externalFittingChanged.emit() + + ######################## + ## Calculators + @Property('QVariantList', notify=calculatorChanged) + def calculatorsAvailable(self) -> List[str]: + return self._calculators_logic.available() + @Property(int, notify=calculatorChanged) + def calculatorCurrentIndex(self) -> int: + return self._calculators_logic.current_index() + @Slot(int) + def setCalculatorCurrentIndex(self, new_value: int) -> None: + if self._calculators_logic.set_current_index(new_value): + self.calculatorChanged.emit() + self.externalCalculatorChanged.emit() + + ######################## + ## Experiments + @Property('QVariantList', notify=experimentsChanged) + def experimentsAvailable(self) -> List[str]: + return self._experiments_logic.available() + @Property(int, notify=experimentsChanged) + def experimentCurrentIndex(self) -> int: + return self._experiments_logic.current_index() + @Slot(int) + def setExperimentCurrentIndex(self, new_value: int) -> None: + self._experiments_logic.set_current_index(new_value) + + ######################## + ## Minimizers + @Property('QVariantList', notify=minimizerChanged) + def minimizersAvailable(self) -> List[str]: + return self._minimizers_logic.minimizers_available() + @Property(int, notify=minimizerChanged) + def minimizerCurrentIndex(self) -> int: + return self._minimizers_logic.minimizer_current_index() + @Slot(int) + def setMinimizerCurrentIndex(self, new_value: int) -> None: + if self._minimizers_logic.set_minimizer_current_index(new_value): + self.minimizerChanged.emit() + self.externalMinimizerChanged.emit() + + @Property('QVariant', notify=minimizerChanged) + def minimizerTolerance(self) -> Optional[float]: + return self._minimizers_logic.tolerance + + @Property('QVariant', notify=minimizerChanged) + def minimizerMaxIterations(self) -> Optional[int]: + return self._minimizers_logic.max_iterations + + @Slot(float) + def setMinimizerTolerance(self, new_value: float) -> None: + if self._minimizers_logic.set_tolerance(new_value): + self.minimizerChanged.emit() + + @Slot(int) + def setMinimizerMaxIterations(self, new_value: int) -> None: + if self._minimizers_logic.set_max_iterations(new_value): + self.minimizerChanged.emit() + + ############# + ## Parameters + @Property('QVariantList', notify=parametersChanged) + def fitableParameters(self) -> List[dict[str]]: + if self._chached_paramters is None: + self._chached_paramters = self._paramters_logic.parameters + return self._chached_paramters + @Property(int, notify=parametersIndexChanged) + def currentParameterIndex(self) -> int: + return self._paramters_logic.current_index() + @Slot(int) + def setCurrentParameterIndex(self, new_value: int) -> None: + if self._paramters_logic.set_current_index(new_value): + self.parametersIndexChanged.emit() + + @Property(int, notify=parametersChanged) + def freeParametersCount(self) -> int: + return self._paramters_logic.count_free_parameters() + + @Property(int, notify=parametersChanged) + def fixedParametersCount(self) -> int: + return self._paramters_logic.count_fixed_parameters() + + @Property(int, notify=parametersChanged) + def modelParametersCount(self) -> int: + return 3 + + @Property(int, notify=parametersChanged) + def experimentParametersCount(self) -> int: + return 3 + + @Slot(float) + def setCurrentParameterValue(self, new_value: float) -> None: + if self._paramters_logic.set_current_parameter_value(new_value): + self._clearCacheAndEmitParametersChanged() + self.externalParametersChanged.emit() + + @Slot(float) + def setCurrentParameterMin(self, new_value: float) -> None: + if self._paramters_logic.set_current_parameter_min(new_value): + self._clearCacheAndEmitParametersChanged() + + @Slot(float) + def setCurrentParameterMax(self, new_value: float) -> None: + if self._paramters_logic.set_current_parameter_max(new_value): + self._clearCacheAndEmitParametersChanged() + @Slot(bool) + def setCurrentParameterFit(self, new_value: bool) -> None: + if self._paramters_logic.set_current_parameter_fit(new_value): + self._clearCacheAndEmitParametersChanged() + + def _clearCacheAndEmitParametersChanged(self): + self._chached_paramters = None + self.parametersChanged.emit() \ No newline at end of file diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/experiment.py b/src_qt6/EasyReflectometryApp/Backends/Py/experiment.py index bba6372b..438c2c4c 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/experiment.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/experiment.py @@ -12,6 +12,7 @@ class Experiment(QObject): experimentChanged = Signal() + externalExperimentChanged = Signal() def __init__(self, project_lib: ProjectLib, parent=None): super().__init__(parent) @@ -30,18 +31,6 @@ def background(self) -> float: def resolution(self) -> str: return self._model_logic.resolution_at_current_index - @Property(float, notify=experimentChanged) - def q_min(self) -> float: - return self._project_logic.q_min - - @Property(float, notify=experimentChanged) - def q_max(self) -> float: - return self._project_logic.q_max - - @Property(int, notify=experimentChanged) - def q_resolution(self) -> int: - return self._project_logic.q_resolution - @Property(bool, notify=experimentChanged) def experimentalData(self) -> bool: return self._project_logic.experimental_data_at_current_index @@ -53,36 +42,19 @@ def setModelIndex(self, value: int) -> None: @Slot(float) def setScaling(self, new_value: float) -> None: - self._model_logic.set_scaling_at_current_index(new_value) - self.experimentChanged.emit() + if self._model_logic.set_scaling_at_current_index(new_value): + self.experimentChanged.emit() + self.externalExperimentChanged.emit() @Slot(float) def setBackground(self, new_value: float) -> None: - self._model_logic.set_background_at_current_index(new_value) - self.experimentChanged.emit() - - @Slot(float) - def setResolution(self, new_value: float) -> None: - self._model_logic.set_resolution_at_current_index(new_value) - self.experimentChanged.emit() - - @Slot(float) - def setQMin(self, new_value: float) -> None: - self._project_logic.set_q_min(new_value) - self.experimentChanged.emit() - - @Slot(float) - def setQMax(self, new_value: float) -> None: - self._project_logic.set_q_max(new_value) - self.experimentChanged.emit() - - @Slot(int) - def setQElements(self, new_value: float) -> None: - self._project_logic.set_q_resolution(new_value) - self.experimentChanged.emit() + if self._model_logic.set_background_at_current_index(new_value): + self.experimentChanged.emit() + self.externalExperimentChanged.emit() # Actions @Slot(str) def load(self, path: str) -> None: self._project_logic.load_experiment(generalizePath(path)) - self.experimentChanged.emit() \ No newline at end of file + self.experimentChanged.emit() + self.externalExperimentChanged.emit() diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/assemblies.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/assemblies.py index ce0376e9..ca7fa5c7 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/logic/assemblies.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/assemblies.py @@ -10,30 +10,26 @@ class Assemblies: def __init__(self, project_lib: ProjectLib): self._project_lib = project_lib - self._model_index = 0 - self._assembly_index = 0 - self._assemblies: Sample = project_lib._models[self._model_index].sample # Sample is a collection of assemblies - def set_model_index(self, new_value: int) -> None: - self._model_index = new_value - self._assembly_index = 0 - self._assemblies = self._project_lib._models[self._model_index].sample + @property + def _assemblies(self) -> Sample: + return self._project_lib._models[self._project_lib.current_model_index].sample # Sample is a collection of assemblies @property def index(self) -> int: - return self._assembly_index + return self._project_lib.current_assembly_index @index.setter def index(self, new_value: Union[int, str]) -> None: - self._assembly_index = int(new_value) + self._project_lib.current_assembly_index = int(new_value) @property def name_at_current_index(self) -> str: - return self._assemblies[self._assembly_index].name + return self._assemblies[self.index].name @property def type_at_current_index(self) -> str: - return self._assemblies[self._assembly_index].type + return self._assemblies[self.index].type @property def assemblies(self) -> list[dict[str, str]]: @@ -48,74 +44,79 @@ def remove_at_index(self, value: str) -> None: def add_new(self) -> None: self._assemblies.add_assembly() + index_si = self._project_lib.get_index_si() + self._assemblies[-1].layers[0].material = self._project_lib._materials[index_si] def duplicate_selected(self) -> None: - self._assemblies.duplicate_assembly(self._assembly_index) + self._assemblies.duplicate_assembly(self.index) def move_selected_up(self) -> None: - if self._assembly_index > 0: - self._assemblies.move_up(self._assembly_index) - self._assembly_index = self._assembly_index - 1 + if self.index > 0: + self._assemblies.move_up(self.index) + self.index = self.index - 1 def move_selected_down(self) -> None: - if self._assembly_index < len(self._assemblies) - 1: - self._assemblies.move_down(self._assembly_index) - self._assembly_index = self._assembly_index + 1 + if self.index < len(self._assemblies) - 1: + self._assemblies.move_down(self.index) + self.index = self.index + 1 def set_name_at_current_index(self, new_value: str) -> None: - self._assemblies[self._assembly_index].name = new_value + if self._assemblies[self.index].name != new_value: + self._assemblies[self.index].name = new_value + return True + return False - def set_type_at_current_index(self, new_value: str) -> None: - if new_value == self._assemblies[self._assembly_index].type: - return + def set_type_at_current_index(self, new_value: str) -> bool: + if new_value == self._assemblies[self.index].type: + return False if new_value == 'Multi-layer': - if 'Si' not in [material.name for material in self._project_lib._materials]: - self._project_lib._materials.add_material('Si', 2.07, 0.0) - index_si = [material.name for material in self._project_lib._materials].index('Si') new_assembly = Multilayer() - new_assembly.layers[0].material = self._project_lib._materials[index_si] + new_assembly.layers[0].material = self._assemblies[self.index].layers.data[0].material elif new_value == 'Repeating Multi-layer': - if 'Si' not in [material.name for material in self._project_lib._materials]: - self._project_lib._materials.add_material('Si', 2.07, 0.0) - index_si = [material.name for material in self._project_lib._materials].index('Si') new_assembly = RepeatingMultilayer() - new_assembly.layers[0].material = self._project_lib._materials[index_si] + new_assembly.layers[0].material = self._assemblies[self.index].layers.data[0].material elif new_value == 'Surfactant Layer': - if 'Air' not in [material.name for material in self._project_lib._materials]: - self._project_lib._materials.add_material('Air', 0.0, 0.0) - if 'D2O' not in [material.name for material in self._project_lib._materials]: - self._project_lib._materials.add_material('D2O', 6.36, 0.0) - index_air = [material.name for material in self._project_lib._materials].index('Air') - index_d2o = [material.name for material in self._project_lib._materials].index('D2O') + index_air = self._project_lib.get_index_air() + index_d2o = self._project_lib.get_index_d2o() new_assembly = SurfactantLayer() new_assembly.layers[0].solvent = self._project_lib._materials[index_air] new_assembly.layers[1].solvent = self._project_lib._materials[index_d2o] - new_assembly.name = self._assemblies[self._assembly_index].name + new_assembly.name = self._assemblies[self.index].name - self._assemblies[self._assembly_index] = new_assembly - self._project_lib._models[self._model_index].sample._disable_changes_to_outermost_layers() + self._assemblies[self.index] = new_assembly + self._project_lib._models[self._project_lib.current_model_index].sample._disable_changes_to_outermost_layers() + return True # Only for repeating multilayer @property def repetitions_at_current_index(self) -> str: - if isinstance(self._assemblies[self._assembly_index], RepeatingMultilayer): - return str(int(self._assemblies[self._assembly_index].repetitions.value)) + if isinstance(self._assemblies[self.index], RepeatingMultilayer): + return str(int(self._assemblies[self.index].repetitions.value)) return '1' - def set_repeated_layer_reptitions(self, new_value: int) -> None: - if isinstance(self._assemblies[self._assembly_index], RepeatingMultilayer): - self._assemblies[self._assembly_index].repetitions.value = new_value - + def set_repeated_layer_reptitions(self, new_value: int) -> bool: + if isinstance(self._assemblies[self.index], RepeatingMultilayer): + if new_value != self._assemblies[self.index].repetitions.value: + self._assemblies[self.index].repetitions.value = new_value + return True + return False + # # Only for surfactant layer - def set_constrain_apm(self, new_value: str) -> None: - if isinstance(self._assemblies[self._assembly_index], SurfactantLayer): - self._assemblies[self._assembly_index].constrain_area_per_molecule = new_value - - def set_conformal_roughness(self, new_value: str) -> None: - if isinstance(self._assemblies[self._assembly_index], SurfactantLayer): - self._assemblies[self._assembly_index].conformal_roughness = new_value + def set_constrain_apm(self, new_value: str) -> bool: + if isinstance(self._assemblies[self.index], SurfactantLayer): + if self._assemblies[self.index].constrain_area_per_molecule != new_value: + self._assemblies[self.index].constrain_area_per_molecule = new_value + return True + return False + + def set_conformal_roughness(self, new_value: str) -> bool: + if isinstance(self._assemblies[self.index], SurfactantLayer): + if self._assemblies[self.index].conformal_roughness != new_value: + self._assemblies[self.index].conformal_roughness = new_value + return True + return False def _from_assemblies_collection_to_list_of_dicts(assemblies_collection: Sample) -> list[dict[str, str]]: diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/calculators.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/calculators.py new file mode 100644 index 00000000..844fcdc3 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/calculators.py @@ -0,0 +1,23 @@ +from easyreflectometry import Project as ProjectLib + + +class Calculators: + def __init__(self, project_lib: ProjectLib): + self._project_lib = project_lib + self._list_available_calculators = self._project_lib._calculator.available_interfaces + self._current_index = 0 + + def available(self) -> list[str]: + return self._list_available_calculators + + def current_index(self) -> int: + return self._current_index + + def set_current_index(self, new_value: int) -> None: + if new_value != self._current_index: + self._current_index = new_value + new_calculator = self._list_available_calculators[new_value] + self._project_lib._calculator.switch(new_calculator) + return True + return False + diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/experiments.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/experiments.py new file mode 100644 index 00000000..47ee1078 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/experiments.py @@ -0,0 +1,25 @@ +from easyreflectometry import Project as ProjectLib + + +class Experiments: + def __init__(self, project_lib: ProjectLib): + self._project_lib = project_lib + self._current_index = 0 + + def available(self) -> list[str]: + experiments_name = [] + try: + experiments_name.append(self._project_lib.experimental_data_for_model_at_index().name) + except IndexError: + pass + return experiments_name + + def current_index(self) -> int: + return self._current_index + + def set_current_index(self, new_value: int) -> None: + if new_value != self._current_index: + new_value = self._current_index + print(new_value) + return True + return False diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/fitting.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/fitting.py new file mode 100644 index 00000000..ebea015f --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/fitting.py @@ -0,0 +1,39 @@ +# from easyscience import AvailableMinimizers +from easyreflectometry import Project as ProjectLib +from easyscience.fitting import FitResults + + +class Fitting: + def __init__(self, project_lib: ProjectLib): + self._project_lib = project_lib + self._running = False + self._finished = True + self._result: FitResults = None + + @property + def status(self) -> str: + if self._result is None: + return False + else: + return self._result.success + + @property + def running(self) -> bool: + return self._running + + @property + def fit_finished(self) -> bool: + return self._finished + + def start_stop(self) -> None: + if self._running: + # Stop running the fitting + self._running = False + else: + # Start running the fitting + self._running = True + self._finished = False + exp_data = self._project_lib.experimental_data_for_model_at_index(0) + self._result = self._project_lib._fitter.fit_single_data_set_1d(exp_data) + self._running = False + self._finished = True diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/layers.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/layers.py index fa287c26..18e0231c 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/logic/layers.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/layers.py @@ -8,33 +8,22 @@ class Layers: def __init__(self, project_lib: ProjectLib): self._project_lib = project_lib - self._model_index = 0 - self._assembly_index = 0 - self._layer_index = 0 - self._layers: LayerCollection = self._project_lib._models[self._model_index].sample[self._assembly_index].layers - - def set_model_index(self, new_value: int) -> None: - self._model_index = new_value - self._assembly_index = 0 - self._layer_index = 0 - self._layers = self._project_lib._models[self._model_index].sample[self._assembly_index].layers - - def set_assembly_index(self, new_value: int) -> None: - self._assembly_index = new_value - self._layer_index = 0 - self._layers = self._project_lib._models[self._model_index].sample[self._assembly_index].layers + + @property + def _layers(self) -> LayerCollection: + return self._project_lib._models[self._project_lib.current_model_index].sample[self._project_lib.current_assembly_index].layers @property def index(self) -> int: - return self._layer_index + return self._project_lib.current_layer_index @index.setter def index(self, new_value: Union[int, str]) -> None: - self._layer_index = int(new_value) + self._project_lib.current_layer_index = int(new_value) @property def name_at_current_index(self) -> str: - return self._layers[self._layer_index].name + return self._layers[self.index].name @property def layers(self) -> list[dict[str, str]]: @@ -55,42 +44,65 @@ def add_new(self) -> None: self._layers[-1].material = self._project_lib._materials[index_si] def duplicate_selected(self) -> None: - self._layers.duplicate_layer(self._layer_index) + self._layers.duplicate_layer(self.index) def move_selected_up(self) -> None: - if self._layer_index > 0: - self._layers.move_up(self._layer_index) - self._layer_index = self._layer_index - 1 + if self.index > 0: + self._layers.move_up(self.index) + self.index = self.index - 1 def move_selected_down(self) -> None: - if self._layer_index < len(self._layers) - 1: - self._layers.move_down(self._layer_index) - self._layer_index = self._layer_index + 1 - - def set_name_at_current_index(self, new_value: str) -> None: - self._layers[self._layer_index].name = new_value - - def set_thickness_at_current_index(self, new_value: float) -> None: - self._layers[self._layer_index].thickness.value = new_value - - def set_roughness_at_current_index(self, new_value: float) -> None: - self._layers[self._layer_index].roughness.value = new_value - - def set_material_at_current_index(self, new_value: int) -> None: - self._layers[self._layer_index].material = self._project_lib._materials[new_value] - - def set_solvent_at_current_index(self, new_value: int) -> None: - self._layers[self._layer_index].solvent = self._project_lib._materials[new_value] - - def set_apm_at_current_index(self, new_value: float) -> None: - self._layers[self._layer_index].area_per_molecule = new_value - - def set_solvation_at_current_index(self, new_value: float) -> None: - self._layers[self._layer_index].solvent_fraction = new_value - - def set_formula(self, new_value: str) -> None: - self._layers[self._layer_index].molecular_formula = new_value - + if self.index < len(self._layers) - 1: + self._layers.move_down(self.index) + self.index = self.index + 1 + + def set_name_at_current_index(self, new_value: str) -> bool: + if self._layers[self.index].name != new_value: + self._layers[self.index].name = new_value + return True + return False + + def set_thickness_at_current_index(self, new_value: float) -> bool: + if self._layers[self.index].thickness.value != new_value: + self._layers[self.index].thickness.value = new_value + return True + return False + + def set_roughness_at_current_index(self, new_value: float) -> bool: + if self._layers[self.index].roughness.value != new_value: + self._layers[self.index].roughness.value = new_value + return True + return False + + def set_material_at_current_index(self, new_value: int) -> bool: + if self._layers[self.index].material != self._project_lib._materials[new_value]: + self._layers[self.index].material = self._project_lib._materials[new_value] + return True + return False + + def set_solvent_at_current_index(self, new_value: int) -> bool: + if self._layers[self.index].solvent != self._project_lib._materials[new_value]: + self._layers[self.index].solvent = self._project_lib._materials[new_value] + return True + return False + + def set_apm_at_current_index(self, new_value: float) -> bool: + if self._layers[self.index].area_per_molecule != new_value: + self._layers[self.index].area_per_molecule = new_value + return True + return False + + def set_solvation_at_current_index(self, new_value: float) -> bool: + if self._layers[self.index].solvent_fraction != new_value: + self._layers[self.index].solvent_fraction = new_value + return True + return False + + def set_formula(self, new_value: str) -> bool: + if self._layers[self.index].molecular_formula != new_value: + self._layers[self.index].molecular_formula = new_value + return True + return False def _from_layers_collection_to_list_of_dicts(layers_collection: LayerCollection) -> list[dict[str, str]]: layers_list = [] diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/material.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/material.py index 81b126d7..81cae509 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/logic/material.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/material.py @@ -7,20 +7,22 @@ class Material: def __init__(self, project_lib: ProjectLib): self._project_lib = project_lib - self._material_index = 0 - self._materials = self._project_lib._materials + + @property + def _materials(self) -> MaterialCollection: + return self._project_lib._materials @property def index(self) -> int: - return self._material_index + return self._project_lib.current_material_index @index.setter def index(self, new_value: Union[int, str]) -> None: - self._material_index = int(new_value) + self._project_lib.current_material_index = int(new_value) @property def name_at_current_index(self) -> str: - return self._materials[self._material_index].name + return self._materials[self.index].name @property def materials(self) -> list[dict[str, str]]: @@ -37,27 +39,36 @@ def add_new(self) -> None: self._materials.add_material() def duplicate_selected(self) -> None: - self._materials.duplicate_material(self._material_index) + self._materials.duplicate_material(self.index) def move_selected_up(self) -> None: - if self._material_index > 0: - self._materials.move_up(self._material_index) - self._material_index = self._material_index - 1 + if self.index > 0: + self._materials.move_up(self.index) + self.index = self.index - 1 def move_selected_down(self) -> None: - if self._material_index < len(self._materials) - 1: - self._materials.move_down(self._material_index) - self._material_index = self._material_index + 1 + if self.index < len(self._materials) - 1: + self._materials.move_down(self.index) + self.index = self.index + 1 - def set_name_at_current_index(self, new_value: str) -> None: - self._materials[self._material_index].name = new_value + def set_name_at_current_index(self, new_value: str) -> bool: + if self._materials[self.index].name != new_value: + self._materials[self.index].name = new_value + return True + return False - def set_sld_at_current_index(self, new_value: float) -> None: - self._materials[self._material_index].sld.value = new_value - - def set_isld_at_current_index(self, new_value: float) -> None: - self._materials[self._material_index].isld.value = new_value + def set_sld_at_current_index(self, new_value: float) -> bool: + if self._materials[self.index].sld.value != new_value: + self._materials[self.index].sld.value = new_value + return True + return False + def set_isld_at_current_index(self, new_value: float) -> bool: + if self._materials[self.index].isld.value != new_value: + self._materials[self.index].isld.value = new_value + return True + return False + def _from_materials_collection_to_list_of_dicts(materials_collection: MaterialCollection) -> list[dict[str, str]]: materials_list = [] for material in materials_collection: diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/minimizers.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/minimizers.py new file mode 100644 index 00000000..25ce0d6b --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/minimizers.py @@ -0,0 +1,57 @@ +from easyscience import AvailableMinimizers +from easyreflectometry import Project as ProjectLib + + +class Minimizers: + def __init__(self, project_lib: ProjectLib): + self._project_lib = project_lib + self._minimizer_current_index = 0 + self._list_available_minimizers = list(AvailableMinimizers) + try: + self._list_available_minimizers.remove(AvailableMinimizers.LMFit) + except ValueError: + pass + try: + self._list_available_minimizers.remove(AvailableMinimizers.Bumps) + except ValueError: + pass + try: + self._list_available_minimizers.remove(AvailableMinimizers.DFO) + except ValueError: + pass + + def minimizers_available(self) -> list[str]: + return [minimizer.name for minimizer in self._list_available_minimizers] + + def minimizer_current_index(self) -> int: + return self._minimizer_current_index + + def set_minimizer_current_index(self, new_value: int) -> None: + if new_value != self._minimizer_current_index: + self._minimizer_current_index = new_value + enum_new_minimizer = self._list_available_minimizers[new_value] + self._project_lib._fitter.switch_minimizer(enum_new_minimizer) + return True + return False + + @property + def tolerance(self) -> float: + return self._project_lib._fitter.easy_science_multi_fitter.tolerance + + @property + def max_iterations(self) -> int: + return self._project_lib._fitter.easy_science_multi_fitter.max_evaluations + + def set_tolerance(self, new_value: float) -> bool: + if new_value != self._project_lib._fitter.easy_science_multi_fitter.tolerance: + self._project_lib._fitter.easy_science_multi_fitter.tolerance = new_value + print(new_value) + return True + return False + + def set_max_iterations(self, new_value: float) -> bool: + if new_value != self._project_lib._fitter.easy_science_multi_fitter.max_evaluations: + self._project_lib._fitter.easy_science_multi_fitter.max_evaluations = new_value + print(new_value) + return True + return False \ No newline at end of file diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/models.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/models.py index 5b20303e..4eb22c3f 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/logic/models.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/models.py @@ -8,33 +8,32 @@ class Models: def __init__(self, project_lib: ProjectLib): self._project_lib = project_lib - self._model_index = 0 self._models = project_lib._models @property def index(self) -> int: - return self._model_index + return self._project_lib.current_model_index @index.setter def index(self, new_value: Union[int, str]) -> None: - self._model_index = int(new_value) + self._project_lib.current_model_index = int(new_value) @property def name_at_current_index(self) -> str: - return self._models[self._model_index].name + return self._models[self.index].name @property def scaling_at_current_index(self) -> float: - return self._models[self._model_index].scale.value + return self._models[self.index].scale.value @property def background_at_current_index(self) -> float: - return self._models[self._model_index].background.value + return self._models[self.index].background.value @property def resolution_at_current_index(self) -> str: - if isinstance(self._models[self._model_index].resolution_function, PercentageFhwm): - return str(self._models[self._model_index].resolution_function.constant) + if isinstance(self._models[self.index].resolution_function, PercentageFhwm): + return str(self._models[self.index].resolution_function.constant) else: return '-' @@ -46,37 +45,68 @@ def models(self) -> list[dict[str, str]]: def models_names(self) -> list[str]: return [element['label'] for element in self.models] - def set_name_at_current_index(self, new_value: str) -> None: - self._models[self._model_index].name = new_value - - def set_scaling_at_current_index(self, new_value: str) -> None: - self._models[self._model_index].scale.value = new_value - - def set_background_at_current_index(self, new_value: str) -> None: - self._models[self._model_index].background.value = new_value - - def set_resolution_at_current_index(self, new_value: str) -> None: - if isinstance(self._models[self._model_index].resolution_function, PercentageFhwm): - self._models[self._model_index].resolution_function.constant = float(new_value) + def set_name_at_current_index(self, new_value: str) -> bool: + if self._models[self.index].name != new_value: + self._models[self.index].name = new_value + return True + return False + + def set_scaling_at_current_index(self, new_value: str) -> bool: + if self._models[self.index].scale.value != float(new_value): + self._models[self.index].scale.value = float(new_value) + return True + return False + + def set_background_at_current_index(self, new_value: str) -> bool: + if self._models[self.index].background.value != float(new_value): + self._models[self.index].background.value = float(new_value) + return True + return False + + def set_resolution_at_current_index(self, new_value: str) -> bool: + if isinstance(self._models[self.index].resolution_function, PercentageFhwm): + if self._models[self.index].resolution_function.constant != float(new_value): + self._models[self.index].resolution_function.constant = float(new_value) + return True + return False def remove_at_index(self, value: str) -> None: self._models.pop(int(value)) def add_new(self) -> None: self._models.add_model() + self._models[-1].sample.add_assembly() + self._models[-1].sample._enable_changes_to_outermost_layers() + + self._models[-1].sample.data[0].layers.data[0].material = self._project_lib._materials[self._project_lib.get_index_air()] + self._models[-1].sample.data[0].layers.data[0].thickness = 0.0 + self._models[-1].sample.data[0].layers.data[0].roughness = 0.0 + self._models[-1].sample.data[0].name = 'Superphase' + + self._models[-1].sample.data[1].layers.data[0].material = self._project_lib._materials[self._project_lib.get_index_sio2()] + self._models[-1].sample.data[1].layers.data[0].thickness = 20.0 + self._models[-1].sample.data[1].layers.data[0].roughness = 3.0 + self._models[-1].sample.data[1].name = 'SiO2' + + self._models[-1].sample.data[2].layers.data[0].material = self._project_lib._materials[self._project_lib.get_index_si()] + self._models[-1].sample.data[2].name = 'Substrate' + self._models[-1].sample.data[2].layers.data[0].thickness = 0.0 + self._models[-1].sample.data[2].layers.data[0].roughness = 1.2 + + self._models[-1].sample._disable_changes_to_outermost_layers() def duplicate_selected_model(self) -> None: - self._models.duplicate_model(self._model_index) + self._models.duplicate_model(self.index) def move_selected_up(self) -> None: - if self._model_index > 0: - self._models.move_up(self._model_index) - self._model_index = self._model_index - 1 + if self.index > 0: + self._models.move_up(self.index) + self.index = self.index - 1 def move_selected_down(self) -> None: - if self._model_index < len(self._models) - 1: - self._models.move_down(self._model_index) - self._model_index = self._model_index + 1 + if self.index < len(self._models) - 1: + self._models.move_down(self.index) + self.index = self.index + 1 def _from_models_collection_to_list_of_dicts(models_collection: ModelCollection) -> list[dict[str, str]]: diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/parameters.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/parameters.py new file mode 100644 index 00000000..8801f240 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/parameters.py @@ -0,0 +1,142 @@ +from easyscience.Objects.new_variable import Parameter +from easyscience import global_object +from easyscience.Constraints import ObjConstraint +from easyscience.Constraints import NumericConstraint + +from easyreflectometry import Project as ProjectLib +from typing import List + + +class Parameters: + def __init__(self, project_lib: ProjectLib): + self._project_lib = project_lib + self._current_index = 0 + + @property + def as_status_string(self) -> str: + return f"{self.count_free_parameters() + self.count_fixed_parameters()} ({self.count_free_parameters()} free, {self.count_fixed_parameters()} fixed)" + + @property + def parameters(self) -> List[str]: + return _from_parameters_to_list_of_dicts(self._project_lib.parameters, self._project_lib._models[self._project_lib.current_model_index].unique_name) + + def current_index(self) -> int: + return self._current_index + + def set_current_index(self, new_value: int) -> None: + if new_value != self._current_index: + self._current_index = new_value + return True + return False + + def count_free_parameters(self) -> int: + count = 0 + parameters = self._project_lib.parameters + for parameter in parameters: + if parameter.free: + count = count + 1 + return count + + def count_fixed_parameters(self) -> int: + count = 0 + parameters = self._project_lib.parameters + for parameter in parameters: + if not parameter.free: + count = count + 1 + return count + + def set_current_parameter_value(self, new_value: str) -> bool: + parameters = self._project_lib.parameters + if float(new_value) != parameters[self._current_index].value: + try: + parameters[self._current_index].value = float(new_value) + except ValueError: + pass + return True + return False + + def set_current_parameter_min(self, new_value: str) -> bool: + parameters = self._project_lib.parameters + if float(new_value) != parameters[self._current_index].min: + try: + parameters[self._current_index].min = float(new_value) + except ValueError: + pass + return True + return False + + def set_current_parameter_max(self, new_value: str) -> bool: + parameters = self._project_lib.parameters + if float(new_value) != parameters[self._current_index].max: + try: + parameters[self._current_index].max = float(new_value) + except ValueError: + pass + return True + return False + + def set_current_parameter_fit(self, new_value: str) -> bool: + parameters = self._project_lib.parameters + if bool(new_value) != parameters[self._current_index].free: + parameters[self._current_index].free = bool(new_value) + return True + return False + + ### Constraints + def constraint_relations(self) -> List[str]: + return [ '=', '<', '>' ] + + def constraint_arithmetic(self) -> List[str]: + return [ '', '*', '/', '+', '-'] + + def add_constraint( + self, + dependent_idx: int, + relational_operator: str, + value: float, + arithmetic_operator: str, + independent_idx: int + ) -> None: + + independent = self._project_lib.parameters[independent_idx] + dependent = self._project_lib.parameters[dependent_idx] + + if arithmetic_operator != "" and independent_idx > -1: + constaint = ObjConstraint( + dependent_obj=dependent, + operator=str(float(value)) + arithmetic_operator, + independent_obj=independent + ) + elif arithmetic_operator == "" and independent_idx == -1: + constaint = NumericConstraint( + dependent_obj=dependent, + operator=relational_operator.replace("=", "=="), + value=float(value) + ) + else: + print("Failed to add constraint: Unsupported type") + return + # print(c) + independent.user_constraints[dependent.name] = constaint + constaint() + + print(f"{dependent_idx}, {relational_operator}, {value}, {arithmetic_operator}, {independent_idx}") + +def _from_parameters_to_list_of_dicts(parameters: List[Parameter], model_unique_name: str) -> list[dict[str, str]]: + parameter_list = [] + for parameter in parameters: + path = global_object.map.find_path(model_unique_name, parameter.unique_name) + if 0 < len(path): + name = f"{global_object.map.get_item_by_key(path[-2]).name} {global_object.map.get_item_by_key(path[-1]).name}" + parameter_list.append( + { + 'name': name, + 'value': float(parameter.value), + 'error': float(parameter.variance), + 'max': float(parameter.max), + 'min': float(parameter.min), + 'units': parameter.unit, + 'fit': parameter.free + } + ) + return parameter_list \ No newline at end of file diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/project.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/project.py index fb34c7e4..ed096cf9 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/logic/project.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/project.py @@ -48,27 +48,36 @@ def q_min(self) -> float: return self._project_lib.q_min def set_q_min(self, new_value: str) -> None: - self._project_lib.q_min = float(new_value) + if float(new_value) != self._project_lib.q_min: + self._project_lib.q_min = float(new_value) + return True + return False @property def q_max(self) -> float: return self._project_lib.q_max def set_q_max(self, new_value: str) -> None: - self._project_lib.q_max = float(new_value) - + if float(new_value) != self._project_lib.q_max: + self._project_lib.q_max = float(new_value) + return True + return False + @property def q_resolution(self) -> int: return self._project_lib.q_resolution def set_q_resolution(self, new_value: str) -> None: - self._project_lib.q_resolution = int(new_value) + if float(new_value) != self._project_lib.q_resolution: + self._project_lib.q_resolution = int(new_value) + return True + return False @property def experimental_data_at_current_index(self) -> bool: experimental_data = False try: - self._project_lib.experimental_data_for_model_at_index() + self._project_lib.experimental_data_for_model_at_index(self._project_lib._current_model_index) experimental_data = True except IndexError: pass @@ -84,13 +93,13 @@ def create(self) -> None: self._project_lib.save_as_json() def save(self) -> None: - self._project_lib.save_as_json() + self._project_lib.save_as_json(overwrite=True) def load(self, path: str) -> None: self._project_lib.load_from_json(path) def load_experiment(self, path: str) -> None: - self._project_lib.load_experiment_for_model_at_index(path) + self._project_lib.load_experiment_for_model_at_index(path, self._project_lib._current_model_index) def reset(self) -> None: self._project_lib.reset() diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/logic/status.py b/src_qt6/EasyReflectometryApp/Backends/Py/logic/status.py index d061eefa..5075d0d4 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/logic/status.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/logic/status.py @@ -3,7 +3,6 @@ class Status: def __init__(self, project_lib: ProjectLib): self._project_lib = project_lib - self._variables = '31 (3 free, 28 fixed)' @property def project(self): @@ -11,16 +10,13 @@ def project(self): @property def minimizer(self): - return self._project_lib._minimizer.name + return self._project_lib.minimizer.name @property def calculator(self): - return self._project_lib._calculator().name + return self._project_lib.calculator @property def experiments_count(self): return str(len(self._project_lib._experiments.keys())) - @property - def variables(self): - return self._variables diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/plotting_1d.py b/src_qt6/EasyReflectometryApp/Backends/Py/plotting_1d.py index 2c232c51..afce62e5 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/plotting_1d.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/plotting_1d.py @@ -14,8 +14,6 @@ PLOT_BACKEND = 'QtCharts' class Plotting1d(QObject): - currentLib1dChanged = Signal() - useAcceleration1dChanged = Signal() chartRefsChanged = Signal() sldChartRangesChanged = Signal() sampleChartRangesChanged = Signal() @@ -26,8 +24,6 @@ def __init__(self, project_lib: ProjectLib, parent=None): self._project_lib = project_lib self._proxy = parent self._currentLib1d = 'QtCharts' - self._useAcceleration1d = True - self._model_index = 0 self._chartRefs = { 'QtCharts': { @@ -40,13 +36,17 @@ def __init__(self, project_lib: ProjectLib, parent=None): 'errorUpperSerie': None, 'errorLowerSerie': None, }, + 'analysisPage': { + 'calculatedSerie': None, + 'measuredSerie': None, + }, } } @property def sample_data(self) -> DataSet1D: try: - data = self._project_lib.sample_data_for_model_at_index() + data = self._project_lib.sample_data_for_model_at_index(self._project_lib.current_model_index) except IndexError: data = DataSet1D( name='Sample Data empty', @@ -58,7 +58,7 @@ def sample_data(self) -> DataSet1D: @property def sld_data(self) -> DataSet1D: try: - data = self._project_lib.sld_data_for_model_at_index() + data = self._project_lib.sld_data_for_model_at_index(self._project_lib.current_model_index) except IndexError: data = DataSet1D( name='SLD Data empty', @@ -70,14 +70,14 @@ def sld_data(self) -> DataSet1D: @property def experiment_data(self) -> DataSet1D: try: - data = self._project_lib.experimental_data_for_model_at_index() + data = self._project_lib.experimental_data_for_model_at_index(self._project_lib.current_model_index) except IndexError: data = DataSet1D( name='Experiment Data empty', x=np.empty(0), y=np.empty(0), - ey=np.empty(0), - ex=np.empty(0), + ye=np.empty(0), + xe=np.empty(0), ) return data @@ -114,54 +114,11 @@ def sldMaxY(self): @Property(float, notify=sldChartRangesChanged) def sldMinY(self): return self.sld_data.y.min() - - # Experiment - @Property(float, notify=experimentChartRangesChanged) - def experimentMaxX(self): - return self.experiment_data.x.max() - - @Property(float, notify=experimentChartRangesChanged) - def experimentMinX(self): - return self.experiment_data.x.min() - - @Property(float, notify=experimentChartRangesChanged) - def experimentMaxY(self): - return np.log10(self.experiment_data.y.max()) - - @Property(float, notify=experimentChartRangesChanged) - def experimentMinY(self): - return np.log10(self.experiment_data.y.min()) - - @Property(str, notify=currentLib1dChanged) - def currentLib1d(self): - return self._currentLib1d - - @currentLib1d.setter - def currentLib1d(self, newValue): - if self._currentLib1d == newValue: - return - self._currentLib1d = newValue - self.currentLib1dChanged.emit() - - @Property(bool, notify=useAcceleration1dChanged) - def useAcceleration1d(self): - return self._useAcceleration1d - - @useAcceleration1d.setter - def useAcceleration1d(self, newValue): - if self._useAcceleration1d == newValue: - return - self._useAcceleration1d = newValue - self.useAcceleration1dChanged.emit() - + @Property('QVariant', notify=chartRefsChanged) def chartRefs(self): return self._chartRefs - @Slot(int) - def setModelIndex(self, value: int) -> None: - self._model_index = value - @Slot(str, str, 'QVariant') def setQtChartsSerieRef(self, page:str, serie:str, ref: QObject): self._chartRefs['QtCharts'][page][serie] = ref @@ -174,18 +131,14 @@ def refreshSamplePage(self): def refreshExperimentPage(self): self.drawMeasuredOnExperimentChart() + def refreshAnalysisPage(self): + self.drawCalculatedAndMeasuredOnAnalysisChart() + + @Slot() def drawCalculatedOnSampleChart(self): if PLOT_BACKEND == 'QtCharts': self.qtchartsReplaceCalculatedOnSampleChartAndRedraw() - def drawCalculatedOnSldChart(self): - if PLOT_BACKEND == 'QtCharts': - self.qtchartsReplaceCalculatedOnSldChartAndRedraw() - - def drawMeasuredOnExperimentChart(self): - if PLOT_BACKEND == 'QtCharts': - self.qtchartsReplaceMeasuredOnExperimentChartAndRedraw() - def qtchartsReplaceCalculatedOnSampleChartAndRedraw(self): series = self._chartRefs['QtCharts']['samplePage']['sampleSerie'] series.clear() @@ -195,6 +148,11 @@ def qtchartsReplaceCalculatedOnSampleChartAndRedraw(self): nr_points = nr_points + 1 console.debug(IO.formatMsg('sub', 'Calc curve', f'{nr_points} points', 'on sample page', 'replaced')) + @Slot() + def drawCalculatedOnSldChart(self): + if PLOT_BACKEND == 'QtCharts': + self.qtchartsReplaceCalculatedOnSldChartAndRedraw() + def qtchartsReplaceCalculatedOnSldChartAndRedraw(self): series = self._chartRefs['QtCharts']['samplePage']['sldSerie'] series.clear() @@ -204,6 +162,10 @@ def qtchartsReplaceCalculatedOnSldChartAndRedraw(self): nr_points = nr_points + 1 console.debug(IO.formatMsg('sub', 'Sld curve', f'{nr_points} points', 'on sample page', 'replaced')) + def drawMeasuredOnExperimentChart(self): + if PLOT_BACKEND == 'QtCharts': + self.qtchartsReplaceMeasuredOnExperimentChartAndRedraw() + def qtchartsReplaceMeasuredOnExperimentChartAndRedraw(self): series_measured = self._chartRefs['QtCharts']['experimentPage']['measuredSerie'] series_measured.clear() @@ -219,4 +181,25 @@ def qtchartsReplaceMeasuredOnExperimentChartAndRedraw(self): series_error_lower.append(point[0], np.log10(point[1] - np.sqrt(point[2]))) nr_points = nr_points + 1 - console.debug(IO.formatMsg('sub', 'Measurede curve', f'{nr_points} points', 'on experiment page', 'replaced')) \ No newline at end of file + console.debug(IO.formatMsg('sub', 'Measurede curve', f'{nr_points} points', 'on experiment page', 'replaced')) + + def drawCalculatedAndMeasuredOnAnalysisChart(self): + if PLOT_BACKEND == 'QtCharts': + self.qtchartsReplaceCalculatedAndMeasuredOnAnalysisChartAndRedraw() + + def qtchartsReplaceCalculatedAndMeasuredOnAnalysisChartAndRedraw(self): + series_measured = self._chartRefs['QtCharts']['analysisPage']['measuredSerie'] + series_measured.clear() + series_calculated = self._chartRefs['QtCharts']['analysisPage']['calculatedSerie'] + series_calculated.clear() + nr_points = 0 + for point in self.experiment_data.data_points(): + if point[0] < self._project_lib.q_max and self._project_lib.q_min < point[0]: + series_measured.append(point[0], np.log10(point[1])) + nr_points = nr_points + 1 + console.debug(IO.formatMsg('sub', 'Measurede curve', f'{nr_points} points', 'on analysis page', 'replaced')) + + for point in self.sample_data.data_points(): + series_calculated.append(point[0], np.log10(point[1])) + nr_points = nr_points + 1 + console.debug(IO.formatMsg('sub', 'Calculated curve', f'{nr_points} points', 'on analysis page', 'replaced')) diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/project.py b/src_qt6/EasyReflectometryApp/Backends/Py/project.py index 05ba07eb..71e1e11e 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/project.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/project.py @@ -15,6 +15,10 @@ class Project(QObject): descriptionChanged = Signal() locationChanged = Signal() + externalCreatedChanged = Signal() + externalNameChanged = Signal() + externalProjectLoaded = Signal() + def __init__(self, project_lib: ProjectLib, parent=None): super().__init__(parent) self._logic = ProjectLogic(project_lib) @@ -44,6 +48,7 @@ def setName(self, new_value: str) -> None: if self._logic.name != new_value: self._logic.name = new_value self.nameChanged.emit() + self.externalNameChanged.emit() @Property(str, notify=descriptionChanged) def description(self) -> str: @@ -71,6 +76,7 @@ def setLocation(self, new_value: str) -> None: def create(self) -> None: self._logic.create() self.createdChanged.emit() + self.externalCreatedChanged.emit() @Slot(str) def load(self, path: str) -> None: @@ -79,6 +85,7 @@ def load(self, path: str) -> None: self.nameChanged.emit() self.descriptionChanged.emit() self.locationChanged.emit() + self.externalProjectLoaded.emit() @Slot() def save(self) -> None: @@ -91,3 +98,5 @@ def reset(self) -> None: self.nameChanged.emit() self.descriptionChanged.emit() self.locationChanged.emit() + self.externalCreatedChanged.emit() + self.externalNameChanged.emit() diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/py_backend.py b/src_qt6/EasyReflectometryApp/Backends/Py/py_backend.py index c2724b4d..714d7ad9 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/py_backend.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/py_backend.py @@ -80,37 +80,65 @@ def logger(self): def _connect_backend_parts(self) -> None: self._connect_project_page() self._connect_sample_page() - self._connect_example_page() + self._connect_experiment_page() + self._connect_analysis_page() - ######### Project + ######### Forming connections between the backend parts def _connect_project_page(self) -> None: - self._project.nameChanged.connect(self._relay_project_page_name) - self._project.createdChanged.connect(self._relay_project_page_created) + self._project.externalNameChanged.connect(self._relay_project_page_name) + self._project.externalCreatedChanged.connect(self._relay_project_page_created) + self._project.externalProjectLoaded.connect(self._relay_project_page_project_loaded) def _connect_sample_page(self) -> None: - self._sample.modelsIndexChanged.connect(self._relay_sample_page_models_index) - self._sample.sampleChanged.connect(self._relay_sample_page_sample_changed) + self._sample.externalSampleChanged.connect(self._relay_sample_page_sample_changed) + self._sample.externalRefreshPlot.connect(self._refresh_plots) - def _connect_example_page(self) -> None: - self._experiment.experimentChanged.connect(self._relay_experiment_page_experiment_changed) + def _connect_experiment_page(self) -> None: + self._experiment.externalExperimentChanged.connect(self._relay_experiment_page_experiment_changed) + self._experiment.externalExperimentChanged.connect(self._refresh_plots) + + def _connect_analysis_page(self) -> None: + self._analysis.externalMinimizerChanged.connect(self._relay_analysis_page) + self._analysis.externalCalculatorChanged.connect(self._relay_analysis_page) + self._analysis.externalParametersChanged.connect(self._relay_analysis_page) + self._analysis.externalParametersChanged.connect(self._refresh_plots) + self._analysis.externalFittingChanged.connect(self._refresh_plots) def _relay_project_page_name(self): - self._status.projectChanged.emit() + self._status.statusChanged.emit() self._report.asHtmlChanged.emit() def _relay_project_page_created(self): self._report.createdChanged.emit() - def _relay_sample_page_models_index(self, index: int): - self._plotting.setModelIndex(index) - self._experiment.setModelIndex(index) + def _relay_project_page_project_loaded(self): + self._sample.materialsTableChanged.emit() + self._sample.modelsTableChanged.emit() + self._sample.assembliesTableChanged.emit() + self._sample._clearCacheAndEmitLayersChanged() + self._experiment.experimentChanged.emit() + self._analysis.experimentsChanged.emit() + self._analysis._clearCacheAndEmitParametersChanged() + self._status.statusChanged.emit() + self._refresh_plots() def _relay_sample_page_sample_changed(self): - self._plotting.sldChartRangesChanged.emit() - self._plotting.sampleChartRangesChanged.emit() - self._plotting.refreshSamplePage() + self._analysis._clearCacheAndEmitParametersChanged() + self._status.statusChanged.emit() def _relay_experiment_page_experiment_changed(self): + self._analysis.experimentsChanged.emit() + self._analysis._clearCacheAndEmitParametersChanged() + self._status.statusChanged.emit() + + def _relay_analysis_page(self): + self._status.statusChanged.emit() + self._experiment.experimentChanged.emit() + + def _refresh_plots(self): + self._plotting.sampleChartRangesChanged.emit() + self._plotting.sldChartRangesChanged.emit() + self._plotting.experimentChartRangesChanged.emit() + self._plotting.refreshSamplePage() self._plotting.refreshExperimentPage() - self._status.experimentsCountChanged.emit() - self._sample.sampleChanged.emit() + self._plotting.refreshAnalysisPage() diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/sample.py b/src_qt6/EasyReflectometryApp/Backends/Py/sample.py index 7709d9f8..a1e108d9 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/sample.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/sample.py @@ -4,76 +4,64 @@ from PySide6.QtCore import Property from easyreflectometry import Project as ProjectLib -from .logic.material import Material -from .logic.models import Models -from .logic.assemblies import Assemblies -from .logic.layers import Layers +from .logic.material import Material as MaterialLogic +from .logic.assemblies import Assemblies as AssembliesLogic +from .logic.layers import Layers as LayersLogic +from .logic.models import Models as ModelsLogic +from .logic.project import Project as ProjectLogic +from .logic.parameters import Parameters as ParametersLogic class Sample(QObject): - sampleChanged = Signal() + materialsTableChanged = Signal() + materialsIndexChanged = Signal() - materialsChanged = Signal() - materialIndexChanged = Signal(int) - - modelsChange = Signal() modelsTableChanged = Signal() - modelsIndexChanged = Signal(int) + modelsIndexChanged = Signal() - assembliesChange = Signal() assembliesTableChanged = Signal() - assembliesIndexChanged = Signal(int) + assembliesIndexChanged = Signal() layersChange = Signal() - layersTableChanged = Signal() + layersIndexChanged = Signal() + + qRangeChanged = Signal() + + externalRefreshPlot = Signal() + externalSampleChanged = Signal() def __init__(self, project_lib: ProjectLib, parent=None): super().__init__(parent) - self._material_logic = Material(project_lib) - self._models_logic = Models(project_lib) - self._assemblies_logic = Assemblies(project_lib) - self._layers_logic = Layers(project_lib) + self._project_lib = project_lib + self._material_logic = MaterialLogic(project_lib) + self._models_logic = ModelsLogic(project_lib) + self._assemblies_logic = AssembliesLogic(project_lib) + self._layers_logic = LayersLogic(project_lib) + self._project_logic = ProjectLogic(project_lib) + self._parameters_logic = ParametersLogic(project_lib) + + self._chached_layers = None self.connect_logic() - + def connect_logic(self) -> None: - self.modelsIndexChanged.connect(self._assemblies_logic.set_model_index) - self.modelsIndexChanged.connect(self._layers_logic.set_model_index) - self.assembliesIndexChanged.connect(self._layers_logic.set_assembly_index) - - self.modelsChange.connect(self.layersConnectChanges) - self.assembliesChange.connect(self.layersConnectChanges) - self.layersTableChanged.connect(self.layersConnectChanges) - - self.modelsChange.connect(self.assembliesConnectChanges) - self.assembliesIndexChanged.connect(self.assembliesConnectChanges) - self.assembliesTableChanged.connect(self.assembliesConnectChanges) - - self.modelsIndexChanged.connect(self.modelsConnectChanges) - self.modelsTableChanged.connect(self.modelsConnectChanges) - - self.materialsChanged.connect(self.sampleConnectChanges) - self.modelsChange.connect(self.sampleConnectChanges) - self.assembliesChange.connect(self.sampleConnectChanges) - self.layersChange.connect(self.sampleConnectChanges) - - # # # - # Sample - # # # - def sampleConnectChanges(self) -> None: - self.sampleChanged.emit() + self.assembliesIndexChanged.connect(self.layersConnectChanges) # # # # Materials # # # - @Property('QVariantList', notify=materialsChanged) + @Property('QVariantList', notify=materialsTableChanged) def materials(self) -> list[dict[str, str]]: return self._material_logic.materials - - @Property('QVariantList', notify=materialsChanged) + + @Property(int, notify=materialsIndexChanged) + def currentMaterialIndex(self) -> int: + return self._material_logic.index + + @Property('QVariantList', notify=materialsTableChanged) def materialNames(self) -> list[str]: return self._material_logic.material_names - @Property(str, notify=materialIndexChanged) + @Property(str, notify=materialsIndexChanged) def currentMaterialName(self) -> str: return self._material_logic.name_at_current_index @@ -81,60 +69,68 @@ def currentMaterialName(self) -> str: @Slot(int) def setCurrentMaterialIndex(self, new_value: int) -> None: self._material_logic.index = new_value - self.materialIndexChanged.emit(new_value) + self.materialsIndexChanged.emit() @Slot(str) def setCurrentMaterialName(self, new_value: str) -> None: - self._material_logic.set_name_at_current_index(new_value) - self.materialsChanged.emit() + if self._material_logic.set_name_at_current_index(new_value): + self.materialsTableChanged.emit() @Slot(float) def setCurrentMaterialSld(self, new_value: float) -> None: - self._material_logic.set_sld_at_current_index(new_value) - self.materialsChanged.emit() + if self._material_logic.set_sld_at_current_index(new_value): + self.materialsTableChanged.emit() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot(float) def setCurrentMaterialISld(self, new_value: float) -> None: - self._material_logic.set_isld_at_current_index(new_value) - self.materialsChanged.emit() + if self._material_logic.set_isld_at_current_index(new_value): + self.materialsTableChanged.emit() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() # Actions @Slot(str) def removeMaterial(self, value: str) -> None: self._material_logic.remove_at_index(value) - self.materialsChanged.emit() + self.materialsTableChanged.emit() + self.externalSampleChanged.emit() @Slot() def addNewMaterial(self) -> None: self._material_logic.add_new() - self.materialsChanged.emit() + self.materialsTableChanged.emit() + self.externalSampleChanged.emit() @Slot() def duplicateSelectedMaterial(self) -> None: self._material_logic.duplicate_selected() - self.materialsChanged.emit() + self.materialsTableChanged.emit() + self.externalSampleChanged.emit() @Slot() def moveSelectedMaterialUp(self) -> None: self._material_logic.move_selected_up() - self.materialsChanged.emit() + self.materialsTableChanged.emit() @Slot() def moveSelectedMaterialDown(self) -> None: self._material_logic.move_selected_down() - self.materialsChanged.emit() + self.materialsTableChanged.emit() # # # # Models # # # - def modelsConnectChanges(self) -> None: - self.modelsChange.emit() - - @Property('QVariantList', notify=modelsChange) + @Property('QVariantList', notify=modelsTableChanged) def models(self) -> list[dict[str, str]]: return self._models_logic.models - @Property('QVariantList', notify=modelsChange) + @Property(int, notify=modelsIndexChanged) + def currentModelIndex(self) -> int: + return self._models_logic.index + + @Property('QVariantList', notify=modelsTableChanged) def modelslNames(self) -> list[str]: return self._models_logic.models_names @@ -145,13 +141,16 @@ def currentModelName(self) -> str: # Setters @Slot(int) def setCurrentModelIndex(self, new_value: int) -> None: - self._models_logic.index = new_value - self.modelsIndexChanged.emit(new_value) + self._project_lib.current_model_index = new_value + self.modelsIndexChanged.emit() + self.assembliesTableChanged.emit() + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() @Slot(str) def setCurrentModelName(self, value: str) -> None: - self._models_logic.set_name_at_current_index(value) - self.modelsTableChanged.emit() + if self._models_logic.set_name_at_current_index(value): + self.modelsTableChanged.emit() # Actions @Slot(str) @@ -163,6 +162,7 @@ def removeModel(self, value: str) -> None: def addNewModel(self) -> None: self._models_logic.add_new() self.modelsTableChanged.emit() + self.materialsTableChanged.emit() @Slot() def duplicateSelectedModel(self) -> None: @@ -182,96 +182,122 @@ def moveSelectedModelDown(self)-> None: # # # # Assemblies # # # - def assembliesConnectChanges(self) -> None: - self.assembliesChange.emit() - - @Property('QVariantList', notify=assembliesChange) + @Property('QVariantList', notify=assembliesTableChanged) def assemblies(self) -> list[dict[str, str]]: return self._assemblies_logic.assemblies - @Property('QVariantList', notify=assembliesChange) + @Property(int, notify=assembliesIndexChanged) + def currentAssemblyIndex(self) -> int: + return self._assemblies_logic.index + + @Property('QVariantList', notify=assembliesTableChanged) def assembliesNames(self) -> list[str]: return self._assemblies_logic.assemblies_names - @Property(str, notify=assembliesChange) + @Property(str, notify=assembliesTableChanged) def currentAssemblyName(self) -> str: return self._assemblies_logic.name_at_current_index - @Property(str, notify=assembliesChange) + @Property(str, notify=assembliesIndexChanged) def currentAssemblyType(self) -> str: return self._assemblies_logic.type_at_current_index # Setters @Slot(int) def setCurrentAssemblyIndex(self, new_value: int) -> None: - self._assemblies_logic.index = new_value - self.assembliesIndexChanged.emit(new_value) + self._project_lib.current_assembly_index = new_value + self._clearCacheAndEmitLayersChanged() + self.assembliesTableChanged.emit() + self.assembliesIndexChanged.emit() @Slot(str) def setCurrentAssemblyName(self, new_value: str) -> None: - self._assemblies_logic.set_name_at_current_index(new_value) - self.assembliesTableChanged.emit() + if self._assemblies_logic.set_name_at_current_index(new_value): + self.assembliesTableChanged.emit() @Slot(str) def setCurrentAssemblyType(self, new_value: str) -> None: self._assemblies_logic.set_type_at_current_index(new_value) + self._clearCacheAndEmitLayersChanged() self.assembliesTableChanged.emit() + self.assembliesIndexChanged.emit() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() # Assembly specific - @Property(str, notify=assembliesChange) + @Property(str, notify=assembliesTableChanged) def currentAssemblyRepeatedLayerReptitions(self) -> str: return self._assemblies_logic.repetitions_at_current_index @Slot(int) def setCurrentAssemblyRepeatedLayerReptitions(self, new_value: int) -> None: - self._assemblies_logic.set_repeated_layer_reptitions(new_value) - self.assembliesTableChanged.emit() + if self._assemblies_logic.set_repeated_layer_reptitions(new_value): + self.assembliesTableChanged.emit() + self.externalRefreshPlot.emit() @Slot(bool) def setCurrentAssemblyConstrainAPM(self, new_value: bool) -> None: - self._assemblies_logic.set_constrain_apm(new_value) - self.assembliesTableChanged.emit() + if self._assemblies_logic.set_constrain_apm(new_value): + self.assembliesTableChanged.emit() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot(bool) def setCurrentAssemblyConformalRoughness(self, new_value: bool) -> None: - self._assemblies_logic.set_conformal_roughness(new_value) - self.assembliesTableChanged.emit() + if self._assemblies_logic.set_conformal_roughness(new_value): + self.assembliesTableChanged.emit() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() # Actions @Slot(str) def removeAssembly(self, value: str) -> None: self._assemblies_logic.remove_at_index(value) self.assembliesTableChanged.emit() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot() def addNewAssembly(self) -> None: self._assemblies_logic.add_new() self.assembliesTableChanged.emit() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot() def duplicateSelectedAssembly(self) -> None: self._assemblies_logic.duplicate_selected() self.assembliesTableChanged.emit() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot() def moveSelectedAssemblyUp(self) -> None: self._assemblies_logic.move_selected_up() self.assembliesTableChanged.emit() + self.externalRefreshPlot.emit() @Slot() def moveSelectedAssemblyDown(self)-> None: self._assemblies_logic.move_selected_down() self.assembliesTableChanged.emit() + self.externalRefreshPlot.emit() # # # # Layers # # # def layersConnectChanges(self) -> None: - self.layersChange.emit() + self._clearCacheAndEmitLayersChanged() @Property('QVariantList', notify=layersChange) def layers(self) -> list[dict[str, str]]: - return self._layers_logic.layers + if self._chached_layers is None: + self._chached_layers = self._layers_logic.layers + return self._chached_layers + + @Property(int, notify=layersIndexChanged) + def currentLayerIndex(self) -> int: + return self._layers_logic.index @Property('QVariantList', notify=layersChange) def layersNames(self) -> list[str]: @@ -284,70 +310,164 @@ def currentLayerName(self) -> str: # Setters @Slot(int) def setCurrentLayerIndex(self, new_value: int) -> None: - self._layers_logic.index = new_value + self._project_lib.current_layer_index = new_value + self.layersIndexChanged.emit() @Slot(str) def setCurrentLayerName(self, new_value: str) -> None: - self._layers_logic.set_name_at_current_index(new_value) - self.layersTableChanged.emit() + if self._layers_logic.set_name_at_current_index(new_value): + self._clearCacheAndEmitLayersChanged() @Slot(int) def setCurrentLayerMaterial(self, new_value: int) -> None: - self._layers_logic.set_material_at_current_index(new_value) - self.layersTableChanged.emit() + if self._layers_logic.set_material_at_current_index(new_value): + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot(int) def setCurrentLayerSolvent(self, new_value: int) -> None: - self._layers_logic.set_solvent_at_current_index(new_value) - self.layersTableChanged.emit() + if self._layers_logic.set_solvent_at_current_index(new_value): + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot(float) def setCurrentLayerThickness(self, new_value: float) -> None: - self._layers_logic.set_thickness_at_current_index(new_value) - self.layersTableChanged.emit() + if self._layers_logic.set_thickness_at_current_index(new_value): + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot(float) def setCurrentLayerRoughness(self, new_value: float) -> None: - self._layers_logic.set_roughness_at_current_index(new_value) - self.layersTableChanged.emit() + if self._layers_logic.set_roughness_at_current_index(new_value): + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot(str) def setCurrentLayerFormula(self, new_value: str) -> None: - self._layers_logic.set_formula(new_value) - self.layersTableChanged.emit() + if self._layers_logic.set_formula(new_value): + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot(float) def setCurrentLayerAPM(self, new_value: float) -> None: - self._layers_logic.set_apm_at_current_index(new_value) - self.layersTableChanged.emit() + if self._layers_logic.set_apm_at_current_index(new_value): + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot(float) def setCurrentLayerSolvation(self, new_value: float) -> None: - self._layers_logic.set_solvation_at_current_index(new_value) - self.layersTableChanged.emit() + if self._layers_logic.set_solvation_at_current_index(new_value): + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() # Actions @Slot(str) def removeLayer(self, value: str) -> None: self._layers_logic.remove_at_index(value) - self.layersTableChanged.emit() + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot() def addNewLayer(self) -> None: self._layers_logic.add_new() - self.layersTableChanged.emit() + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot() def duplicateSelectedLayer(self) -> None: self._layers_logic.duplicate_selected() - self.layersTableChanged.emit() + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + self.externalSampleChanged.emit() @Slot() def moveSelectedLayerUp(self) -> None: self._layers_logic.move_selected_up() - self.layersTableChanged.emit() + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() @Slot() def moveSelectedLayerDown(self)-> None: self._layers_logic.move_selected_down() - self.layersTableChanged.emit() \ No newline at end of file + self._clearCacheAndEmitLayersChanged() + self.externalRefreshPlot.emit() + + def _clearCacheAndEmitLayersChanged(self): + self._chached_layers = None + self.layersChange.emit() + + # # # + # Constraints + # # # + @Property('QVariantList', notify=layersChange) + def parameterNames(self) -> list[dict[str, str]]: + return [parameter['name'] for parameter in self._parameters_logic.parameters] + + @Property('QVariantList', notify=layersChange) + def relationOperators(self) -> list[str]: + return self._parameters_logic.constraint_relations() + + @Property('QVariantList', notify=layersChange) + def arithmicOperators(self) -> list[str]: + return self._parameters_logic.constraint_arithmetic() + + @Slot(str, str, str, str, str) + def addConstraint(self, value1: str, value2: str, value3: str, value4: str, value5: str) -> None: + self._parameters_logic.add_constraint( + dependent_idx=int(value1), + relational_operator=value2, + value=float(value3), + arithmetic_operator=value4, + independent_idx=int(value5) + ) + + # # # + # Q Range + # # # + @Property(float, notify=qRangeChanged) + def q_min(self) -> float: + return self._project_logic.q_min + + @Property(float, notify=qRangeChanged) + def q_max(self) -> float: + return self._project_logic.q_max + + @Property(int, notify=qRangeChanged) + def q_resolution(self) -> int: + return self._project_logic.q_resolution + + @Property(bool, notify=qRangeChanged) + def experimentalData(self) -> bool: + return self._project_logic.experimental_data_at_current_index + + # Setters + @Slot(int) + def setModelIndex(self, value: int) -> None: + self._models_logic.index = value + + @Slot(float) + def setQMin(self, new_value: float) -> None: + if self._project_logic.set_q_min(new_value): + self.qRangeChanged.emit() + self.externalRefreshPlot.emit() + + @Slot(float) + def setQMax(self, new_value: float) -> None: + if self._project_logic.set_q_max(new_value): + self.qRangeChanged.emit() + self.externalRefreshPlot.emit() + + @Slot(int) + def setQElements(self, new_value: float) -> None: + if self._project_logic.set_q_resolution(new_value): + self.qRangeChanged.emit() + self.externalRefreshPlot.emit() diff --git a/src_qt6/EasyReflectometryApp/Backends/Py/status.py b/src_qt6/EasyReflectometryApp/Backends/Py/status.py index 33ee2d4e..21f38118 100644 --- a/src_qt6/EasyReflectometryApp/Backends/Py/status.py +++ b/src_qt6/EasyReflectometryApp/Backends/Py/status.py @@ -8,44 +8,36 @@ from easyreflectometry import Project as ProjectLib from .logic.status import Status as StatusLogic +from .logic.parameters import Parameters as ParametersLogic class Status(QObject): - projectChanged = Signal() - phaseCountChanged = Signal() - experimentsCountChanged = Signal() - calculatorChanged = Signal() - minimizerChanged = Signal() - variablesChanged = Signal() + statusChanged = Signal() def __init__(self, project_lib: ProjectLib, parent=None): super().__init__(parent) - self._logic = StatusLogic(project_lib) + self._status_logic = StatusLogic(project_lib) + self._parameters_logic = ParametersLogic(project_lib) - @Property(str, notify=projectChanged) + @Property(str, notify=statusChanged) def project(self): - return self._logic.project - - def setProject(self, new_value: str): - if self._logic.project != new_value: - self._logic.project = new_value - self.projectChanged.emit() - - @Property(str, notify=experimentsCountChanged) + return self._status_logic.project + + @Property(str, notify=statusChanged) def experimentsCount(self): - return self._logic.experiments_count + return self._status_logic.experiments_count - @Property(str, notify=calculatorChanged) + @Property(str, notify=statusChanged) def calculator(self): - return self._logic.calculator + return self._status_logic.calculator - @Property(str, notify=minimizerChanged) + @Property(str, notify=statusChanged) def minimizer(self): - return self._logic.minimizer + return self._status_logic.minimizer - @Property(str, notify=variablesChanged) + @Property(str, notify=statusChanged) def variables(self): - return self._logic.variables + return self._parameters_logic.as_status_string - @Property(str, notify=phaseCountChanged) + @Property(str, notify=statusChanged) def phaseCount(self): return None \ No newline at end of file diff --git a/src_qt6/EasyReflectometryApp/Gui/ApplicationWindow.qml b/src_qt6/EasyReflectometryApp/Gui/ApplicationWindow.qml index da90aea5..4befd413 100644 --- a/src_qt6/EasyReflectometryApp/Gui/ApplicationWindow.qml +++ b/src_qt6/EasyReflectometryApp/Gui/ApplicationWindow.qml @@ -26,10 +26,11 @@ EaComponents.ApplicationWindow { }, EaElements.ToolButton { - enabled: Globals.BackendWrapper.projectCreated + enabled: Globals.References.resetActive fontIcon: "backspace" ToolTip.text: qsTr("Reset to initial state without project, models and data") onClicked: { + Globals.References.resetActive = false Globals.BackendWrapper.projectReset() Globals.References.applicationWindow.appBarCentralTabs.projectButton.toggle() if (Globals.References.applicationWindow.appBarCentralTabs.sampleButton !== null) { @@ -73,7 +74,6 @@ EaComponents.ApplicationWindow { Globals.References.applicationWindow.appBarCentralTabs.homeButton = homeButton } }, - // Home page // Project page EaElements.AppBarTabButton { @@ -86,7 +86,6 @@ EaComponents.ApplicationWindow { Globals.References.applicationWindow.appBarCentralTabs.projectButton = projectButton } }, - // Project page // Sample page EaElements.AppBarTabButton { @@ -99,7 +98,6 @@ EaComponents.ApplicationWindow { Globals.References.applicationWindow.appBarCentralTabs.sampleButton = sampleButton } }, - // Sample page // Experiment tab EaElements.AppBarTabButton { @@ -111,19 +109,17 @@ EaComponents.ApplicationWindow { Component.onCompleted:Globals.References.applicationWindow.appBarCentralTabs.experimentButton = experimentTabButton }, -/* + // Analysis tab EaElements.AppBarTabButton { id: analysisTabButton - enabled: false //ExGlobals.Variables.samplePageEnabled && - //(ExGlobals.Constants.proxy.data.experimentSkipped || - // ExGlobals.Constants.proxy.data.experimentLoaded) + enabled: false fontIcon: "calculator" text: qsTr("Analysis") ToolTip.text: qsTr("Simulation and fitting page") Component.onCompleted: Globals.References.applicationWindow.appBarCentralTabs.analysisButton = analysisTabButton }, -*/ + // Summary page EaElements.AppBarTabButton { id: summaryButton @@ -135,7 +131,6 @@ EaComponents.ApplicationWindow { Globals.References.applicationWindow.appBarCentralTabs.summaryButton = summaryButton } } - // Summary page ] ////////////////////////////////// @@ -148,8 +143,8 @@ EaComponents.ApplicationWindow { Loader { source: 'Pages/Project/Layout.qml' }, Loader { source: 'Pages/Sample/Layout.qml' }, Loader { source: 'Pages/Experiment/Layout.qml' }, -// Loader { source: 'Pages/Analysis/Layout.qml' }, - Loader { source: 'Pages/Report/Layout.qml' } + Loader { source: 'Pages/Analysis/Layout.qml' }, + Loader { source: 'Pages/Summary/Layout.qml' } ] ///////////// diff --git a/src_qt6/EasyReflectometryApp/Gui/Globals/BackendWrapper.qml b/src_qt6/EasyReflectometryApp/Gui/Globals/BackendWrapper.qml index 298e7f10..35bdac2c 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Globals/BackendWrapper.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Globals/BackendWrapper.qml @@ -81,6 +81,7 @@ QtObject { readonly property var sampleMaterials: activeBackend.sample.materials readonly property var sampleMaterialNames: activeBackend.sample.materialNames + readonly property int sampleCurrentMaterialIndex: activeBackend.sample.currentMaterialIndex function sampleSetCurrentMaterialIndex(value) { activeBackend.sample.setCurrentMaterialIndex(value) } function sampleSetCurrentMaterialName(value) { activeBackend.sample.setCurrentMaterialName(value) } @@ -96,6 +97,7 @@ QtObject { readonly property var sampleModels: activeBackend.sample.models readonly property string sampleCurrentModelName: activeBackend.sample.currentModelName + readonly property int sampleCurrentModelIndex: activeBackend.sample.currentModelIndex function sampleSetCurrentModelIndex(value) { activeBackend.sample.setCurrentModelIndex(value) } function sampleSetCurrentModelName(value) { activeBackend.sample.setCurrentModelName(value) } @@ -110,6 +112,7 @@ QtObject { readonly property string sampleCurrentAssemblyName: activeBackend.sample.currentAssemblyName readonly property string sampleCurrentAssemblyType: activeBackend.sample.currentAssemblyType + readonly property int sampleCurrentAssemblyIndex: activeBackend.sample.currentAssemblyIndex function sampleSetCurrentAssemblyIndex(value) { activeBackend.sample.setCurrentAssemblyIndex(value) } function sampleSetCurrentAssemblyName(value) { activeBackend.sample.setCurrentAssemblyName(value) } @@ -130,6 +133,7 @@ QtObject { readonly property var sampleLayers: activeBackend.sample.layers readonly property string sampleCurrentLayerName: activeBackend.sample.currentLayerName + readonly property int sampleCurrentLayerIndex: activeBackend.sample.currentLayerIndex function sampleSetCurrentLayerIndex(value) { activeBackend.sample.setCurrentLayerIndex(value) } function sampleRemoveLayer(value) { activeBackend.sample.removeLayer(value) } @@ -146,6 +150,20 @@ QtObject { function sampleSetCurrentLayerAPM(value) { activeBackend.sample.setCurrentLayerAPM(value) } function sampleSetCurrentLayerSolvation(value) { activeBackend.sample.setCurrentLayerSolvation(value) } + // Constraints + readonly property var sampleParameterNames: activeBackend.sample.parameterNames + readonly property var sampleRelationOperators: activeBackend.sample.relationOperators + readonly property var sampleArithmicOperators: activeBackend.sample.arithmicOperators + + function sampleAddConstraint(value1, value2, value3, value4, value5) { activeBackend.sample.addConstraint(value1, value2, value3, value4, value5) } + + // Q range + readonly property var sampleQMin: activeBackend.sample.q_min + function sampleSetQMin(value) { activeBackend.sample.setQMin(value) } + readonly property var sampleQMax: activeBackend.sample.q_max + function sampleSetQMax(value) { activeBackend.sample.setQMax(value) } + readonly property var sampleQResolution: activeBackend.sample.q_resolution + function sampleSetQElements(value) { activeBackend.sample.setQElements(value) } ////////////////// // Experiment page @@ -158,20 +176,50 @@ QtObject { function experimentSetBackground(value) { activeBackend.experiment.setBackground(value) } readonly property var experimentResolution: activeBackend.experiment.resolution function experimentSetResolution(value) { activeBackend.experiment.setResolution(value) } - readonly property var experimentQMin: activeBackend.experiment.q_min - function experimentSetQMin(value) { activeBackend.experiment.setQMin(value) } - readonly property var experimentQMax: activeBackend.experiment.q_max - function experimentSetQMax(value) { activeBackend.experiment.setQMax(value) } - readonly property var experimentQResolution: activeBackend.experiment.q_resolution - function experimentSetQElements(value) { activeBackend.experiment.setQElements(value) } function experimentLoad(value) { activeBackend.experiment.load(value) } /////////////// // Analysis page /////////////// + readonly property var analysisExperimentsAvailable: activeBackend.analysis.experimentsAvailable + readonly property int analysisExperimentsCurrentIndex: activeBackend.analysis.experimentCurrentIndex + function analysisSetExperimentsCurrentIndex(value) { activeBackend.analysis.setExperimentCurrentIndex(value) } + + readonly property var analysisCalculatorsAvailable: activeBackend.analysis.calculatorsAvailable + readonly property int analysisCalculatorCurrentIndex: activeBackend.analysis.calculatorCurrentIndex + function analysisSetCalculatorCurrentIndex(value) { activeBackend.analysis.setCalculatorCurrentIndex(value) } + + readonly property var analysisMinimizersAvailable: activeBackend.analysis.minimizersAvailable + readonly property int analysisMinimizerCurrentIndex: activeBackend.analysis.minimizerCurrentIndex + function analysisSetMinimizerCurrentIndex(value) { activeBackend.analysis.setMinimizerCurrentIndex(value) } + + readonly property var analysisFitableParameters: activeBackend.analysis.fitableParameters + readonly property int analysisCurrentParameterIndex: activeBackend.analysis.currentParameterIndex + function analysisSetCurrentParameterIndex(value) { activeBackend.analysis.setCurrentParameterIndex(value) } + + // Minimizer + readonly property var analysisMinimizerTolerance: activeBackend.analysis.minimizerTolerance + function analysisSetMinimizerTolerance(value) { activeBackend.analysis.setMinimizerTolerance(value) } + readonly property var analysisMinimizerMaxIterations: activeBackend.analysis.minimizerMaxIterations + function analysisSetMinimizerMaxIterations(value) { activeBackend.analysis.setMinimizerMaxIterations(value) } + + // Fitting + readonly property string analysisFittingStatus: activeBackend.analysis.fittingStatus + readonly property bool analysisFittingRunning: activeBackend.analysis.fittingRunning readonly property bool analysisIsFitFinished: activeBackend.analysis.isFitFinished + function analysisFittingStartStop() { activeBackend.analysis.fittingStartStop() } + // Parameters + readonly property int analysisFreeParametersCount: activeBackend.analysis.freeParametersCount + readonly property int analysisFixedParametersCount: activeBackend.analysis.fixedParametersCount + readonly property int analysisModelParametersCount: activeBackend.analysis.modelParametersCount + readonly property int analysisExperimentParametersCount: activeBackend.analysis.experimentParametersCount + + function analysisSetCurrentParameterValue(value) { activeBackend.analysis.setCurrentParameterValue(value) } + function analysisSetCurrentParameterMin(value) { activeBackend.analysis.setCurrentParameterMin(value) } + function analysisSetCurrentParameterMax(value) { activeBackend.analysis.setCurrentParameterMax(value) } + function analysisSetCurrentParameterFit(value) { activeBackend.analysis.setCurrentParameterFit(value) } /////////////// // Summary page @@ -197,5 +245,12 @@ QtObject { readonly property var plottingExperimentMinY: activeBackend.plotting.sampleMinY readonly property var plottingExperimentMaxY: activeBackend.plotting.sampleMaxY + readonly property var plottingAnalysisMinX: activeBackend.plotting.sampleMinX + readonly property var plottingAnalysisMaxX: activeBackend.plotting.sampleMaxX + readonly property var plottingAnalysisMinY: activeBackend.plotting.sampleMinY + readonly property var plottingAnalysisMaxY: activeBackend.plotting.sampleMaxY + function plottingSetQtChartsSerieRef(value1, value2, value3) { activeBackend.plotting.setQtChartsSerieRef(value1, value2, value3) } + function plottingRefreshSample() { activeBackend.plotting.drawCalculatedOnSampleChart() } + function plottingRefreshSLD() { activeBackend.plotting.drawCalculatedOnSldChart() } } diff --git a/src_qt6/EasyReflectometryApp/Gui/Globals/References.qml b/src_qt6/EasyReflectometryApp/Gui/Globals/References.qml index b9ba22eb..1133778a 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Globals/References.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Globals/References.qml @@ -9,6 +9,8 @@ import QtQuick QtObject { + property bool resetActive: false + // Populated in ApplicationWindows.qml readonly property var applicationWindow: { 'appBarCentralTabs': { @@ -50,11 +52,19 @@ QtObject { } } } + }, + 'analysis': { + 'mainContent': { + 'analysisView': null, + }, + 'sidebar': { + 'basic': { + 'popups': { + 'startFittingButton': null, + 'fitStatusDialogOkButton': null + } + } + } } } - -// // Populated in plotting/... -// readonly property var plotting: { -// 'graph1d': null, -// } } diff --git a/src_qt6/EasyReflectometryApp/Gui/Globals/Variables.qml b/src_qt6/EasyReflectometryApp/Gui/Globals/Variables.qml index 039a2226..d269b1d4 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Globals/Variables.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Globals/Variables.qml @@ -8,5 +8,7 @@ import QtQuick // whose id is stored here can be accessed from any other qml file. QtObject { + property bool showLegendOnSamplePage: false property bool showLegendOnExperimentPage: false -} \ No newline at end of file + property bool showLegendOnAnalysisPage: false +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Layout.qml new file mode 100644 index 00000000..e1adec3e --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Layout.qml @@ -0,0 +1,66 @@ +import QtQuick +import QtQuick.Controls +//import QtQuick.XmlListModel 2.15 + +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents + +import Gui.Globals as Globals +//import Gui.Components as Components + + +EaComponents.ContentPage { +// defaultInfo: Globals.Proxies.main.model.defined && +// Globals.Proxies.main.experiment.defined ? +// "" : +// qsTr("No analysis done") + + mainView: EaComponents.MainContent { +/* tabs: [ + EaElements.TabButton { + text: Globals.Proxies.experimentMainParam('_sample', 'type').value === 'pd' ? + qsTr("Fitting") : + qsTr("I vs. sinθ/λ") + }, + EaElements.TabButton { text: qsTr("Imeas vs. Icalc") } + ] +*/ + items: [ + Loader { + source: `MainContent/AnalysisView.qml` + onStatusChanged: if (status === Loader.Ready) console.debug(`${source} loaded`) + } +// Loader { +// source: `MainContent/ScChartTab.qml` +// onStatusChanged: if (status === Loader.Ready) console.debug(`${source} loaded`) +// } + ] + } + + sideBar: EaComponents.SideBar { + tabs: [ + EaElements.TabButton { text: qsTr("Basic controls") }, + EaElements.TabButton { text: qsTr("Extra controls") } //; enabled: Globals.Proxies.main.analysis.defined } + ] + + items: [ + Loader { source: 'Sidebar/Basic/Layout.qml' }, + Loader { source: 'Sidebar/Advanced/Layout.qml' } + ] + +// continueButton.enabled: Globals.Proxies.main.summary.isCreated + + continueButton.onClicked: { + console.debug(`Clicking '${continueButton.text}' button: ${this}`) + Globals.References.applicationWindow.appBarCentralTabs.summaryButton.enabled = true + Globals.References.applicationWindow.appBarCentralTabs.summaryButton.toggle() + } + +// Component.onCompleted: Globals.Refs.app.analysisPage.continueButton = continueButton + } + + Component.onCompleted: console.debug(`Analysis page loaded: ${this}`) + Component.onDestruction: console.debug(`Analysis page destroyed: ${this}`) +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/MainContent/AnalysisView.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/MainContent/AnalysisView.qml new file mode 100644 index 00000000..db10ca5b --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/MainContent/AnalysisView.qml @@ -0,0 +1,183 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick +import QtQuick.Controls +import QtCharts + +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Charts as EaCharts + +import Gui.Globals as Globals + + +Rectangle { + id: container + + color: EaStyle.Colors.chartBackground + EaCharts.QtCharts1dMeasVsCalc { + id: chartView + + property alias calculated: chartView.calcSerie + property alias measured: chartView.measSerie +// property alias errorLower: chartView.bkgSerie + bkgSerie.color: measSerie.color + measSerie.width: 1 + bkgSerie.width: 1 + + anchors.topMargin: EaStyle.Sizes.toolButtonHeight - EaStyle.Sizes.fontPixelSize - 1 + + useOpenGL: EaGlobals.Vars.useOpenGL + + property double xRange: Globals.BackendWrapper.plottingAnalysisMaxX - Globals.BackendWrapper.plottingAnalysisMinX + axisX.title: "q (Å⁻¹)" + axisX.min: Globals.BackendWrapper.plottingAnalysisMinX - xRange * 0.01 + axisX.max: Globals.BackendWrapper.plottingAnalysisMaxX + xRange * 0.01 + axisX.minAfterReset: Globals.BackendWrapper.plottingAnalysisMinX - xRange * 0.01 + axisX.maxAfterReset: Globals.BackendWrapper.plottingAnalysisMaxX + xRange * 0.01 + + property double yRange: Globals.BackendWrapper.plottingAnalysisMaxY - Globals.BackendWrapper.plottingAnalysisMinY + axisY.title: "Log10 R(q)" + axisY.min: Globals.BackendWrapper.plottingAnalysisMinY - yRange * 0.01 + axisY.max: Globals.BackendWrapper.plottingAnalysisMaxY + yRange * 0.01 + axisY.minAfterReset: Globals.BackendWrapper.plottingAnalysisMinY - yRange * 0.01 + axisY.maxAfterReset: Globals.BackendWrapper.plottingAnalysisMaxY + yRange * 0.01 + + calcSerie.onHovered: (point, state) => showMainTooltip(chartView, point, state) + + // Tool buttons + Row { + id: toolButtons + + x: chartView.plotArea.x + chartView.plotArea.width - width + y: chartView.plotArea.y - height - EaStyle.Sizes.fontPixelSize + + spacing: 0.25 * EaStyle.Sizes.fontPixelSize + + EaElements.TabButton { + checked: Globals.Variables.showLegendOnAnalysisPage + autoExclusive: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "align-left" + ToolTip.text: Globals.Variables.showLegendOnAnalysisPage ? + qsTr("Hide legend") : + qsTr("Show legend") + onClicked: Globals.Variables.showLegendOnAnalysisPage = checked + } + + EaElements.TabButton { + checked: chartView.allowHover + autoExclusive: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "comment-alt" + ToolTip.text: qsTr("Show coordinates tooltip on hover") + onClicked: chartView.allowHover = !chartView.allowHover + } + + Item { height: 1; width: 0.5 * EaStyle.Sizes.fontPixelSize } // spacer + + EaElements.TabButton { + checked: !chartView.allowZoom + autoExclusive: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "arrows-alt" + ToolTip.text: qsTr("Enable pan") + onClicked: chartView.allowZoom = !chartView.allowZoom + } + + EaElements.TabButton { + checked: chartView.allowZoom + autoExclusive: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "expand" + ToolTip.text: qsTr("Enable box zoom") + onClicked: chartView.allowZoom = !chartView.allowZoom + } + + EaElements.TabButton { + checkable: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "backspace" + ToolTip.text: qsTr("Reset axes") + onClicked: chartView.resetAxes() + } + + } + // Tool buttons + + // Legend + Rectangle { + visible: Globals.Variables.showLegendOnAnalysisPage + + x: chartView.plotArea.x + chartView.plotArea.width - width - EaStyle.Sizes.fontPixelSize + y: chartView.plotArea.y + EaStyle.Sizes.fontPixelSize + width: childrenRect.width + height: childrenRect.height + + color: EaStyle.Colors.mainContentBackgroundHalfTransparent + border.color: EaStyle.Colors.chartGridLine + + Column { + leftPadding: EaStyle.Sizes.fontPixelSize + rightPadding: EaStyle.Sizes.fontPixelSize + topPadding: EaStyle.Sizes.fontPixelSize * 0.5 + bottomPadding: EaStyle.Sizes.fontPixelSize * 0.5 + + EaElements.Label { + text: '━ I (Measured)' + color: chartView.measSerie.color + } + EaElements.Label { + text: '━ (calculated)' + color: chartView.calcSerie.color + } + } + } + // Legend + + EaElements.ToolTip { + id: dataToolTip + + arrowLength: 0 + textFormat: Text.RichText + } + + // Data is set in python backend (plotting_1d.py) + Component.onCompleted: { + Globals.References.pages.analysis.mainContent.analysisView = chartView + Globals.BackendWrapper.plottingSetQtChartsSerieRef('analysisPage', + 'measuredSerie', + measured) + Globals.BackendWrapper.plottingSetQtChartsSerieRef('analysisPage', + 'calculatedSerie', + calculated) + } + } + + // Logic + + function showMainTooltip(chart, point, state) { + if (!chartView.allowHover) { + return + } + const pos = chart.mapToPosition(Qt.point(point.x, point.y)) + dataToolTip.x = pos.x + dataToolTip.y = pos.y + dataToolTip.text = `

x: ${point.x.toFixed(3)}y: ${point.y.toFixed(3)}

` + dataToolTip.parent = chart + dataToolTip.visible = state + } +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/MainContent/diffraction_AnalysisView.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/MainContent/diffraction_AnalysisView.qml new file mode 100644 index 00000000..42a45bd3 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/MainContent/diffraction_AnalysisView.qml @@ -0,0 +1,570 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick +import QtQuick.Controls +import QtCharts + +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Charts as EaCharts + +import Gui.Globals as Globals + + +Column { + id: container + + property alias measSerie: measSerie + property alias bkgSerie: bkgSerie + property alias calcSerie: calcSerie + property alias residSerie: residSerie + + property var phaseNames: { + if (typeof Globals.Proxies.main.experiment.dataBlocksNoMeas[ + Globals.Proxies.main.experiment.currentIndex].loops._pd_phase_block !== 'undefined') { + return Globals.Proxies.main.experiment.dataBlocksNoMeas[ + Globals.Proxies.main.experiment.currentIndex].loops._pd_phase_block.map( + phase => phase.id.value) + } else if (typeof Globals.Proxies.main.experiment.dataBlocksNoMeas[ + Globals.Proxies.main.experiment.currentIndex].loops._exptl_crystal !== 'undefined') { + return Globals.Proxies.main.experiment.dataBlocksNoMeas[ + Globals.Proxies.main.experiment.currentIndex].loops._exptl_crystal.map( + phase => phase.id.value) + } else { + //console.error('No phase names found') + return [] + } + } + + property string calcSerieColor: EaStyle.Colors.chartForegrounds[0] + + property int extraMargin: -12 + property real residualToMainChartHeightRatio: 0.3 + property real mainChartHeightCoeff: 1 - residualToMainChartHeightRatio + + property bool useOpenGL: Globals.Proxies.main.fitting.isFittingNow ? + true : + EaGlobals.Vars.useOpenGL //Globals.Proxies.main.plotting.useWebGL1d + + Column { + width: parent.width + height: parent.height - 3 * EaStyle.Sizes.fontPixelSize + 2 + + /////////////////////////////////////////// + // Main chart container: Imeas, Icalc, Ibkg + /////////////////////////////////////////// + + Item { + width: parent.width + height: parent.height * mainChartHeightCoeff - + braggChart.parent.height * 0.5 + + EaCharts.QtCharts1dBase { + id: mainChart + + property var experimentDataBlocksNoMeas: Globals.Proxies.main.experiment.dataBlocksNoMeas + onExperimentDataBlocksNoMeasChanged: { + if (Globals.Proxies.experimentMainParam('_sample', 'type').value === 'pd') { + if (Globals.Proxies.experimentMainParam('_diffrn_radiation', 'type').value === 'cwl') { + axisX.title = '2θ (degree)' + } else if (Globals.Proxies.experimentMainParam('_diffrn_radiation', 'type').value === 'tof') { + axisX.title = 'TOF (µs)' + } else { + axisX.title = '' + } + } else if (Globals.Proxies.experimentMainParam('_sample', 'type').value === 'sg') { + axisX.title = 'sinθ/λ (Å⁻¹)' + } else { + axisX.title = '' + } + } + + anchors.topMargin: EaStyle.Sizes.toolButtonHeight - EaStyle.Sizes.fontPixelSize - 1 + anchors.bottomMargin: -12 - EaStyle.Sizes.fontPixelSize + + useOpenGL: container.useOpenGL + + axisX.titleVisible: false + axisX.labelsVisible: false + axisX.min: Globals.Proxies.rangeValue('xMin') + axisX.max: Globals.Proxies.rangeValue('xMax') + axisX.minAfterReset: Globals.Proxies.rangeValue('xMin') + axisX.maxAfterReset: Globals.Proxies.rangeValue('xMax') + axisX.onRangeChanged: alignAllCharts() + + axisY.title: Globals.Proxies.experimentMainParam('_sample', 'type').value === 'pd' ? + "Imeas, Icalc, Ibkg" : + "Imeas, Icalc" + axisY.min: Globals.Proxies.rangeValue('yMin') + axisY.max: Globals.Proxies.rangeValue('yMax') + axisY.minAfterReset: Globals.Proxies.rangeValue('yMin') + axisY.maxAfterReset: Globals.Proxies.rangeValue('yMax') + axisY.onRangeChanged: adjustResidualChartRangeY() + + backgroundColor: "transparent" + plotAreaColor: "transparent" + + // Measured points + /* + ScatterSeries { + id: measSerie + + axisX: mainChart.axisX + axisY: mainChart.axisY + + useOpenGL: mainChart.useOpenGL + + markerSize: 5 + borderWidth: 1 + color: EaStyle.Colors.chartForegroundsExtra[2] + borderColor: this.color + } + */ + LineSeries { + id: measSerie + + axisX: mainChart.axisX + axisY: mainChart.axisY + + //useOpenGL: mainChart.useOpenGL + + color: EaStyle.Colors.chartForegroundsExtra[2] + width: 2 + + //style: Globals.Proxies.experimentMainParam('_sample', 'type').value === 'pd' ? + // Qt.SolidLine : + // Qt.NoPen + pointsVisible: true + + onHovered: (point, state) => showMainTooltip(mainChart, point, state) + } + + // Background curve + LineSeries { + id: bkgSerie + + axisX: mainChart.axisX + axisY: mainChart.axisY + + //useOpenGL: mainChart.useOpenGL + + color: EaStyle.Colors.chartForegrounds[1] + width: 1 + + onHovered: (point, state) => showMainTooltip(mainChart, point, state) + } + + // Calculated curve + LineSeries { + id: calcSerie + + axisX: mainChart.axisX + axisY: mainChart.axisY + + //useOpenGL: mainChart.useOpenGL + + color: calcSerieColor + width: 2 + + //style: Globals.Proxies.experimentMainParam('_sample', 'type').value === 'pd' ? + // Qt.SolidLine : + // Qt.NoPen + //pointsVisible: Globals.Proxies.experimentMainParam('_sample', 'type').value === 'pd' ? + // false : + // true + + onHovered: (point, state) => showMainTooltip(mainChart, point, state) + } + + // Tool buttons + Row { + id: toolButtons + + x: mainChart.plotArea.x + mainChart.plotArea.width - width + y: mainChart.plotArea.y - height - EaStyle.Sizes.fontPixelSize + + spacing: 0.25 * EaStyle.Sizes.fontPixelSize + + EaElements.TabButton { + checked: Globals.Vars.showLegendOnAnalysisPage + autoExclusive: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "align-left" + ToolTip.text: Globals.Vars.showLegendOnAnalysisPage ? + qsTr("Hide legend") : + qsTr("Show legend") + onClicked: Globals.Vars.showLegendOnAnalysisPage = checked + } + + EaElements.TabButton { + checked: mainChart.allowHover + autoExclusive: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "comment-alt" + ToolTip.text: qsTr("Show coordinates tooltip on hover") + onClicked: mainChart.allowHover = !mainChart.allowHover + } + + Item { height: 1; width: 0.5 * EaStyle.Sizes.fontPixelSize } // spacer + + EaElements.TabButton { + checked: !mainChart.allowZoom + autoExclusive: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "arrows-alt" + ToolTip.text: qsTr("Enable pan") + onClicked: mainChart.allowZoom = !mainChart.allowZoom + } + + EaElements.TabButton { + checked: mainChart.allowZoom + autoExclusive: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "expand" + ToolTip.text: qsTr("Enable box zoom") + onClicked: mainChart.allowZoom = !mainChart.allowZoom + } + + EaElements.TabButton { + checkable: false + height: EaStyle.Sizes.toolButtonHeight + width: EaStyle.Sizes.toolButtonHeight + borderColor: EaStyle.Colors.chartAxis + fontIcon: "backspace" + ToolTip.text: qsTr("Reset axes") + onClicked: mainChart.resetAxes() + } + + } + // Tool buttons + } + } + + ////////////////////////////////////// + // Bragg peaks chart container: Bragg + ////////////////////////////////////// + + Item { + z: -1 + width: parent.width + height: (0.5 + 1.5 * phaseNames.length) * EaStyle.Sizes.fontPixelSize + + + /////onHeightChanged: console.info(`================== ${height} - ${phaseNames.length}`) + //visible: false + + EaCharts.QtCharts1dBase { + id: braggChart + + anchors.topMargin: -12 - EaStyle.Sizes.fontPixelSize * 1.5 + anchors.bottomMargin: -12 - EaStyle.Sizes.fontPixelSize * 1.5 + + useOpenGL: container.useOpenGL + + axisX.min: mainChart.axisX.min + axisX.max: mainChart.axisX.max + axisX.titleVisible: false + axisX.labelsVisible: false + + axisY.min: -0.5 * phaseNames.length + axisY.max: 0.5 + axisY.titleVisible: false + axisY.labelsVisible: false + axisY.tickCount: 2 + + backgroundColor: "transparent" + plotAreaColor: "transparent" + + //onSeriesAdded: { console.error(series) } + + /* + ScatterSeries { + id: braggSerie + + axisX: braggChart.axisX + axisY: braggChart.axisY + + //useOpenGL: braggChart.useOpenGL + + brush: Globals.Proxies.main.plotting.verticalLine( + 1.5 * EaStyle.Sizes.fontPixelSize, + EaStyle.Colors.chartForegroundsExtra[0]) + borderWidth: 0.001 + borderColor: 'transparent' + + Component.onCompleted: console.error(this) + } + */ + } + } + + ////////////////////////////////////////// + // Residual chart container: Imeas - Icalc + ////////////////////////////////////////// + + Item { + width: parent.width + height: parent.height * residualToMainChartHeightRatio - + braggChart.parent.height * 0.5 + + EaCharts.QtCharts1dBase { + id: residualChart + + anchors.topMargin: -12 - EaStyle.Sizes.fontPixelSize + + useOpenGL: container.useOpenGL + + axisX.min: mainChart.axisX.min + axisX.max: mainChart.axisX.max + axisX.titleVisible: false + axisX.labelsVisible: false + + axisY.min: Globals.Proxies.main.plotting.chartRanges.yMin + axisY.max: Globals.Proxies.main.plotting.chartRanges.yMax + axisY.tickType: ValueAxis.TicksFixed + axisY.tickCount: 3 + axisY.title: 'Imeas - Icalc' + + backgroundColor: "transparent" + plotAreaColor: "transparent" + + LineSeries { + id: residSerie + + axisX: residualChart.axisX + axisY: residualChart.axisY + + //useOpenGL: residualChart.useOpenGL + + color: EaStyle.Colors.chartForegrounds[2] + + onHovered: (point, state) => showMainTooltip(residualChart, point, state) + } + } + } + } + + ///////////////////////// + // X-axis chart container + ///////////////////////// + + Item { + z: -1 + width: parent.width + height: container.height + parent: container.parent + + EaCharts.QtCharts1dBase { + id: xAxisChart + + axisX.title: mainChart.axisX.title + axisX.min: mainChart.axisX.min + axisX.max: mainChart.axisX.max + axisX.lineVisible: false + axisX.gridVisible: false + + axisY.titleVisible: false + axisY.labelsVisible: false + axisY.visible: false + + LineSeries { + axisX: xAxisChart.axisX + axisY: xAxisChart.axisY + + Component.onCompleted: initialChartsSetupTimer.start() + + Timer { + id: initialChartsSetupTimer + interval: 50 + onTriggered: { + alignAllCharts() + adjustResidualChartRangeY() + } + } + } + } + } + + ///////// + // Legend + ///////// + + Rectangle { + visible: Globals.Vars.showLegendOnAnalysisPage + parent: container.parent + + x: mainChart.plotArea.x + mainChart.plotArea.width - width - 12 - EaStyle.Sizes.fontPixelSize + y: mainChart.plotArea.y - 12 + EaStyle.Sizes.fontPixelSize + mainChart.anchors.topMargin + EaStyle.Sizes.fontPixelSize - 1 + width: childrenRect.width + height: childrenRect.height + + color: EaStyle.Colors.mainContentBackgroundHalfTransparent + border.color: EaStyle.Colors.chartGridLine + + Column { + id: legendColumn + + leftPadding: EaStyle.Sizes.fontPixelSize + rightPadding: EaStyle.Sizes.fontPixelSize + topPadding: EaStyle.Sizes.fontPixelSize * 0.5 + bottomPadding: EaStyle.Sizes.fontPixelSize * 0.5 + + EaElements.Label { + text: '━ Measured (Imeas)' + color: measSerie.color + } + EaElements.Label { + text: Globals.Proxies.experimentMainParam('_sample', 'type').value === 'pd' ? + '━ Total calculated (Icalc)' : + '━ Calculated (Icalc)' + color: calcSerie.color + } + EaElements.Label { + visible: Globals.Proxies.experimentMainParam('_sample', 'type').value === 'pd' + text: '─ Background (Ibkg)' + color: bkgSerie.color + } + EaElements.Label { + text: '━ Residual (Imeas - Icalc)' + color: residSerie.color + } + /* + EaElements.Label { + text: '│ Ibragg (Bragg peaks)' + color: EaStyle.Colors.chartForegroundsExtra[0] //braggSerie.color + } + */ + } + } + + /////////// + // ToolTips + /////////// + + EaElements.ToolTip { + id: dataToolTip + + arrowLength: 0 + textFormat: Text.RichText + } + + // Save references to chart series to be accessible from Python for updating data + Component.onCompleted: { + Globals.Refs.app.analysisPage.plotView = this + Globals.Proxies.main.plotting.setQtChartsSerieRef('analysisPage', + 'measSerie', + this.measSerie) + Globals.Proxies.main.plotting.setQtChartsSerieRef('analysisPage', + 'bkgSerie', + this.bkgSerie) + Globals.Proxies.main.plotting.setQtChartsSerieRef('analysisPage', + 'totalCalcSerie', + this.calcSerie) + Globals.Proxies.main.plotting.setQtChartsSerieRef('analysisPage', + 'residSerie', + this.residSerie) + //Globals.Proxies.main.plotting.setQtChartsSerieRef('analysisPage', + // 'braggSerie', + // this.braggSerie) + createBraggSeries() + + Globals.Proxies.main.analysis.defined = true + } + + // Logic + + function residualChartMeanY() { + return 0 + } + + function residualChartHalfRangeY() { + if (mainChart.plotArea.height === 0) { + return 0.5 + } + + const mainChartRangeY = mainChart.axisY.max - mainChart.axisY.min + const residualToMainChartHeightRatio = residualChart.plotArea.height / mainChart.plotArea.height + const residualChartRangeY = mainChartRangeY * residualToMainChartHeightRatio + return 0.5 * residualChartRangeY + } + + function adjustResidualChartRangeY() { + residualChart.axisY.min = residualChartMeanY() - residualChartHalfRangeY() + residualChart.axisY.max = residualChartMeanY() + residualChartHalfRangeY() + console.debug('Residual chart Y-range has been adjusted') + } + + function alignAllCharts() { + xAxisChart.plotArea.width -= mainChart.plotArea.x - xAxisChart.plotArea.x + xAxisChart.plotArea.x = mainChart.plotArea.x + residualChart.plotArea.width = xAxisChart.plotArea.width + residualChart.plotArea.x = mainChart.plotArea.x + braggChart.plotArea.width = xAxisChart.plotArea.width + braggChart.plotArea.x = mainChart.plotArea.x + mainChart.plotArea.width = xAxisChart.plotArea.width + console.debug('All charts have been aligned') + } + + function showMainTooltip(chart, point, state) { + if (!mainChart.allowHover) { + return + } + const pos = chart.mapToPosition(Qt.point(point.x, point.y)) + dataToolTip.x = pos.x + dataToolTip.y = pos.y + dataToolTip.text = `

x: ${point.x.toFixed(2)}y: ${point.y.toFixed(2)}

` + dataToolTip.parent = chart + dataToolTip.visible = state + } + + function createBraggSeries() { + for (const phaseIdx in phaseNames) { + const phaseName = phaseNames[phaseIdx] + const serie = braggChart.createSeries(ChartView.SeriesTypeScatter, + phaseName, + braggChart.axisX, + braggChart.axisY) + const markerSize = //serie.useOpenGL ? + //5 : // don't work here... :( + 1.5 * EaStyle.Sizes.fontPixelSize + serie.useOpenGL = braggChart.useOpenGL + //serie.useOpenGL = Globals.Proxies.main.fitting.isFittingNow + serie.brush = Globals.Proxies.main.plotting.verticalLine( + markerSize, + EaStyle.Colors.models[phaseIdx]) + serie.borderWidth = 0.001 + serie.borderColor = 'transparent' + //serie.markerShape = ScatterSeries.MarkerShapeRectangle + serie.markerSize = serie.useOpenGL ? + 0 : + markerSize + + Globals.Proxies.main.plotting.setQtChartsSerieRef('analysisPage', + 'braggSeries', + serie) + + const legendItem = Qt.createQmlObject('import EasyApp.Gui.Elements as EaElements; EaElements.Label {}', legendColumn) + const textFont = `'${EaStyle.Fonts.fontFamily}'` + const iconFont = `'${EaStyle.Fonts.iconsFamily}'` + const textColor = `'${EaStyle.Colors.models[phaseIdx]}'` + const iconColor = `'${EaStyle.Colors.models[phaseIdx]}'` + const textHtmlStart = `│  Bragg peaks` + const iconHtml = `layer-group` + const textHtmlEnd = `${phaseName}` + legendItem.text = `${textHtmlStart} ${iconHtml} ${textHtmlEnd}` + legendItem.textFormat = Text.RichText + } + } + +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Groups/Calculator.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Groups/Calculator.qml new file mode 100644 index 00000000..8200ed16 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Groups/Calculator.qml @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick +import QtQuick.Controls + +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements + +import Gui.Globals as Globals + +EaElements.GroupBox { + title: qsTr("Calculation engine") + icon: 'calculator' + EaElements.GroupRow { + + EaElements.ComboBox { + width: EaStyle.Sizes.sideBarContentWidth + model: Globals.BackendWrapper.analysisCalculatorsAvailable + currentIndex: Globals.BackendWrapper.analysisCalculatorCurrentIndex + onCurrentIndexChanged: Globals.BackendWrapper.analysisSetCalculatorCurrentIndex(currentIndex) + } + } +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Groups/Minimizer.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Groups/Minimizer.qml new file mode 100644 index 00000000..4f26a385 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Groups/Minimizer.qml @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick +import QtQuick.Controls + +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements + +import Gui.Globals as Globals + +EaElements.GroupBox { + title: qsTr("Minimization method") + icon: 'level-down-alt' + + EaElements.GroupRow{ + EaElements.ComboBox { + width: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 2 + topInset: minimizerLabel.height + topPadding: topInset + padding + model: Globals.BackendWrapper.analysisMinimizersAvailable + EaElements.Label { + id: minimizerLabel + text: qsTr("Minimizer") + color: EaStyle.Colors.themeForegroundMinor + } + currentIndex: Globals.BackendWrapper.analysisMinimizerCurrentIndex + onCurrentIndexChanged: Globals.BackendWrapper.analysisSetMinimizerCurrentIndex(currentIndex) + } + + EaElements.TextField { + width: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 4 + topInset: toleranceLabel.height + topPadding: topInset + padding + horizontalAlignment: TextInput.AlignLeft + onAccepted: { + onAccepted: Globals.BackendWrapper.analysisSetMinimizerTolerance(text) + focus = false + } + text: Globals.BackendWrapper.analysisMinimizerTolerance === undefined ? 'Defaults' : Number(Globals.BackendWrapper.analysisMinimizerTolerance).toFixed(3) + EaElements.Label { + id: toleranceLabel + text: qsTr("Tolerance") + color: EaStyle.Colors.themeForegroundMinor + } + } + + EaElements.TextField { + width: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 4 + topInset: maxIterLabel.height + topPadding: topInset + padding + horizontalAlignment: TextInput.AlignLeft + onAccepted: { + Globals.BackendWrapper.analysisSetMinimizerMaxIterations(text) + focus = false + } + text: Globals.BackendWrapper.analysisMinimizerMaxIterations === undefined ? 'Defaults' : Number(Globals.BackendWrapper.analysisMinimizerMaxIterations) + EaElements.Label { + id: maxIterLabel + text: qsTr("Max evaluations") + color: EaStyle.Colors.themeForegroundMinor + } + } + } +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Groups/ParamNames.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Groups/ParamNames.qml new file mode 100644 index 00000000..5d148b89 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Groups/ParamNames.qml @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick +import QtQuick.Controls + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents + +import Gui.Globals as Globals + +EaElements.GroupBox { + title: qsTr("Parameter names") + icon: "paint-brush" + collapsed: false + EaElements.GroupRow{ + + EaComponents.TableView { + id: tableView + + readonly property var param: { + "blockType": "model", + "blockIcon": "layer-group", + "blockIdx": 0, + "blockName": "co2sio4", + "categoryIcon": "atom", + "category": "_atom_site", + "prettyCategory": "atom", + "rowIndex": 2, + "rowName": "Si", + "icon": "map-marker-alt", + "name": "_fract_x", + "prettyName": "fract x", + "shortPrettyName": "x" } + + showHeader: false + tallRows: true + maxRowCountShow: model.length + + /* + model: [ + { value: EaGlobals.Vars.ShortestWithIconsAndPrettyLabels, + text: qsTr('Shortest iconified name with pretty labels') }, + { value: EaGlobals.Vars.ReducedWithIconsAndPrettyLabels, + text: qsTr('Shorter iconified name with pretty labels') }, + { value: EaGlobals.Vars.FullWithIconsAndPrettyLabels, + text: qsTr('Full iconified name with pretty labels') }, + { value: EaGlobals.Vars.FullWithPrettyLabels, + text: qsTr('Full plain text name with pretty labels') }, + { value: EaGlobals.Vars.FullWithLabels, + text: qsTr('Full plain text name with labels') }, + { value: EaGlobals.Vars.FullWithIndices, + text: qsTr('Full plain text name with indices') } + ] + */ + + model: [ + { value: EaGlobals.Vars.ShortestWithIconsAndPrettyLabels, + text: qsTr('Iconified name with pretty labels') }, + { value: EaGlobals.Vars.PlainShortWithLabels, + text: qsTr('Short plain text name with labels') }, + { value: EaGlobals.Vars.PlainFullWithLabels, + text: qsTr('Full plain text name with labels') } + ] + + header: EaComponents.TableViewHeader { + + EaComponents.TableViewLabel { + width: EaStyle.Sizes.fontPixelSize * 2.5 + } + + EaComponents.TableViewLabel { + flexibleWidth: true + } + } + + delegate: EaComponents.TableViewDelegate { + mouseArea.onPressed: EaGlobals.Vars.paramNameFormat = currentIndex + + EaElements.RadioButton { + checked: index === EaGlobals.Vars.paramNameFormat + anchors.verticalCenter: parent.verticalCenter + } + + EaComponents.TableViewTwoRowsAdvancedLabel { + text: tableView.model[index].text + minorText: Globals.Proxies.paramName(param, tableView.model[index].value) + } + } + } + } +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Layout.qml new file mode 100644 index 00000000..1168f404 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Advanced/Layout.qml @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick + +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents + +import "./Groups" as Groups + + +EaComponents.SideBarColumn { + +// Groups.ParamNames {} + + Groups.Calculator {} + + Groups.Minimizer {} +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Groups/Experiments.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Groups/Experiments.qml new file mode 100644 index 00000000..0195cd68 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Groups/Experiments.qml @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick +import QtQuick.Controls +import QtQuick.Dialogs + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents +import EasyApp.Gui.Logic as EaLogic + +import Gui.Globals as Globals + + +EaElements.GroupBox { + collapsible: false + last: true + + EaElements.ComboBox { + id: comboBox + + topInset: 0 + bottomInset: 0 + + width: EaStyle.Sizes.sideBarContentWidth + anchors.bottomMargin: EaStyle.Sizes.fontPixelSize + + textRole: "name" + + model: Globals.BackendWrapper.analysisExperimentsAvailable.length + currentIndex: Globals.BackendWrapper.analysisExperimentsCurrentIndex + onActivated: Globals.BackendWrapper.analysisSetExperimentsCurrentIndex(currentIndex) + + // ComboBox delegate (popup rows) + delegate: ItemDelegate { + id: itemDelegate + + width: parent.width + height: EaStyle.Sizes.tableRowHeight + + highlighted: comboBox.highlightedIndex === index + + // ComboBox delegate (popup rows) contentItem + contentItem: Item { + width: parent.width + height: parent.height + + Row { + height: parent.height + spacing: EaStyle.Sizes.tableColumnSpacing + + EaComponents.TableViewLabel { + text: index + 1 + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewButton { + anchors.verticalCenter: parent.verticalCenter + fontIcon: "microscope" + ToolTip.text: qsTr("Measured pattern color") + backgroundColor: "transparent" + borderColor: "transparent" + iconColor: EaStyle.Colors.chartForegroundsExtra[2] + } + + EaComponents.TableViewParameter { + enabled: false + //text: comboBox.model[index]//.name.value + text: Globals.BackendWrapper.analysisExperimentsAvailable[index] + + } + } + } + // ComboBox delegate (popup rows) contentItem + + // ComboBox delegate (popup rows) background + background: Rectangle { + color: itemDelegate.highlighted ? + EaStyle.Colors.tableHighlight : + index % 2 ? + EaStyle.Colors.themeBackgroundHovered2 : + EaStyle.Colors.themeBackgroundHovered1 + } + // ComboBox delegate (popup rows) background + + } + // ComboBox delegate (popup rows) + + // ComboBox (selected item) contentItem + contentItem: Item { + width: parent.width + height: parent.height + + Row { + height: parent.height + spacing: EaStyle.Sizes.tableColumnSpacing + + EaComponents.TableViewLabel { + text: comboBox.currentIndex + 1 + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewButton { + anchors.verticalCenter: parent.verticalCenter + fontIcon: "microscope" + ToolTip.text: qsTr("Measured pattern color") + backgroundColor: "transparent" + borderColor: "transparent" + iconColor: EaStyle.Colors.chartForegroundsExtra[2] + } + + EaComponents.TableViewParameter { + enabled: false + text: typeof comboBox.model[comboBox.currentIndex] !== 'undefined' ? + comboBox.model[comboBox.currentIndex].name.value : + '' + } + } + } + // ComboBox (selected item) contentItem + } +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Groups/Fittables.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Groups/Fittables.qml new file mode 100644 index 00000000..6ca4d3ef --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Groups/Fittables.qml @@ -0,0 +1,352 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick +import QtQuick.Controls +import QtCharts + +import EasyApp.Gui.Logic as EaLogic +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents + +import Gui.Globals as Globals + +EaElements.GroupBox { + //title: qsTr("Parameters") + collapsible: false + last: true + + Column { + id: fittables + property int selectedParamIndex: Globals.BackendWrapper.analysisCurrentParameterIndex + onSelectedParamIndexChanged: { + updateSliderLimits() + updateSliderValue() + } + + property string selectedColor: EaStyle.Colors.themeForegroundHovered + + spacing: EaStyle.Sizes.fontPixelSize +/* + // Filter parameters widget + Row { + spacing: EaStyle.Sizes.fontPixelSize * 0.5 + + // Filter criteria + EaElements.TextField { + id: filterCriteriaField + + width: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 3 + + placeholderText: qsTr("Filter criteria") + + onTextChanged: { + nameFilterSelector.currentIndex = nameFilterSelector.indexOfValue(text) + Globals.Proxies.main.fittables.nameFilterCriteria = text + } + } + // Filter criteria + + // Filter by name + EaElements.ComboBox { + id: nameFilterSelector + + topInset: 0 + bottomInset: 0 + + width: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 3 + + valueRole: "value" + textRole: "text" + + displayText: currentIndex === -1 ? + qsTr("Filter by name") : + currentText.replace(' ◦ ', '') + + model: [ + { value: "", text: `All names (${Globals.BackendWrapper.analysisModelParametersCount + Globals.BackendWrapper.analysisExperimentParametersCount})` }, + { value: "model", text: `layer-group Model (${Globals.BackendWrapper.analysisModelParametersCount})` }, + { value: "cell", text: `cube Unit cell` }, + { value: "atom_site", text: `atom Atom sites` }, + { value: "fract", text: `map-marker-alt Atomic coordinates` }, + { value: "occupancy", text: `fill Atomic occupancies` }, + { value: "B_iso", text: `arrows-alt Atomic displacement` }, + { value: "experiment", text: `microscope Experiment (${Globals.BackendWrapper.analysisExperimentParametersCount})` }, + { value: "resolution", text: `shapes Peak shape` }, + { value: "asymmetry", text: `balance-scale-left Peak asymmetry` }, + { value: "background", text: `wave-square Background` } + ] + + onActivated: filterCriteriaField.text = currentValue + } + // Filter by name + + // Filter by variability + EaElements.ComboBox { + id: variabilityFilterSelector + + property int lastIndex: -1 + + topInset: 0 + bottomInset: 0 + + width: (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize) / 3 + + displayText: currentIndex === -1 ? qsTr("Filter by variability") : currentText + + valueRole: "value" + textRole: "text" + + model: [ + { value: 'all', text: `All parameters (${Globals.BackendWrapper.analysisFreeParamsCount + + Globals.BackendWrapper.analysisFixedParamsCount})` }, + { value: 'free', text: `Free parameters (${Globals.BackendWrapper.analysisFreeParamsCount})` }, + { value: 'fixed', text: `Fixed parameters (${Globals.BackendWrapper.analysisFixedParamsCount})` } + ] + onModelChanged: currentIndex = lastIndex + + onActivated: { + lastIndex = currentIndex + Globals.Proxies.main.fittables.variabilityFilterCriteria = currentValue + } + } + // Filter by variability + + } + // Filter parameters widget +*/ + // Table + EaComponents.TableView { + id: tableView + defaultInfoText: qsTr("No parameters found") + + maxRowCountShow: 7 + + Math.trunc((applicationWindow.height - EaStyle.Sizes.appWindowMinimumHeight) / + EaStyle.Sizes.tableRowHeight) + // Table model + // We only use the length of the model object defined in backend logic and + // directly access that model in every row using the TableView index property. + model: Globals.BackendWrapper.analysisFitableParameters.length //Globals.Proxies.main_fittables_data.length + // Table model + + Component.onCompleted: { + Globals.BackendWrapper.analysisSetCurrentParameterIndex(0)// fittables.selectedParamIndex = 0 + updateSliderLimits() + updateSliderValue() + } + + // Header row + header: EaComponents.TableViewHeader { + EaComponents.TableViewLabel { + width: EaStyle.Sizes.fontPixelSize * 2.5 + //text: qsTr("No.") + } + + EaComponents.TableViewLabel { + flexibleWidth: true + horizontalAlignment: Text.AlignLeft + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("name") + } + + EaComponents.TableViewLabel { + id: valueLabel + width: EaStyle.Sizes.fontPixelSize * 4.5 + horizontalAlignment: Text.AlignRight + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("value") + } + + EaComponents.TableViewLabel { + width: EaStyle.Sizes.fontPixelSize * 2.0 + horizontalAlignment: Text.AlignLeft + //text: qsTr("units") + } + + EaComponents.TableViewLabel { + width: valueLabel.width + horizontalAlignment: Text.AlignRight + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("error") + } + + EaComponents.TableViewLabel { + width: valueLabel.width + horizontalAlignment: Text.AlignRight + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("min") + } + + EaComponents.TableViewLabel { + width: valueLabel.width + horizontalAlignment: Text.AlignRight + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("max") + } + + EaComponents.TableViewLabel { + width: EaStyle.Sizes.fontPixelSize * 3.0 + color: EaStyle.Colors.themeForegroundMinor + text: qsTr("vary") + } + } + // Header row + + // Table content row + delegate: EaComponents.TableViewDelegate { + enabled: !Globals.BackendWrapper.analysisFittingRunning + + mouseArea.onPressed: { + if (Globals.BackendWrapper.analysisCurrentParameterIndex !== index) { + Globals.BackendWrapper.analysisSetCurrentParameterIndex(index) + updateSliderLimits() + updateSliderValue() + } + } + + EaComponents.TableViewLabel { + text: index + 1 + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewLabel { + width: EaStyle.Sizes.fontPixelSize * 5 + text: Globals.BackendWrapper.analysisFitableParameters[index].name + ToolTip.text: textFormat === Text.PlainText ? text : '' + } + + EaComponents.TableViewParameter { + id: valueColumn + selected: index === Globals.BackendWrapper.analysisCurrentParameterIndex + text: EaLogic.Utils.toDefaultPrecision(Globals.BackendWrapper.analysisFitableParameters[index].value) + onEditingFinished: { + focus = false + console.debug("*** Editing (manual) 'value' field of fittable on Analysis page ***") + Globals.BackendWrapper.analysisSetCurrentParameterValue(text) + updateSliderValue() + updateSliderLimits() + } + } + + EaComponents.TableViewLabel { + text: Globals.BackendWrapper.analysisFitableParameters[index].units + color: EaStyle.Colors.themeForegroundMinor + } + + EaComponents.TableViewLabel { + text: EaLogic.Utils.toDefaultPrecision(Globals.BackendWrapper.analysisFitableParameters[index].error) + } + + EaComponents.TableViewParameter { + minored: true + text: EaLogic.Utils.toDefaultPrecision(Globals.BackendWrapper.analysisFitableParameters[index].min).replace('Infinity', 'inf') + onEditingFinished: { + focus = false + console.debug("*** Editing 'min' field of fittable on Analysis page ***") + const value = (text === '' ? '-inf' : text) + Globals.BackendWrapper.analysisSetCurrentParameterMin(text) + } + } + + EaComponents.TableViewParameter { + minored: true + text: EaLogic.Utils.toDefaultPrecision(Globals.BackendWrapper.analysisFitableParameters[index].max).replace('Infinity', 'inf') + onEditingFinished: { + focus = false + console.debug("*** Editing 'max' field of fittable on Analysis page ***") + const value = (text === '' ? 'inf' : text) + Globals.BackendWrapper.analysisSetCurrentParameterMax(value) + } + } + + EaComponents.TableViewCheckBox { + id: fitColumn + enabled: Globals.BackendWrapper.analysisExperimentsAvailable.length + checked: Globals.BackendWrapper.analysisFitableParameters[index].fit + onToggled: { + console.debug("*** Editing 'fit' field of fittable on Analysis page ***") + Globals.BackendWrapper.analysisSetCurrentParameterFit(checkState) + } + } + } + // Table content row + } + // Table + + // Parameter change slider + Row { + visible: Globals.BackendWrapper.analysisFitableParameters.length + + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.TextField { + readOnly: true + width: EaStyle.Sizes.fontPixelSize * 6 + text: { + const value = Globals.BackendWrapper.analysisFitableParameters[Globals.BackendWrapper.analysisCurrentParameterIndex].value + const error = Globals.BackendWrapper.analysisFitableParameters[Globals.BackendWrapper.analysisCurrentParameterIndex].error + return EaLogic.Utils.toDefaultPrecision(slider.from) + } + } + + EaElements.Slider { + id: slider + + enabled: !Globals.BackendWrapper.analysisFittingRunning + width: tableView.width - EaStyle.Sizes.fontPixelSize * 14 + + stepSize: (to - from) / 20 + snapMode: Slider.SnapAlways + + toolTipText: { + return EaLogic.Utils.toDefaultPrecision(value) + } +// onMoved: { +// console.debug("*** Editing (slider) 'value' field of fittable on Analysis page ***") +// Globals.BackendWrapper.analysisSetCurrentParameterValue(value) +// } + onPressedChanged: { + if (!pressed) { + console.debug("*** Editing (slider) 'value' field of fittable on Analysis page ***") + Globals.BackendWrapper.analysisSetCurrentParameterValue(value) + updateSliderLimits() + } + } + } + + EaElements.TextField { + readOnly: true + width: EaStyle.Sizes.fontPixelSize * 6 + text: { + return EaLogic.Utils.toDefaultPrecision(slider.to) + } + } + } + } + // Logic + + function updateSliderValue() { + const value = Globals.BackendWrapper.analysisFitableParameters[Globals.BackendWrapper.analysisCurrentParameterIndex].value + slider.value = EaLogic.Utils.toDefaultPrecision(value) + } + + function updateSliderLimits() { + var from = Globals.BackendWrapper.analysisFitableParameters[Globals.BackendWrapper.analysisCurrentParameterIndex].value * 0.9 + var to = Globals.BackendWrapper.analysisFitableParameters[Globals.BackendWrapper.analysisCurrentParameterIndex].value * 1.1 + if (from === 0 && to === 0) { + to = 0.1 + } + if (Globals.BackendWrapper.analysisFitableParameters[Globals.BackendWrapper.analysisCurrentParameterIndex].max < to) { + to = Globals.BackendWrapper.analysisFitableParameters[Globals.BackendWrapper.analysisCurrentParameterIndex].max + } + if (Globals.BackendWrapper.analysisFitableParameters[Globals.BackendWrapper.analysisCurrentParameterIndex].min > from) { + from = Globals.BackendWrapper.analysisFitableParameters[Globals.BackendWrapper.analysisCurrentParameterIndex].min + } + slider.from = EaLogic.Utils.toDefaultPrecision(from) + slider.to = EaLogic.Utils.toDefaultPrecision(to) + } +} + diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Groups/Fitting.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Groups/Fitting.qml new file mode 100644 index 00000000..a7f72830 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Groups/Fitting.qml @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick +import QtQuick.Controls + +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements + +import Gui.Globals as Globals + +EaElements.GroupBox { + collapsible: false + + Column { + spacing: EaStyle.Sizes.fontPixelSize + + EaElements.SideBarButton { + enabled: Globals.BackendWrapper.analysisExperimentsAvailable.length + wide: true + fontIcon: Globals.BackendWrapper.analysisFittingRunning ? 'stop-circle' : 'play-circle' + text: Globals.BackendWrapper.analysisFittingRunning ? qsTr('Cancel fitting') : qsTr('Start fitting') + + onClicked: { + console.debug(`Clicking '${text}' button: ${this}`) + Globals.BackendWrapper.analysisFittingStartStop() + } + + Component.onCompleted: Globals.References.pages.analysis.sidebar.basic.popups.startFittingButton = this + Loader { source: "../Popups/FitStatusDialog.qml" } + } + + } +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml new file mode 100644 index 00000000..81b534cf --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Layout.qml @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick + +import EasyApp.Gui.Style 1.0 as EaStyle +import EasyApp.Gui.Elements as EaElements +import EasyApp.Gui.Components as EaComponents + +import "./Groups" as Groups +import Gui.Globals as Globals + + +EaComponents.SideBarColumn { + + Groups.Experiments{} +// enabled: Globals.BackendWrapper.analysisIsFitFinished +// } + +// EaElements.GroupBox { +// collapsible: false +// last: true +// +// Loader { source: 'Experiments.qml' } +// } + + Groups.Fittables{} + +/* EaElements.GroupBox { + //title: qsTr("Parameters") + collapsible: false + last: true + + Loader { source: 'Fittables.qml' } + } +*/ + Groups.Fitting{} + +/* EaElements.GroupBox { + //title: qsTr("Fitting") + collapsible: false + + Loader { source: 'Fitting.qml' } + } +*/ +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Popups/FitStatusDialog.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Popups/FitStatusDialog.qml new file mode 100644 index 00000000..d294c6b6 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Analysis/Sidebar/Basic/Popups/FitStatusDialog.qml @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2023 EasyDiffraction contributors +// SPDX-License-Identifier: BSD-3-Clause +// © 2023 Contributors to the EasyDiffraction project + +import QtQuick +import QtQuick.Controls + +import EasyApp.Gui.Globals as EaGlobals +import EasyApp.Gui.Style as EaStyle +import EasyApp.Gui.Elements as EaElements + +import Gui.Globals as Globals + + +EaElements.Dialog { + id: dialog + + visible: Globals.BackendWrapper.analysisFittingStatus + title: qsTr("Fit status") + standardButtons: Dialog.Ok + + Component.onCompleted: Globals.References.pages.analysis.sidebar.basic.popups.fitStatusDialogOkButton = okButtonRef() + + EaElements.Label { + text: { + if ( Globals.BackendWrapper.analysisFittingStatus === 'Success') { + return 'Optimization finished successfully.' + } else if (Globals.BackendWrapper.analysisFittingStatus === 'Failure') { + return 'Optimization failed.' + } else if (Globals.BackendWrapper.analysisFittingStatus === 'Aborted') { + return 'Optimization aborted.' + } else if (Globals.BackendWrapper.analysisFittingStatus === 'No free params') { + return 'Nothing to vary. Allow some parameters to be free.' + } else { + return '' + } + } + } + + // Logic + + function okButtonRef() { + const buttons = dialog.footer.contentModel.children + for (let i in buttons) { + const button = buttons[i] + if (button.text === 'OK') { + return button + } + } + return null + } +} diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Layout.qml index 67aed043..f56ede8a 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Layout.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Layout.qml @@ -35,7 +35,7 @@ EaComponents.ContentPage { continueButton.onClicked: { console.debug(`Clicking '${continueButton.text}' button ::: ${this}`) Globals.References.applicationWindow.appBarCentralTabs.analysisButton.enabled = true - Globals.References.applicationWindow.appBarCentralTabs.anslysisButton.toggle() + Globals.References.applicationWindow.appBarCentralTabs.analysisButton.toggle() } } diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/ExperimentalData.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/ExperimentalData.qml index 50b55e95..cc6ecbf5 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/ExperimentalData.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/ExperimentalData.qml @@ -16,7 +16,7 @@ EaElements.GroupBox { EaElements.SideBarButton { enabled: true - + wide: true fontIcon: "upload" text: qsTr("Import data from local drive") @@ -29,12 +29,6 @@ EaElements.GroupBox { source: '../Popups/OpenExperimentFile.qml' } } - - EaElements.SideBarButton { - fontIcon: "arrow-circle-right" - text: qsTr("Continue without experiment data") - Component.onCompleted: Globals.Variables.continueWithoutExperimentDataButton = this - } } Component.onCompleted: Globals.Variables.experimentalDataGroup = this diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/InstrumentParameters.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/InstrumentParameters.qml index 7913ca8b..76ba8e36 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/InstrumentParameters.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/InstrumentParameters.qml @@ -10,7 +10,7 @@ import Gui.Globals as Globals EaElements.GroupBox { title: qsTr("Instrumental parameters") visible: Globals.BackendWrapper.experimentExperimentalData - collapsed: true + collapsed: false Row { spacing: EaStyle.Sizes.fontPixelSize diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Layout.qml index 6fef4346..b30d6d99 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Layout.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Layout.qml @@ -11,13 +11,8 @@ EaComponents.SideBarColumn { Groups.ExperimentalData{ enabled: Globals.BackendWrapper.analysisIsFitFinished } - Groups.InstrumentParameters{ enabled: Globals.BackendWrapper.analysisIsFitFinished } - - Groups.QRange{ - enabled: Globals.BackendWrapper.analysisIsFitFinished - } } diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Project/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Project/Layout.qml index 21151199..e4728e03 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Project/Layout.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Project/Layout.qml @@ -34,9 +34,7 @@ EaComponents.ContentPage { Loader { source: 'Sidebar/Basic/Layout.qml' } ] - continueButton.text: Globals.BackendWrapper.projectCreated ? - qsTr('Continue') : - qsTr('Continue without project') + continueButton.text: qsTr('Continue') continueButton.onClicked: { console.debug(`Clicking '${continueButton.text}' button ::: ${this}`) diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Layout.qml index d9aaae3e..5dc96c1d 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Layout.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Layout.qml @@ -30,19 +30,20 @@ EaComponents.ContentPage { sideBar: EaComponents.SideBar { tabs: [ - EaElements.TabButton { text: qsTr('Basic controls') } -// EaElements.TabButton { text: qsTr('Advanced controls') } + EaElements.TabButton { text: qsTr('Basic controls') }, + EaElements.TabButton { text: qsTr('Advanced controls') } ] items: [ - Loader { source: 'Sidebar/Basic/Layout.qml' } - // Loader { source: 'Sidebar/Advanced/Layout.qml' } + Loader { source: 'Sidebar/Basic/Layout.qml' }, + Loader { source: 'Sidebar/Advanced/Layout.qml' } ] continueButton.text: qsTr('Continue') continueButton.onClicked: { console.debug(`Clicking '${continueButton.text}' button ::: ${this}`) + Globals.References.resetActive = true Globals.References.applicationWindow.appBarCentralTabs.experimentButton.enabled = true Globals.References.applicationWindow.appBarCentralTabs.experimentButton.toggle() } diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/MainContent/SampleView.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/MainContent/SampleView.qml index 855fe0e3..cce12f8d 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/MainContent/SampleView.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/MainContent/SampleView.qml @@ -52,16 +52,16 @@ Rectangle { spacing: 0.25 * EaStyle.Sizes.fontPixelSize EaElements.TabButton { - checked: Globals.Variables.showLegendOnExperimentPage + checked: Globals.Variables.showLegendOnSamplePage autoExclusive: false height: EaStyle.Sizes.toolButtonHeight width: EaStyle.Sizes.toolButtonHeight borderColor: EaStyle.Colors.chartAxis fontIcon: "align-left" - ToolTip.text: Globals.Variables.showLegendOnExperimentPage ? + ToolTip.text: Globals.Variables.showLegendOnSamplePage ? qsTr("Hide legend") : qsTr("Show legend") - onClicked: Globals.Variables.showLegendOnExperimentPage = checked + onClicked: Globals.Variables.showLegendOnSamplePage = checked } EaElements.TabButton { @@ -151,6 +151,7 @@ Rectangle { Globals.BackendWrapper.plottingSetQtChartsSerieRef('samplePage', 'sampleSerie', chartView.calcSerie) + Globals.BackendWrapper.plottingRefreshSample() } } diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/MainContent/SldView.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/MainContent/SldView.qml index 84a764a8..7d16f5af 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/MainContent/SldView.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/MainContent/SldView.qml @@ -52,16 +52,16 @@ Rectangle { spacing: 0.25 * EaStyle.Sizes.fontPixelSize EaElements.TabButton { - checked: Globals.Variables.showLegendOnExperimentPage + checked: Globals.Variables.showLegendOnSamplePage autoExclusive: false height: EaStyle.Sizes.toolButtonHeight width: EaStyle.Sizes.toolButtonHeight borderColor: EaStyle.Colors.chartAxis fontIcon: "align-left" - ToolTip.text: Globals.Variables.showLegendOnExperimentPage ? + ToolTip.text: Globals.Variables.showLegendOnSamplePage ? qsTr("Hide legend") : qsTr("Show legend") - onClicked: Globals.Variables.showLegendOnExperimentPage = checked + onClicked: Globals.Variables.showLegendOnSamplePage = checked } EaElements.TabButton { @@ -151,8 +151,8 @@ Rectangle { Globals.BackendWrapper.plottingSetQtChartsSerieRef('samplePage', 'sldSerie', chartView.calcSerie) + Globals.BackendWrapper.plottingRefreshSLD() } - } // Logic diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Groups/Constraints.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Groups/Constraints.qml new file mode 100644 index 00000000..d49ce999 --- /dev/null +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Groups/Constraints.qml @@ -0,0 +1,115 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQml.XmlListModel + +import easyApp.Gui.Style as EaStyle +import easyApp.Gui.Elements as EaElements +import easyApp.Gui.Components as EaComponents +import easyApp.Gui.Logic as EaLogic + +import Gui.Globals as Globals + +EaElements.GroupBox { + title: qsTr("Sample constraints") + enabled: true + last: false + + Column { + spacing: EaStyle.Sizes.fontPixelSize * 0.5 + Column { + + EaElements.Label { + enabled: false + text: qsTr("Numeric or Parameter-Parameter constraint") + } + + Grid { + columns: 4 + columnSpacing: EaStyle.Sizes.fontPixelSize * 0.5 + rowSpacing: EaStyle.Sizes.fontPixelSize * 0.5 + verticalItemAlignment: Grid.AlignVCenter + + EaElements.ComboBox { + id: dependentPar + width: 359 + currentIndex: -1 + displayText: currentIndex === -1 ? "Select dependent parameter" : currentText + model: Globals.BackendWrapper.sampleParameterNames + onCurrentIndexChanged: { + independentPar.model = Globals.BackendWrapper.sampleParameterNames + independentPar.model[currentIndex] = 'Dependent parameter' + independentPar.currentIndex = -1 + } + } + + EaElements.ComboBox { + id: relationalOperator + width: 47 + currentIndex: 0 + font.family: EaStyle.Fonts.iconsFamily + model: Globals.BackendWrapper.sampleRelationOperators + } + + Item { height: 1; width: 1 } + Item { height: 1; width: 1 } + + EaElements.ComboBox { + id: independentPar + width: dependentPar.width + currentIndex: -1 + displayText: currentIndex === -1 ? "Numeric constrain or select independent parameter" : currentText + model: Globals.BackendWrapper.sampleParameterNames + onCurrentIndexChanged: { + dependentPar.model = Globals.BackendWrapper.sampleParameterNames + if (currentIndex === -1){ + displayText: "Numeric constrain or select independent parameter" + arithmeticOperator.model = Globals.BackendWrapper.sampleArithmicOperators.slice(0,1) // no arithmetic operators + } + else{ + arithmeticOperator.model = Globals.BackendWrapper.sampleArithmicOperators.slice(1) // allow all arithmetic operators + dependentPar.model[currentIndex] = 'Independent parameter' + //arithmeticOperator.currentIndex = 0 + } + } + } + + EaElements.ComboBox { + id: arithmeticOperator + width: relationalOperator.width + currentIndex: 0 + font.family: EaStyle.Fonts.iconsFamily + model: arithmeticOperator.model = Globals.BackendWrapper.sampleArithmicOperators.slice(0,1) + } + + EaElements.TextField { + id: value + width: 65 + horizontalAlignment: Text.AlignRight + text: "1.0000" + } + + EaElements.SideBarButton { + id: addConstraint + enabled: ( dependentPar.currentIndex !== -1 && independentPar.currentIndex !== -1 && independentPar.currentIndex !== dependentPar.currentIndex ) + width: 35 + fontIcon: "plus-circle" + ToolTip.text: qsTr("Add constraint between two parameters") + onClicked: { + Globals.BackendWrapper.sampleAddConstraint( + dependentPar.currentIndex, + relationalOperator.currentText.replace("\uf52c", "=").replace("\uf531", ">").replace("\uf536", "<"), + value.text, + arithmeticOperator.currentText.replace("\uf00d", "*").replace("\uf529", "/").replace("\uf067", "+").replace("\uf068", "-"), + independentPar.currentIndex + ) + independentPar.currentIndex = -1 + dependentPar.currentIndex = -1 + relationalOperator.currentIndex = 0 + arithmeticOperator.currentIndex = 0 + } + } + } + } + } +} + diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/QRange.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Groups/QRange.qml similarity index 64% rename from src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/QRange.qml rename to src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Groups/QRange.qml index 93bbf287..0aede6fe 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Experiment/Sidebar/Basic/Groups/QRange.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Groups/QRange.qml @@ -4,13 +4,12 @@ import QtQuick.Controls 2.13 import easyApp.Gui.Style as EaStyle import easyApp.Gui.Elements as EaElements import easyApp.Gui.Components as EaComponents -import easyApp.Gui.Logic as EaLogic import Gui.Globals as Globals EaElements.GroupBox { title: qsTr("Q range of interest") - visible: Globals.BackendWrapper.experimentExperimentalData + collapsed: false Row { spacing: EaStyle.Sizes.fontPixelSize * 0.5 @@ -20,11 +19,10 @@ EaElements.GroupBox { width: labelWidth() } EaElements.Parameter { - enabled: Globals.BackendWrapper.experimentExperimentalData width: textFieldWidth() units: "Å-1" - text: Globals.BackendWrapper.experimentQMin.toFixed(3) - onEditingFinished: Globals.BackendWrapper.experimentSetQMin(text) + text: Globals.BackendWrapper.sampleQMin.toFixed(3) + onEditingFinished: Globals.BackendWrapper.sampleSetQMin(text) } EaComponents.TableViewLabel{ @@ -33,11 +31,10 @@ EaElements.GroupBox { width: labelWidth() } EaElements.Parameter { - enabled: Globals.BackendWrapper.experimentExperimentalData width: textFieldWidth() units: "Å-1" - text: Globals.BackendWrapper.experimentQMax.toFixed(3) - onEditingFinished: Globals.BackendWrapper.experimentSetQMax(text) + text: Globals.BackendWrapper.sampleQMax.toFixed(3) + onEditingFinished: Globals.BackendWrapper.sampleSetQMax(text) } EaComponents.TableViewLabel{ @@ -46,10 +43,9 @@ EaElements.GroupBox { width: labelWidth() } EaElements.Parameter { - enabled: Globals.BackendWrapper.experimentExperimentalData width: textFieldWidth() - text: Globals.BackendWrapper.experimentQResolution - onEditingFinished: Globals.BackendWrapper.experimentSetQElements(text) + text: Globals.BackendWrapper.sampleQResolution + onEditingFinished: Globals.BackendWrapper.sampleSetQElements(text) } } diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Layout.qml index 0fbbee29..5824765a 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Layout.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Layout.qml @@ -1,16 +1,21 @@ -import QtQuick 2.13 -import QtQuick.Controls 2.13 -import QtQML.XmlListModel 2.13 +import QtQuick 2.14 +import QtQuick.Controls 2.14 +//import QtQml.XmlListModel -import easyApp.Gui.Style 1.0 as EaStyle -import easyApp.Gui.Elements 1.0 as EaElements +//import easyApp.Gui.Style 1.0 as EaStyle import easyApp.Gui.Components 1.0 as EaComponents -import Gui.Globals 1.0 as ExGlobals -import Gui.Components 1.0 as ExComponents +import Gui.Globals as Globals +import "./Groups" as Groups EaComponents.SideBarColumn { - + Groups.QRange{ + enabled: Globals.BackendWrapper.analysisIsFitFinished + } + Groups.Constraints{ + enabled: Globals.BackendWrapper.analysisIsFitFinished + } +/* property int independentParCurrentIndex: 0 property int dependentParCurrentIndex: 0 property int dependentParCurrentIndex2: 0 @@ -271,4 +276,5 @@ EaComponents.SideBarColumn { onToggled: ExGlobals.Constants.proxy.simulation.setPlotRQ4() } } + */ } diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/Assemblies/MultiLayer.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/Assemblies/MultiLayer.qml index e4d81e2a..213ab528 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/Assemblies/MultiLayer.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/Assemblies/MultiLayer.qml @@ -94,10 +94,12 @@ EaElements.GroupColumn { enabled: layersView !== null && layersView.model > 1 onClicked: Globals.BackendWrapper.sampleRemoveLayer(index) } - } - onCurrentIndexChanged: { - Globals.BackendWrapper.sampleSetCurrentLayerIndex(layersView.currentIndex) + mouseArea.onPressed: { + if (Globals.BackendWrapper.sampleCurrentLayerIndex !== index) { + Globals.BackendWrapper.sampleSetCurrentLayerIndex(index) + } + } } } // Control buttons below table @@ -114,7 +116,7 @@ EaElements.GroupColumn { EaElements.SideBarButton { width: (EaStyle.Sizes.sideBarContentWidth - (2 * (EaStyle.Sizes.tableRowHeight + EaStyle.Sizes.fontPixelSize)) - EaStyle.Sizes.fontPixelSize) / 2 - enabled: (layersView.currentIndex > 0) ? true : false //when item is selected + enabled: Globals.BackendWrapper.sampleLayers.length//(layersView.currentIndex > 0) ? true : false //when item is selected fontIcon: "clone" text: qsTr("Duplicate layer") onClicked: Globals.BackendWrapper.sampleDuplicateSelectedLayer() @@ -122,7 +124,7 @@ EaElements.GroupColumn { EaElements.SideBarButton { width: EaStyle.Sizes.tableRowHeight - enabled: (layersView.currentIndex !== 0 && Globals.BackendWrapper.sampleLayers.length > 0 ) ? true : false//When item is selected + enabled: (Globals.BackendWrapper.sampleCurrentLayerIndex !== 0 && Globals.BackendWrapper.sampleLayers.length > 0 ) ? true : false//When item is selected fontIcon: "arrow-up" ToolTip.text: qsTr("Move layer up") onClicked: Globals.BackendWrapper.sampleMoveSelectedLayerUp() @@ -130,7 +132,7 @@ EaElements.GroupColumn { EaElements.SideBarButton { width: EaStyle.Sizes.tableRowHeight - enabled: (layersView.currentIndex + 1 !== Globals.BackendWrapper.sampleLayers.length && Globals.BackendWrapper.sampleLayers.length > 0 ) ? true : false + enabled: (Globals.BackendWrapper.sampleCurrentLayerIndex + 1 !== Globals.BackendWrapper.sampleLayers.length && Globals.BackendWrapper.sampleLayers.length > 0 ) ? true : false fontIcon: "arrow-down" ToolTip.text: qsTr("Move layer down") onClicked: Globals.BackendWrapper.sampleMoveSelectedLayerDown() diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/Assemblies/SurfactantLayer.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/Assemblies/SurfactantLayer.qml index b1b66bc4..dd33ea8d 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/Assemblies/SurfactantLayer.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/Assemblies/SurfactantLayer.qml @@ -107,10 +107,11 @@ EaElements.GroupColumn { currentIndex = indexOfValue(Globals.BackendWrapper.sampleLayers[index].solvent) } } - } - - onCurrentIndexChanged: { - Globals.BackendWrapper.sampleSetCurrentLayerIndex(surfactantView.currentIndex) + mouseArea.onPressed: { + if (Globals.BackendWrapper.samplCurrentLayerIndex !== index) { + Globals.BackendWrapper.sampleSetCurrentLayerIndex(index) + } + } } } Row { diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/MaterialEditor.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/MaterialEditor.qml index c3e538b2..b00b3b60 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/MaterialEditor.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/MaterialEditor.qml @@ -80,11 +80,14 @@ EaElements.GroupBox { enabled: materialsView !== null && materialsView.model > 1 fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this material") - onClicked: Globals.BackendWrapper.sampleRemoveMaterial(materialsView.currentIndex) + onClicked: Globals.BackendWrapper.sampleRemoveMaterial(index) + } + + mouseArea.onPressed: { + if (Globals.BackendWrapper.sampleCurrentMaterialIndex !== index) { + Globals.BackendWrapper.sampleSetCurrentMaterialIndex(index) + } } - } - onCurrentIndexChanged: { - Globals.BackendWrapper.sampleSetCurrentMaterialIndex(materialsView.currentIndex) } } @@ -101,7 +104,7 @@ EaElements.GroupBox { } EaElements.SideBarButton { - enabled: (materialsView.currentIndex > 0) ? true : false //When material is selected + enabled: Globals.BackendWrapper.sampleMaterials.length// (Globals.BackendWrapper.sampleCurrentMaterialIndex > 0) ? true : false //When material is selected width: (EaStyle.Sizes.sideBarContentWidth - (2 * (EaStyle.Sizes.tableRowHeight + EaStyle.Sizes.fontPixelSize)) - EaStyle.Sizes.fontPixelSize) / 2 fontIcon: "clone" text: qsTr("Duplicate material") @@ -109,7 +112,7 @@ EaElements.GroupBox { } EaElements.SideBarButton { - enabled: (materialsView.currentIndex !== 0 && Globals.BackendWrapper.sampleMaterials.length > 0) ? true : false//When item is selected + enabled: (Globals.BackendWrapper.sampleCurrentMaterialIndex !== 0 && Globals.BackendWrapper.sampleMaterials.length > 0) ? true : false//When item is selected width: EaStyle.Sizes.tableRowHeight fontIcon: "arrow-up" ToolTip.text: qsTr("Move material up") @@ -117,7 +120,7 @@ EaElements.GroupBox { } EaElements.SideBarButton { - enabled: (materialsView.currentIndex + 1 !== Globals.BackendWrapper.sampleMaterials.length && Globals.BackendWrapper.sampleMaterials.length > 0) ? true : false//When item is selected + enabled: (Globals.BackendWrapper.sampleCurrentMaterialIndex + 1 !== Globals.BackendWrapper.sampleMaterials.length && Globals.BackendWrapper.sampleMaterials.length > 0) ? true : false//When item is selected width: EaStyle.Sizes.tableRowHeight fontIcon: "arrow-down" ToolTip.text: qsTr("Move material down") diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/ModelEditor.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/ModelEditor.qml index 7fe4f57a..06abb823 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/ModelEditor.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/ModelEditor.qml @@ -79,16 +79,17 @@ EaElements.GroupBox { fontIcon: "minus-circle" ToolTip.text: qsTr("Remove this assembly") enabled: assembliesView.model > 1 - onClicked: Globals.BackendWrapper.sampleRemoveAssembly(assembliesView.currentIndex) + onClicked: Globals.BackendWrapper.sampleRemoveAssembly(index) } - } + mouseArea.onPressed: { + if (Globals.BackendWrapper.sampleCurrentAssemblyIndex !== index) { + Globals.BackendWrapper.sampleSetCurrentAssemblyIndex(index) + } + } - onCurrentIndexChanged: { - Globals.BackendWrapper.sampleSetCurrentAssemblyIndex(assembliesView.currentIndex) } - - onModelChanged: currentIndex = 0 +// onModelChanged: Globals.BackendWrapper.sampleSetCurrentAssemblyIndex(0) } // Control buttons below table @@ -104,7 +105,7 @@ EaElements.GroupBox { } EaElements.SideBarButton { - enabled: (assembliesView.currentIndex > 0) ? true : false//When item is selected + enabled: Globals.BackendWrapper.sampleAssemblies.length //(assembliesView.currentIndex > 0) ? true : false//When item is selected width: (EaStyle.Sizes.sideBarContentWidth - (2 * (EaStyle.Sizes.tableRowHeight + EaStyle.Sizes.fontPixelSize)) - EaStyle.Sizes.fontPixelSize) / 2 fontIcon: "clone" text: qsTr("Duplicate assembly") @@ -112,7 +113,7 @@ EaElements.GroupBox { } EaElements.SideBarButton { - enabled: (assembliesView.currentIndex !== 0 && Globals.BackendWrapper.sampleAssemblies.length > 0 ) ? true : false//When item is selected + enabled: (Globals.BackendWrapper.sampleCurrentAssemblyIndex !== 0 && Globals.BackendWrapper.sampleAssemblies.length > 0 ) ? true : false//When item is selected width: EaStyle.Sizes.tableRowHeight fontIcon: "arrow-up" ToolTip.text: qsTr("Move assembly up") @@ -120,7 +121,7 @@ EaElements.GroupBox { } EaElements.SideBarButton { - enabled: (assembliesView.currentIndex + 1 !== Globals.BackendWrapper.sampleAssemblies.length && Globals.BackendWrapper.sampleAssemblies.length > 0 ) ? true : false//When item is selected + enabled: (Globals.BackendWrapper.sampleCurrentAssemblyIndex + 1 !== Globals.BackendWrapper.sampleAssemblies.length && Globals.BackendWrapper.sampleAssemblies.length > 0 ) ? true : false//When item is selected width: EaStyle.Sizes.tableRowHeight fontIcon: "arrow-down" ToolTip.text: qsTr("Move assembly down") diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/ModelSelector.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/ModelSelector.qml index fe0f0e43..490e1774 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/ModelSelector.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Groups/ModelSelector.qml @@ -62,16 +62,15 @@ EaElements.GroupBox { enabled: (modelView.model > 1) ? true : false//When item is selected ToolTip.text: qsTr("Remove this model") onClicked: { - Globals.BackendWrapper.sampleRemoveModel(modelView.currentIndex) + Globals.BackendWrapper.sampleRemoveModel(index) } } - } - onCurrentIndexChanged: { - Globals.BackendWrapper.sampleSetCurrentModelIndex(modelView.currentIndex) - // NEED TO CONNECT THIS - // In ModelEditor assembliesView.currentIndex = 0 - // In MultiLayer layersView.currentIndex = 0 + mouseArea.onPressed: { + if (Globals.BackendWrapper.sampleCurrentModelIndex !== index) { + Globals.BackendWrapper.sampleSetCurrentModelIndex(index) + } + } } } diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Layout.qml index cec36979..b1ba3525 100644 --- a/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Layout.qml +++ b/src_qt6/EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Basic/Layout.qml @@ -1,42 +1,24 @@ import QtQuick 2.14 import QtQuick.Controls 2.14 -import QtQml.XmlListModel -import easyApp.Gui.Style 1.0 as EaStyle import easyApp.Gui.Components 1.0 as EaComponents import Gui.Globals as Globals import "./Groups" as Groups - EaComponents.SideBarColumn { Groups.MaterialEditor{ enabled: Globals.BackendWrapper.analysisIsFitFinished } - Groups.ModelSelector{ enabled: Globals.BackendWrapper.analysisIsFitFinished } - Groups.ModelEditor { id: modelEditor enabled: Globals.BackendWrapper.analysisIsFitFinished } - Groups.AssemblyEditor{ id: assemblyEditor enabled: Globals.BackendWrapper.analysisIsFitFinished } - - - // Logic - - function labelWidth() { - return (EaStyle.Sizes.sideBarContentWidth - EaStyle.Sizes.fontPixelSize * 2.5 - textFieldWidth() * 3) / 3 - } - - function textFieldWidth() { - return EaStyle.Sizes.fontPixelSize * 7.0 - } - } diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Report/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Layout.qml similarity index 100% rename from src_qt6/EasyReflectometryApp/Gui/Pages/Report/Layout.qml rename to src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Layout.qml diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Report/MainContent/Summary.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Summary/MainContent/Summary.qml similarity index 100% rename from src_qt6/EasyReflectometryApp/Gui/Pages/Report/MainContent/Summary.qml rename to src_qt6/EasyReflectometryApp/Gui/Pages/Summary/MainContent/Summary.qml diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Report/Sidebar/Basic/Groups/Export.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Sidebar/Basic/Groups/Export.qml similarity index 100% rename from src_qt6/EasyReflectometryApp/Gui/Pages/Report/Sidebar/Basic/Groups/Export.qml rename to src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Sidebar/Basic/Groups/Export.qml diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Report/Sidebar/Basic/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Sidebar/Basic/Layout.qml similarity index 100% rename from src_qt6/EasyReflectometryApp/Gui/Pages/Report/Sidebar/Basic/Layout.qml rename to src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Sidebar/Basic/Layout.qml diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Report/Sidebar/Extra/Groups/Empty.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Sidebar/Extra/Groups/Empty.qml similarity index 100% rename from src_qt6/EasyReflectometryApp/Gui/Pages/Report/Sidebar/Extra/Groups/Empty.qml rename to src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Sidebar/Extra/Groups/Empty.qml diff --git a/src_qt6/EasyReflectometryApp/Gui/Pages/Report/Sidebar/Extra/Layout.qml b/src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Sidebar/Extra/Layout.qml similarity index 100% rename from src_qt6/EasyReflectometryApp/Gui/Pages/Report/Sidebar/Extra/Layout.qml rename to src_qt6/EasyReflectometryApp/Gui/Pages/Summary/Sidebar/Extra/Layout.qml diff --git a/src_qt6/pyproject.toml b/src_qt6/pyproject.toml index f6bcb5e5..c0e17f84 100644 --- a/src_qt6/pyproject.toml +++ b/src_qt6/pyproject.toml @@ -30,10 +30,12 @@ classifiers = [ requires-python = '>=3.11' dependencies = [ 'EasyApp @ git+https://github.com/EasyScience/EasyApp.git@develop', - 'easyreflectometry @ git+https://github.com/EasyScience/EasyReflectometryLib.git@197-add-experiment-data-to-project-in-order-for-this-to-be-exposed-to-the-app', + 'easyreflectometry @ git+https://github.com/EasyScience/EasyReflectometryLib.git@199-make-fitter-a-part-of-project', 'PySide6==6.6', 'toml', ] +# pip install easyscience@git+https://github.com/EasyScience/EasyScience.git@81-to-increase-usability-of-minimizer-it-should-be-possible-to-set-the-tolerance-and-the-max-number-of-iteration + [project.urls] homepage = 'https://easyreflectometry.org'