Skip to content

Commit 21625c5

Browse files
committed
attempt at making layer names consistent
1 parent f6bc5e7 commit 21625c5

5 files changed

Lines changed: 134 additions & 54 deletions

File tree

EasyReflectometryApp/Backends/Py/logic/layers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ def add_new(self) -> None:
6060
index_si = [material.name for material in self._project_lib._materials].index('Si')
6161
self._layers.add_layer()
6262
self._layers[-1].material = self._project_lib._materials[index_si]
63+
# Set layer name based on material name
64+
self._layers[-1].name = self._project_lib._materials[index_si].name + ' Layer'
6365

6466
def duplicate_selected(self) -> None:
6567
self._layers.duplicate_layer(self.index)
@@ -95,6 +97,8 @@ def set_roughness_at_current_index(self, new_value: float) -> bool:
9597
def set_material_at_current_index(self, new_value: int) -> bool:
9698
if self._layers[self.index].material != self._project_lib._materials[new_value]:
9799
self._layers[self.index].material = self._project_lib._materials[new_value]
100+
# Update layer name based on material name
101+
self._layers[self.index].name = self._project_lib._materials[new_value].name + ' Layer'
98102
return True
99103
return False
100104

EasyReflectometryApp/Backends/Py/logic/models.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,33 +77,40 @@ def remove_at_index(self, value: str) -> None:
7777
def default_model_content(self, model: Model) -> None:
7878
"""Set the default content for a model."""
7979
model.add_assemblies()
80-
model.sample.data[0].layers.data[0].material = self._project_lib._materials[
81-
self._project_lib.get_index_air()
82-
]
80+
# Superphase (Air layer)
81+
air_material = self._project_lib._materials[self._project_lib.get_index_air()]
82+
model.sample.data[0].layers.data[0].material = air_material
8383
model.sample.data[0].layers.data[0].thickness = 0.0
8484
model.sample.data[0].layers.data[0].roughness = 0.0
85+
model.sample.data[0].layers.data[0].name = air_material.name + ' Layer'
8586
model.sample.data[0].name = 'Superphase'
8687

87-
model.sample.data[1].layers.data[0].material = self._project_lib._materials[
88-
self._project_lib.get_index_sio2()
89-
]
88+
# Middle layer (SiO2)
89+
sio2_material = self._project_lib._materials[self._project_lib.get_index_sio2()]
90+
model.sample.data[1].layers.data[0].material = sio2_material
9091
model.sample.data[1].layers.data[0].thickness = 100.0
9192
model.sample.data[1].layers.data[0].roughness = 3.0
93+
model.sample.data[1].layers.data[0].name = sio2_material.name + ' Layer'
9294
model.sample.data[1].name = 'SiO2'
9395

94-
model.sample.data[2].layers.data[0].material = self._project_lib._materials[
95-
self._project_lib.get_index_si()
96-
]
96+
# Subphase (Si substrate)
97+
si_material = self._project_lib._materials[self._project_lib.get_index_si()]
98+
model.sample.data[2].layers.data[0].material = si_material
9799
model.sample.data[2].name = 'Substrate'
100+
model.sample.data[2].layers.data[0].name = si_material.name + ' Layer'
98101
model.sample.data[2].layers.data[0].thickness = 0.0
99102
model.sample.data[2].layers.data[0].roughness = 1.2
100103

101104
def add_new(self) -> None:
102105
self._models.add_model()
103106
self.default_model_content(self._models[-1])
107+
# Update index to point to the new model
108+
self.index = len(self._models) - 1
104109

105110
def duplicate_selected_model(self) -> None:
106111
self._models.duplicate_model(self.index)
112+
# Update index to point to the duplicated model
113+
self.index = len(self._models) - 1
107114

108115
def move_selected_up(self) -> None:
109116
if self.index > 0:

EasyReflectometryApp/Backends/Py/logic/parameters.py

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ def as_status_string(self) -> str:
2222
return f'{self.count_free_parameters() + self.count_fixed_parameters()} ({self.count_free_parameters()} free, {self.count_fixed_parameters()} fixed)' # noqa: E501
2323

2424
@property
25-
def parameters(self) -> List[str]:
25+
def parameters(self) -> list[dict[str, Any]]:
2626
return _from_parameters_to_list_of_dicts(
27-
self._project_lib.parameters, self._project_lib._models[self._project_lib.current_model_index].unique_name
27+
self._project_lib.parameters, self._project_lib._models
2828
)
2929

