Source code for rayoptics.qtgui.idealimagerdialog

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright © 2019 Michael J. Hayford
""" Qt5 dialog box for ideal imager ui

.. Created on Tue Jun 18 13:09:19 2019

.. codeauthor: Michael J. Hayford
"""

from rayoptics.parax.specsheet import create_specsheets
from rayoptics.parax import idealimager
from rayoptics.parax.etendue import (obj_img_set, fld_ape_set,
                                     fld_labels, ap_labels)

from rayoptics.util.dict2d import dict2D

from PyQt5.QtCore import Qt as qt
from PyQt5.QtWidgets import (QApplication, QDialog, QRadioButton, QWidget,
                             QStackedWidget, QGridLayout, QGroupBox,
                             QHBoxLayout, QLabel, QLineEdit, QCheckBox,
                             QVBoxLayout, QDialogButtonBox, QMessageBox)


[docs]def value_to_text(value, fmt_str="{:> #.5f}"): if value is None: value_text = '' elif type(value) is str: value_text = value else: value_text = fmt_str.format(value) return value_text
[docs]class IdealImagerDialog(QWidget): def __init__(self, conjugate_type, specsheets, cmd_fct=None, **kwargs): super().__init__(**kwargs) self.dlog_attrs = {} self.conjugate_type = conjugate_type self.conjugate_box = self.createConjugateBox(itype=self.conjugate_type) self.specsheet_dict = specsheets self.imager_stack = {} self.etendue_stack = {} self.imager_groupbox_stack = QStackedWidget() self.etendue_groupbox_stack = QStackedWidget() for key, specsheet in specsheets.items(): isgb = ImagerSpecGroupBox(self, specsheet) self.imager_stack[key] = isgb self.imager_groupbox_stack.addWidget(isgb) egb = EtendueGroupBox(self, key, specsheet) self.etendue_stack[key] = egb self.etendue_groupbox_stack.addWidget(egb) imager_groupbox = self.imager_stack[self.conjugate_type] self.imager_groupbox_stack.setCurrentWidget(imager_groupbox) etendue_groupbox = self.etendue_stack[self.conjugate_type] self.etendue_groupbox_stack.setCurrentWidget(etendue_groupbox) # construct the top level layout overallLayout = QVBoxLayout(self) mainLayout = QHBoxLayout() mainLayout.addWidget(self.conjugate_box) mainLayout.addWidget(self.imager_groupbox_stack) mainLayout.addWidget(self.etendue_groupbox_stack) self.button_box = self.createButtonBox(cmd_fct) overallLayout.addLayout(mainLayout) overallLayout.addWidget(self.button_box) self.setLayout(overallLayout) self.setWindowTitle("Optical Spec Sheet")
[docs] def createButtonBox(self, cmd_fct): def clicked(button): command = button.text() specsheet = self.specsheet_dict[self.conjugate_type] if cmd_fct: try: cmd_fct(self, command, specsheet) except: QMessageBox.warning(self, self.tr("Ray-Optics"), self.tr("Please provide correct inputs.")); else: print(button.text(), 'button pressed') buttonbox = QDialogButtonBox(qt.Horizontal, self) buttonbox.addButton('New', QDialogButtonBox.ApplyRole) buttonbox.addButton(QDialogButtonBox.Apply) buttonbox.addButton('Update', QDialogButtonBox.ApplyRole) buttonbox.addButton(QDialogButtonBox.Close) for b in buttonbox.buttons(): b.setAutoDefault(False) # buttonbox.setCenterButtons(True) buttonbox.clicked.connect(clicked) return buttonbox
[docs] def createConjugateBox(self, itype='infinite'): conjugate_box = QGroupBox("Conjugates") layout = QVBoxLayout() infinite = QRadioButton('Infinite') self.dlog_attrs['infinite'] = infinite layout.addWidget(infinite) infinite.clicked.connect(lambda: self.change_conjugate('infinite')) finite = QRadioButton('Finite') self.dlog_attrs['finite'] = finite layout.addWidget(finite) finite.clicked.connect(lambda: self.change_conjugate('finite')) conjugate_box.setLayout(layout) self.dlog_attrs[itype].setChecked(True) return conjugate_box
[docs] def change_conjugate(self, conj_type): print("change_conjugate:", conj_type) if self.conjugate_type is not conj_type: self.update_conjugate(conj_type)
[docs] def update_conjugate(self, conj_type): prev_specsheet = self.specsheet_dict[self.conjugate_type] self.conjugate_type = conj_type new_specsheet = self.specsheet_dict[conj_type] new_partition, max_inputs = new_specsheet.partition_defined() if new_partition is None: if prev_specsheet.imager.f is not None: prev_f = prev_specsheet.imager.f new_specsheet.imager_inputs['f'] = prev_f new_imager_groupbox = self.imager_stack[conj_type] self.imager_groupbox_stack.setCurrentWidget(new_imager_groupbox) etendue_groupbox = self.etendue_stack[conj_type] self.etendue_groupbox_stack.setCurrentWidget(etendue_groupbox) self.update_values()
[docs] def update_values(self): """ callback routine for any dialog value change """ conj_type = self.conjugate_type specsheet = self.specsheet_dict[conj_type] imager_groupbox = self.imager_stack[conj_type] etendue_groupbox = self.etendue_stack[conj_type] imager_inputs = imager_groupbox.specsheet.imager_inputs etendue_inputs = etendue_groupbox.specsheet.etendue_inputs imager, etendue_values = specsheet.generate_from_inputs(imager_inputs, etendue_inputs) specsheet.imager_inputs = imager_inputs specsheet.etendue_inputs = etendue_inputs imager_groupbox.update_values() etendue_groupbox.update_values()
[docs] def update_checkboxes(self): """ callback routine for any dialog checkbox change """ imager_groupbox = self.imager_stack[self.conjugate_type] etendue_groupbox = self.etendue_stack[self.conjugate_type] imager_groupbox.update_checkboxes() etendue_groupbox.update_checkboxes()
[docs]class ImagerSpecGroupBox(QGroupBox): def __init__(self, parent, specsheet, keys=None, labels=None, **kwargs): super().__init__(title='Imager specs', **kwargs) self.parent = parent self.specsheet = specsheet self.dlog_attrs = {} self.keys = keys if keys else idealimager.ideal_imager_keys self.labels = labels if labels else idealimager.ideal_imager_labels layout = QGridLayout() for i, key in enumerate(self.keys): label = QLabel(self.labels[i]+':') lineEdit = QLineEdit() checkBox = QCheckBox() row = i + 1 layout.addWidget(label, row, 0) layout.addWidget(lineEdit, row, 1) layout.addWidget(checkBox, row, 2) self.dlog_attrs[key] = (label, lineEdit, checkBox) lineEdit.setText(value_to_text(specsheet.imager[i])) # Use returnPressed signal rather than editingFinished. The latter # fires on change of focus as well as return. We only want a # signal when the lineEdit contents change and are committed by # the user. lineEdit.returnPressed.connect(lambda k=key: self.value_change(k)) if key in specsheet.imager_inputs: if not checkBox.isChecked(): checkBox.setCheckState(qt.Checked) checkBox.stateChanged.connect(lambda state, k=key: self.chkbox_change(state, k)) self.setLayout(layout) self.update_values()
[docs] def value_change(self, imager_key): label, lineEdit, checkBox = self.dlog_attrs[imager_key] try: value = float(lineEdit.text()) except ValueError: value = None finally: imager_inputs = self.specsheet.imager_inputs if value is not None: if imager_key in imager_inputs or len(imager_inputs) < 2: imager_inputs[imager_key] = value if not checkBox.isChecked(): checkBox.setCheckState(qt.Checked) else: if imager_key in imager_inputs: del imager_inputs[imager_key] if checkBox.isChecked(): checkBox.setChecked(False) self.parent.update_values()
[docs] def chkbox_change(self, state, imager_key): label, lineEdit, checkBox = self.dlog_attrs[imager_key] checked = state == qt.Checked if checked: try: value = float(lineEdit.text()) except ValueError: value = None finally: self.specsheet.imager_inputs[imager_key] = value if not checkBox.isChecked(): checkBox.setChecked(True) else: if imager_key in self.specsheet.imager_inputs: del self.specsheet.imager_inputs[imager_key] if checkBox.isChecked(): checkBox.setChecked(False) self.parent.update_checkboxes()
[docs] def update_values(self): if self.specsheet.imager is not None: value_list = self.specsheet.imager # refill gui widgets and set enabled state for i, key in enumerate(self.keys): value = value_list[i] label, lineEdit, checkBox = self.dlog_attrs[key] lineEdit.setText(value_to_text(value)) self.update_checkboxes()
[docs] def update_checkboxes(self): partition, max_inputs = self.specsheet.partition_defined() if partition == 'imager': # the inputs for imager are sufficient to calculate all; mark # inputs editable and disable the others enabled_list = [False]*5 for key in self.specsheet.imager_inputs: enabled_list[self.keys.index(key)] = True elif bool(partition): # an imager is defined by other partitions, this means: # 1) the imager key is not editable # 2a) if one imager input, the remainder are uneditable # 2b) if no imager inputs, all but the imager key is editable if len(self.specsheet.imager_inputs): enabled_list = [False]*5 for key in self.specsheet.imager_inputs: enabled_list[self.keys.index(key)] = True imager_key = self.specsheet.imager_defined() if imager_key: enabled_list[self.keys.index(imager_key)] = False else: enabled_list = [True]*5 imager_key = self.specsheet.imager_defined() if imager_key: enabled_list[self.keys.index(imager_key)] = False else: # no imager defined, all items are editable enabled_list = [True]*5 # update the ui from the enabled list and inputs for i, key in enumerate(self.keys): label, lineEdit, checkBox = self.dlog_attrs[key] if key in self.specsheet.imager_inputs: if not checkBox.isChecked(): checkBox.setCheckState(qt.Checked) else: if checkBox.isChecked(): checkBox.setCheckState(qt.Unchecked) if self.specsheet.frozen_imager_inputs[i]: enabled_list[i] = False lineEdit.setEnabled(enabled_list[i]) checkBox.setEnabled(enabled_list[i])
[docs]class EtendueGroupBox(QGroupBox): def __init__(self, parent, itype, specsheet, **kwargs): super().__init__(title='etendue definition', **kwargs) self.parent = parent self.specsheet = specsheet self.groupboxes = dict2D(fld_ape_set, obj_img_set) if itype == 'infinite': gb00 = SpaceGroupBox(parent=self, title='object space', obj_img='object', fld_ape='field', keys=(fld_labels[1],)) self.groupboxes['field']['object'] = gb00 gb10 = SpaceGroupBox(parent=self, title='', obj_img='object', fld_ape='aperture', keys=(ap_labels[0],)) self.groupboxes['aperture']['object'] = gb10 gb01 = SpaceGroupBox(parent=self, title='image space', obj_img='image', fld_ape='field', keys=(fld_labels[0],)) self.groupboxes['field']['image'] = gb01 gb11 = SpaceGroupBox(parent=self, title='', obj_img='image', fld_ape='aperture', keys=(ap_labels[2], ap_labels[1])) self.groupboxes['aperture']['image'] = gb11 else: gb00 = SpaceGroupBox(parent=self, title='object space', fld_ape='field', obj_img='object', keys=(fld_labels[0],)) self.groupboxes['field']['object'] = gb00 gb10 = SpaceGroupBox(parent=self, title='', fld_ape='aperture', obj_img='object', keys=(ap_labels[2], ap_labels[1])) self.groupboxes['aperture']['object'] = gb10 gb01 = SpaceGroupBox(parent=self, title='image space', fld_ape='field', obj_img='image', keys=(fld_labels[0],)) self.groupboxes['field']['image'] = gb01 gb11 = SpaceGroupBox(parent=self, title='', fld_ape='aperture', obj_img='image', keys=(ap_labels[2], ap_labels[1])) self.groupboxes['aperture']['image'] = gb11 layout = QGridLayout() layout.addWidget(gb00, 0, 0) layout.addWidget(gb10, 1, 0) layout.addWidget(gb01, 0, 1) layout.addWidget(gb11, 1, 1) self.setLayout(layout)
[docs] def value_change(self, fld_ape, obj_img, key): """ callback routine for item value widget """ inputs = self.specsheet.etendue_inputs[fld_ape][obj_img] gb = self.groupboxes[fld_ape][obj_img] label, lineEdit, checkBox = gb.dlog_attrs[key] try: value = float(lineEdit.text()) except ValueError: return else: if (key in inputs or len(inputs) < 2): inputs[key] = value if not checkBox.isChecked(): checkBox.setCheckState(qt.Checked) self.parent.update_values()
[docs] def chkbox_change(self, state, fld_ape, obj_img, key): """ callback routine for item checkbox """ inputs = self.specsheet.etendue_inputs[fld_ape][obj_img] gb = self.groupboxes[fld_ape][obj_img] label, lineEdit, checkBox = gb.dlog_attrs[key] checked = state == qt.Checked if checked: try: value = float(lineEdit.text()) except ValueError: value = None finally: inputs[key] = value if not checkBox.isChecked(): checkBox.setChecked(True) else: if key in inputs: del inputs[key] if checkBox.isChecked(): checkBox.setChecked(False) self.parent.update_checkboxes()
[docs] def update_values(self): etendue_inputs = self.specsheet.etendue_inputs etendue_values = self.specsheet.etendue_values for fld_ape_key, fld_ape_value in etendue_values.items(): for obj_img_key, values in fld_ape_value.items(): sgb = self.groupboxes[fld_ape_key][obj_img_key] inputs = etendue_inputs[fld_ape_key][obj_img_key] sgb.update_values(inputs, values) self.update_checkboxes()
[docs] def update_checkboxes(self): """ update the enabled and checked state for the etendue groupbox """ etendue_inputs = self.specsheet.etendue_inputs etendue_values = self.specsheet.etendue_values partition, max_inputs = self.specsheet.partition_defined() for fld_ape_key, fld_ape_value in etendue_values.items(): num_fld_ape_inputs = self.specsheet.partitions[fld_ape_key] for obj_img_key, values in fld_ape_value.items(): sgb = self.groupboxes[fld_ape_key][obj_img_key] inputs = etendue_inputs[fld_ape_key][obj_img_key] if num_fld_ape_inputs == 2: partition_defined = True elif num_fld_ape_inputs == 1 and len(inputs) == 1: partition_defined = True elif num_fld_ape_inputs == 1 and len(inputs) == 0: partition_defined = True if bool(partition) else False else: # num_fld_ape_inputs == 0 and len(inputs) == 0 partition_defined = False sgb.update_checkboxes(inputs, values, partition_defined=partition_defined)
[docs]class SpaceGroupBox(QGroupBox): def __init__(self, parent, title, fld_ape, obj_img, keys, labels=None, **kwargs): super().__init__(title=title, **kwargs) self.dlog_attrs = {} self.parent = parent self.fld_ape = fld_ape self.obj_img = obj_img self.keys = keys if labels is None: self.labels = keys else: self.labels = labels layout = QGridLayout() for i, key in enumerate(self.keys): label = QLabel(self.labels[i]+':') lineEdit = QLineEdit() checkBox = QCheckBox() row = i + 1 layout.addWidget(label, row, 0) layout.addWidget(lineEdit, row, 1) layout.addWidget(checkBox, row, 2) self.dlog_attrs[key] = (label, lineEdit, checkBox) # Use returnPressed signal rather than editingFinished. The latter # fires on change of focus as well as return. We only want a # signal when the lineEdit contents change and are committed by # the user. lineEdit.returnPressed.connect(lambda k=key: parent.value_change(fld_ape, obj_img, k)) checkBox.stateChanged.connect(lambda state, k=key: parent.chkbox_change(state, fld_ape, obj_img, k)) self.setLayout(layout)
[docs] def update_values(self, inputs, values): """ update the display for the etendue cell being updated """ for key in self.keys: value = values[key] label, lineEdit, checkBox = self.dlog_attrs[key] lineEdit.setText(value_to_text(value))
[docs] def update_checkboxes(self, inputs, values, partition_defined=False): """ update the display for the etendue cell being updated A partition is an aperture or field pair of object/image inputs. If it is defined, this means that all attrs can be supplied. In this case, the inputs will have editable values and (checked) checkboxes; the remaining attrs will have uneditable values and (unchecked) checkboxes. If the partition is not defined, all values and checkboxes will be editable, the input attrs, if any, will be checked. """ if partition_defined: for key in self.keys: label, lineEdit, checkBox = self.dlog_attrs[key] if key in inputs: lineEdit.setEnabled(True) checkBox.setEnabled(True) if not checkBox.isChecked(): checkBox.setCheckState(qt.Checked) else: lineEdit.setEnabled(False) checkBox.setEnabled(False) if checkBox.isChecked(): checkBox.setCheckState(qt.Unchecked) else: for key in self.keys: label, lineEdit, checkBox = self.dlog_attrs[key] lineEdit.setEnabled(True) checkBox.setEnabled(True) if key in inputs: if not checkBox.isChecked(): checkBox.setCheckState(qt.Checked) else: if checkBox.isChecked(): checkBox.setCheckState(qt.Unchecked)
if __name__ == '__main__': import sys class Dialog2(QDialog): def __init__(self, parent, conjugate_type, specsheets): QDialog.__init__(self, parent) # self.setModal(0) iid = IdealImagerDialog(conjugate_type, specsheets, parent=self) iid.update_values() def exit(self): self.close() app = QApplication(sys.argv) specsheets = create_specsheets() dialog = Dialog2(None, 'infinite', specsheets) dialog.show() # dialog.exec()