Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions EasyReflectometryApp/Backends/Mock/Project.qml
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,50 @@ QtObject {

property bool created: false
property string creationDate: ''
property string lastSaved: ''
property bool hasUnsavedChanges: false

// Like the Python backend, carries the path of the project file that was written.
signal projectSaved(string path)
signal projectSaveError(string message)
function projectFilePath() { return `${location}/${name}/project.json` }

property string name: 'Super duper project'
function setName(value) { name = value }
function setName(value) { name = value; hasUnsavedChanges = true }
property string description: 'Default project description from Mock proxy'
function setDescription(value) { description = value }
function setDescription(value) { description = value; hasUnsavedChanges = true }
property string location: '/path to the project'
function setLocation(value) { location = value }
function setLocation(value) { location = value; hasUnsavedChanges = true }

function create() {
console.debug(`Creating project ${name}`)
creationDate = `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`
created = true
lastSaved = new Date().toISOString()
hasUnsavedChanges = false
projectSaved(projectFilePath())
}

function save() {
console.debug(`Saving project ${name}`)
lastSaved = new Date().toISOString()
hasUnsavedChanges = false
projectSaved(projectFilePath())
}

function reset() {
console.debug(`Reset project ${name}`)
created = false
lastSaved = ''
hasUnsavedChanges = false
}

function load(path) {
console.debug(`Loading project from ${path}`)
creationDate = `${new Date().toLocaleDateString()} ${new Date().toLocaleTimeString()}`
created = true
lastSaved = ''
hasUnsavedChanges = false
}

}
10 changes: 9 additions & 1 deletion EasyReflectometryApp/Backends/Py/logic/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from easyreflectometry import Project as ProjectLib
from easyreflectometry.sample import MaterialCollection
from easyreflectometry.sample import MaterialDensity

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -61,7 +62,14 @@ def remove_at_index(self, value: str) -> None:
self._materials.pop(int(value))

def add_new(self) -> None:
self._materials.add_material()
# A material added from the GUI is a density material, so that the Material editor's
# detail panel (formula, density, SLD coupling) applies to it: that panel is shown only
# for `kind == 'density'`, and before this the GUI could not produce such a material at
# all — they arrived only through an ORSO sample load. Nothing is lost by the default:
# unchecking the coupling hands the SLD back for direct entry and fitting, which is what
# a plain `Material` offers. The library's defaults (Si at 2.33 g/cm3) also start the
# material at a physical SLD rather than the zero a plain `Material` would carry.
self._materials.add_material(MaterialDensity(name='Material added'))

def duplicate_selected(self) -> None:
self._materials.duplicate_material(self.index)
Expand Down
12 changes: 12 additions & 0 deletions EasyReflectometryApp/Backends/Py/logic/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,26 @@ def default_model_content(self, model: Model) -> None:
def add_new(self) -> None:
self._models.add_model()
self.default_model_content(self._models[-1])
self._attach_calculator(self._models[-1])
# Update index to point to the new model
self.index = len(self._models) - 1

def duplicate_selected_model(self) -> None:
self._models.duplicate_model(self.index)
self._attach_calculator(self._models[-1])
# Update index to point to the duplicated model
self.index = len(self._models) - 1

def _attach_calculator(self, model: Model) -> None:
"""Bind a model added through the collection to the project's calculator.

The collection's `add_model`/`duplicate_model` do not know the project's calculator, so
a model added through them has no interface. The project's fitter is built lazily for
the current model, and `Project.as_dict` (hence every save) touches it, so saving with
such a model selected failed with an internal error instead of writing the file.
"""
model.interface = self._project_lib._calculator

def move_selected_up(self) -> None:
if self.index > 0:
self._models.move_up(self.index)
Expand Down
21 changes: 21 additions & 0 deletions EasyReflectometryApp/Backends/Py/logic/project.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import hashlib
import json
from copy import copy
from pathlib import Path

Expand All @@ -20,6 +22,11 @@ def created(self) -> bool:
def path(self) -> str:
return str(self._project_lib.path)

@property
def path_json(self) -> str:
"""Path of the project file itself, as named in save feedback and error messages."""
return str(self._project_lib.path_json)

@property
def root_path(self) -> str:
return str(self._project_lib.path.parent)
Expand Down Expand Up @@ -118,6 +125,20 @@ def info(self) -> dict:
info['location'] = self._project_lib.path
return info

def content_fingerprint(self) -> str:
"""A digest of exactly what `save()` would write.

Used to tell a real edit from a signal that merely looks like one: selecting another
model or assembly emits the same signals as editing them, but does not change this.
Costs one serialization (under 10 ms with an experiment loaded), so callers keep it to
the moment a project might turn from clean to edited, not to every signal.

:raises Exception: whatever `as_dict` raises; a caller that cannot fingerprint the
project must treat it as changed.
"""
content = self._project_lib.as_dict(include_materials_not_in_model=True)
return hashlib.sha256(json.dumps(content, sort_keys=True).encode('utf-8')).hexdigest()

def create(self) -> None:
self._project_lib.create()
self._project_lib.save_as_json()
Expand Down
Loading
Loading