3030
def constraint_context(self) -> list[dict[str, Any]]:
@@ -160,10 +160,18 @@ def add_constraint(
160160

161161
print(f'{dependent_idx}, {relational_operator}, {value}, {arithmetic_operator}, {independent_idx}')
162162

163-
def _from_parameters_to_list_of_dicts(parameters: List[Parameter], model_unique_name: str) -> list[dict[str, Any]]:
164-
"""Convert parameters to list of dictionaries with simplified logic."""
163+
def _from_parameters_to_list_of_dicts(parameters: List[Parameter], models) -> list[dict[str, Any]]:
164+
"""Convert parameters to list of dictionaries with simplified logic.
165+
166+
Layer parameters (thickness, roughness) are prefixed with model identifier (e.g., M1, M2).
167+
Material parameters and model parameters (scale, background) are not prefixed to avoid duplication.
168+
"""
165169

166170
alias_registry: set[str] = set()
171+
processed_unique_names: set[str] = set() # Track processed parameters to avoid duplicates
172+
173+
# Layer parameter names that need model prefix
174+
LAYER_PARAMS = {'thickness', 'roughness'}
167175

168176
def _make_alias(name: str) -> str:
169177
base = re.sub(r'[^0-9A-Za-z]+', '_', name).strip('_').lower()
@@ -179,7 +187,7 @@ def _make_alias(name: str) -> str:
179187
alias_registry.add(alias)
180188
return alias
181189

182-
def _get_parameter_display_data(param: Parameter) -> Tuple[str, str]:
190+
def _get_parameter_display_data(param: Parameter, model_unique_name: str) -> Tuple[str, str]:
183191
"""Extract display name and group from parameter path."""
184192
path = global_object.map.find_path(model_unique_name, param.unique_name)
185193
if len(path) >= 2:
@@ -188,7 +196,7 @@ def _get_parameter_display_data(param: Parameter) -> Tuple[str, str]:
188196
return f'{parent_name} {param_name}', parent_name
189197
return param.name, '' # Fallback to parameter name without group
190198

191-
def _get_dependency_expression(param: Parameter) -> str:
199+
def _get_dependency_expression(param: Parameter, model_unique_name: str) -> str:
192200
"""Get simplified dependency expression."""
193201
if param.independent:
194202
return ''
@@ -197,38 +205,62 @@ def _get_dependency_expression(param: Parameter) -> str:
197205
if hasattr(param, 'dependency_map') and 'a' in param.dependency_map:
198206
dependent_param = param.dependency_map['a']
199207
if isinstance(dependent_param, Parameter):
200-
dep_name, _ = _get_parameter_display_data(dependent_param)
208+
dep_name, _ = _get_parameter_display_data(dependent_param, model_unique_name)
201209
else:
202210
dep_name = str(dependent_param)
203211
return param.dependency_expression.replace('a', dep_name)
204212

205213
# Simple numerical dependency
206214
return f'= {param.value}'
207215

216+
def _is_layer_parameter(param: Parameter) -> bool:
217+
"""Check if parameter is a layer parameter (thickness or roughness)."""
218+
return param.name.lower() in LAYER_PARAMS
219+
208220
parameter_list = []
209-
for parameter in parameters:
210-
# Skip parameters not in the current model path
211-
if not global_object.map.find_path(model_unique_name, parameter.unique_name):
212-
continue
213-
214-
display_name, group_name = _get_parameter_display_data(parameter)
215-
alias = _make_alias(display_name or parameter.name)
216-
parameter_list.append({
217-
'name': display_name,
218-
'display_name': display_name,
219-
'group': group_name,
220-
'alias': alias,
221-
'unique_name': parameter.unique_name,
222-
'value': float(parameter.value),
223-
'error': float(parameter.variance),
224-
'max': float(parameter.max),
225-
'min': float(parameter.min),
226-
'units': parameter.unit,
227-
'fit': parameter.free,
228-
'independent': parameter.independent,
229-
'dependency': _get_dependency_expression(parameter),
230-
'enabled': parameter.enabled if hasattr(parameter, 'enabled') else True,
231-
'object': parameter, # Direct reference to the Parameter object
232-
})
221+
222+
# Process parameters for each model
223+
for model_idx, model in enumerate(models):
224+
model_unique_name = model.unique_name
225+
model_prefix = f'M{model_idx + 1}'
226+
227+
for parameter in parameters:
228+
# Skip parameters not in this model's path
229+
if not global_object.map.find_path(model_unique_name, parameter.unique_name):
230+
continue
231+
232+
# For non-layer parameters, skip if already processed (they're shared across models)
233+
is_layer_param = _is_layer_parameter(parameter)
234+
if not is_layer_param:
235+
if parameter.unique_name in processed_unique_names:
236+
continue
237+
processed_unique_names.add(parameter.unique_name)
238+
239+
display_name, group_name = _get_parameter_display_data(parameter, model_unique_name)
240+
241+
# Add model prefix only to layer parameters (thickness, roughness)
242+
if is_layer_param:
243+
prefixed_display_name = f'{model_prefix} {display_name}'
244+
else:
245+
prefixed_display_name = display_name
246+
247+
alias = _make_alias(prefixed_display_name or parameter.name)
248+
parameter_list.append({
249+
'name': prefixed_display_name,
250+
'display_name': prefixed_display_name,
251+
'group': group_name,
252+
'alias': alias,
253+
'unique_name': parameter.unique_name,
254+
'value': float(parameter.value),
255+
'error': float(parameter.variance),
256+
'max': float(parameter.max),
257+
'min': float(parameter.min),
258+
'units': parameter.unit,
259+
'fit': parameter.free,
260+
'independent': parameter.independent,
261+
'dependency': _get_dependency_expression(parameter, model_unique_name),
262+
'enabled': parameter.enabled if hasattr(parameter, 'enabled') else True,
263+
'object': parameter, # Direct reference to the Parameter object
264+
})
233265

234266
return parameter_list

EasyReflectometryApp/Backends/Py/sample.py

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ def addNewModel(self) -> None:
213213
@Slot()
214214
def duplicateSelectedModel(self) -> None:
215215
self._models_logic.duplicate_selected_model()
216+
self._project_logic._update_enablement_of_fixed_layers_for_model(self._models_logic.index)
216217
self.modelsTableChanged.emit()
217218

218219
@Slot()
@@ -764,8 +765,11 @@ def constraintsList(self) -> list[dict[str, str]]:
764765
expression_display = state.get('pretty_expression', self._format_numeric(float(value)))
765766
raw_expression = state.get('raw_expression', expression_display)
766767

768+
# Use model-prefixed display name if available (from constrainModelsParameters)
769+
dependent_display = state.get('dependent_display', entry['display_name'])
770+
767771
constraints.append({
768-
'dependentName': entry['display_name'],
772+
'dependentName': dependent_display,
769773
'expression': expression_display,
770774
'rawExpression': raw_expression,
771775
'relation': relation,
@@ -805,11 +809,34 @@ def removeConstraintByIndex(self, index: int) -> None:
805809
self.layersChange.emit()
806810

807811
def _find_parameter_object_by_name(self, param_name: str):
808-
"""Find parameter object by name."""
812+
"""Find parameter object by name.
813+
814+
Handles both regular names ('SiO2 sld') and model-prefixed names ('M2 SiO2 sld').
815+
"""
809816
parameters = self._parameters_logic.parameters
817+
818+
# Direct match by display name
810819
for param in parameters:
811820
if param['name'] == param_name:
812821
return param['object']
822+
823+
# Check constraint states for model-prefixed dependent_display
824+
for unique_name, state in self._constraint_states.items():
825+
if state.get('dependent_display') == param_name:
826+
# Find the parameter by unique_name
827+
for param in parameters:
828+
if param.get('unique_name') == unique_name:
829+
return param['object']
830+
831+
# Try stripping model prefix (e.g., 'M2 SiO2 sld' -> 'SiO2 sld')
832+
import re
833+
prefix_match = re.match(r'^M\d+\s+(.+)$', param_name)
834+
if prefix_match:
835+
stripped_name = prefix_match.group(1)
836+
for param in parameters:
837+
if param['name'] == stripped_name:
838+
return param['object']
839+
813840
return None
814841

815842
def _make_parameter_independent(self, param_obj) -> None:
@@ -965,9 +992,10 @@ def constrainModelsParameters(self, model_indices: list) -> None:
965992
# Store constraint state for display
966993
unique_name = getattr(dependent_param, 'unique_name', None)
967994
if unique_name is not None:
968-
# Get display name for the reference parameter
969-
_, _, display_lookup = self._build_constraint_context()
970-
ref_display = self._get_parameter_display_name(reference_param)
995+
# Get display names with model prefix for clarity
996+
# e.g., "M2 SiO2 sld = M1 SiO2 sld"
997+
ref_display = self._get_parameter_display_name(reference_param, reference_model_idx)
998+
dep_display = self._get_parameter_display_name(dependent_param, model_idx)
971999

9721000
self._constraint_states[unique_name] = {
9731001
'mode': 'dynamic',
@@ -977,6 +1005,7 @@ def constrainModelsParameters(self, model_indices: list) -> None:
9771005
'raw_expression': 'a',
9781006
'pretty_expression': ref_display,
9791007
'dependency_map': {'a': reference_param},
1008+
'dependent_display': dep_display,
9801009
}
9811010

9821011
constraints_added += 1
@@ -1011,8 +1040,14 @@ def _build_model_parameters_map(self, model) -> Dict[str, DescriptorNumber]:
10111040

10121041
return params_map
10131042

1014-
def _get_parameter_display_name(self, param: DescriptorNumber) -> str:
1015-
"""Get a display name for a parameter."""
1043+
def _get_parameter_display_name(self, param: DescriptorNumber, model_index: int | None = None) -> str:
1044+
"""Get a display name for a parameter.
1045+
1046+
:param param: The parameter to get the display name for.
1047+
:param model_index: Optional model index to prefix the display name with (e.g., 'M1').
1048+
:return: Display name, optionally prefixed with model identifier.
1049+
"""
1050+
display_name = param.name # Fallback
10161051
try:
10171052
from easyscience import global_object
10181053
# Try to find the parameter's path in the global object map
@@ -1021,10 +1056,14 @@ def _get_parameter_display_name(self, param: DescriptorNumber) -> str:
10211056
if path and len(path) >= 2:
10221057
parent_name = global_object.map.get_item_by_key(path[-2]).name
10231058
param_name = global_object.map.get_item_by_key(path[-1]).name
1024-
return f'{parent_name} {param_name}'
1059+
display_name = f'{parent_name} {param_name}'
1060+
break
10251061
except Exception: # noqa: BLE001
10261062
pass
1027-
return param.name
1063+
1064+
if model_index is not None:
1065+
return f'M{model_index + 1} {display_name}'
1066+
return display_name
10281067

10291068
# # #
10301069
# Q Range

EasyReflectometryApp/Gui/Pages/Sample/Sidebar/Advanced/Groups/ModelConstraints.qml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ EaElements.GroupBox {
1010
id: modelConstraintsGroup
1111
title: qsTr("Model constraints")
1212
enabled: true
13-
last: false
13+
last: true
1414

1515
property var selectedModelIndices: []
1616
property int selectedModelsCount: selectedModelIndices.length
@@ -140,12 +140,12 @@ EaElements.GroupBox {
140140
Item {
141141
id: modelConstraintsTableContainer
142142
width: parent.width
143-
height: Math.min(200, Math.max(60, modelConstraintsTable.height))
143+
height: modelConstraintsTable.height
144144

145145
EaComponents.TableView {
146146
id: modelConstraintsTable
147147
width: parent.width
148-
height: Math.min(200, Math.max(60, Math.max(modelConstraintsTable.contentHeight, modelConstraintsTable.implicitHeight)))
148+
maxRowCountShow: 1000
149149

150150
defaultInfoText: qsTr("No Model Constraints")
151151

@@ -196,8 +196,6 @@ EaElements.GroupBox {
196196
return constraint.dependentName + ' ' + prefix + constraint.expression
197197
}
198198
elide: Text.ElideRight
199-
ToolTip.visible: hovered && Globals.BackendWrapper.sampleConstraintsList[index] && Globals.BackendWrapper.sampleConstraintsList[index].rawExpression
200-
ToolTip.text: Globals.BackendWrapper.sampleConstraintsList[index] ? Globals.BackendWrapper.sampleConstraintsList[index].rawExpression : ""
201199
}
202200

203201
// Placeholder for delete button space

0 commit comments

Comments
 (0)