Source code for cosedaUI.cellfile_window

"""Modal editor to create and persist CrystFEL .cell files from an INI workspace."""

import os
from PyQt6.QtWidgets import (
    QWidget, QLabel, QLineEdit, QPushButton, QVBoxLayout, QHBoxLayout, QMessageBox, QFormLayout, QComboBox
)

from coseda.crystfel_cell import (
    CRYSTFEL_LATTICE_TYPES,
    crystfel_unique_axis_allowed,
    normalize_crystfel_lattice_type,
)


[docs] class CellfileWindow(QWidget): """Dialog for entering lattice parameters and writing a CrystFEL .cell file.""" def __init__(self, ini_path): """Load defaults from the provided INI and build the form UI.""" super().__init__() self.ini_path = ini_path self.project_dir = os.path.dirname(self.ini_path) self.ini_config = {} try: with open(self.ini_path, "r") as f: for line in f: if line.startswith("[Paths]"): break for line in f: if line.strip().startswith("["): break if "=" in line: key, val = line.strip().split("=", 1) self.ini_config[key.strip()] = val.strip() if "outputfolder" in self.ini_config: self.project_dir = os.path.join(self.project_dir, self.ini_config["outputfolder"]) except Exception: pass self.setWindowTitle("Generate .cell File") self.fields = { "lattice_type": QComboBox(), "centering": QComboBox(), "unique_axis": QComboBox(), "a": QLineEdit(), "b": QLineEdit(), "c": QLineEdit(), "al": QLineEdit(), "be": QLineEdit(), "ga": QLineEdit() } self.fields["lattice_type"].addItems(CRYSTFEL_LATTICE_TYPES) self.fields["centering"].addItems(["P", "A", "B", "C", "I", "R", "F"]) self.fields["unique_axis"].addItems(["a", "b", "c"]) self.fields["unique_axis"].setCurrentIndex(-1) def on_lattice_type_changed(text): lattice_type = normalize_crystfel_lattice_type(text, self.fields["centering"].currentText()) enabled = crystfel_unique_axis_allowed(lattice_type) self.fields["unique_axis"].setEnabled(enabled) if enabled and lattice_type in ("tetragonal", "hexagonal") and self.fields["unique_axis"].currentIndex() == -1: self.fields["unique_axis"].setCurrentText("c") elif not enabled: self.fields["unique_axis"].setCurrentIndex(-1) self.fields["lattice_type"].currentTextChanged.connect(on_lattice_type_changed) self.fields["centering"].currentTextChanged.connect( lambda: on_lattice_type_changed(self.fields["lattice_type"].currentText()) ) on_lattice_type_changed(self.fields["lattice_type"].currentText()) form_layout = QFormLayout() for label, widget in self.fields.items(): form_layout.addRow(label, widget) save_button = QPushButton("Save .cell File") save_button.clicked.connect(self.save_cell_file) layout = QVBoxLayout() layout.addLayout(form_layout) layout.addWidget(save_button) self.setLayout(layout)
[docs] def save_cell_file(self): """Validate inputs, write the .cell file, and update the INI Paths.cellfile entry.""" params = { key: field.currentText().strip() if isinstance(field, QComboBox) else field.text().strip() for key, field in self.fields.items() } params["lattice_type"] = normalize_crystfel_lattice_type(params["lattice_type"], params["centering"]) if not crystfel_unique_axis_allowed(params["lattice_type"]): params["unique_axis"] = "" missing = [key for key, value in params.items() if not value] if not crystfel_unique_axis_allowed(params["lattice_type"]): missing = [key for key in missing if key != "unique_axis"] if missing: QMessageBox.warning(self, "Missing Fields", f"Please fill in all fields: {', '.join(missing)}") return content = [ "CrystFEL unit cell file version 1.0", "", f"lattice_type = {params['lattice_type']}", f"centering = {params['centering']}", "", f"a = {params['a']} A", f"b = {params['b']} A", f"c = {params['c']} A", f"al = {params['al']} deg", f"be = {params['be']} deg", f"ga = {params['ga']} deg" ] if params["unique_axis"]: content.insert(4, f"unique_axis = {params['unique_axis']}") filepath = os.path.join(self.project_dir, "cellfile.cell") try: with open(filepath, "w") as f: f.write("\n".join(content)) QMessageBox.information(self, "Saved", f"Cell file saved to:\n{filepath}") # Update INI file with cellfile entry in [Paths] try: from configparser import ConfigParser parser = ConfigParser() parser.read(self.ini_path) if not parser.has_section("Paths"): parser.add_section("Paths") parser.set("Paths", "cellfile", os.path.basename(filepath)) with open(self.ini_path, "w") as f: parser.write(f) except Exception as e: QMessageBox.warning(self, "Warning", f"Could not update INI file:\n{str(e)}") self.close() except Exception as e: QMessageBox.critical(self, "Error", f"Failed to save file:\n{str(e)}")