class MainWindow(QMainWindow): count = 0 def __init__(self, parent=None, qtapp=None): super().__init__(parent) self.qtapp = qtapp self.mdi = QMdiArea() self.setCentralWidget(self.mdi) self.app_manager = AppManager(None, gui_parent=self) self.mdi.subWindowActivated.connect(self.app_manager. on_view_activated) self.is_dark = self.light_or_dark(False) self.left = 100 self.top = 50 self.width = 1800 self.height = 1200 self.setGeometry(self.left, self.top, self.width, self.height) bar = self.menuBar() file_menu = bar.addMenu("File") file_menu.addAction("New") file_menu.addAction("Open...") file_menu.addSeparator() file_menu.addAction("Save") file_menu.addAction("Save As...") file_menu.addAction("Close") file_menu.triggered[QAction].connect(self.file_action) view_menu = bar.addMenu("Data View") view_menu.addAction("Spec Sheet") view_menu.addAction("Optical Layout") view_menu.addAction("Lens Table") view_menu.addAction("Element Table") view_menu.addAction("Glass Map") # view_menu.addAction("Lens View") view_menu.triggered[QAction].connect(self.view_action) parax_menu = bar.addMenu("Paraxial Model") parax_menu.addAction("Paraxial Model") parax_menu.addAction("y-ybar View") parax_menu.addAction("nu-nubar View") parax_menu.addAction("yui Ray Table") parax_menu.addAction("3rd Order Aberrations") parax_menu.triggered[QAction].connect(self.view_action) analysis_menu = bar.addMenu("Analysis") analysis_menu.addAction("Ray Table") analysis_menu.addAction("Ray Fans") analysis_menu.addAction("OPD Fans") analysis_menu.addAction("Spot Diagram") analysis_menu.addAction("Wavefront Map") analysis_menu.addAction("Astigmatism Curves") analysis_menu.triggered[QAction].connect(self.view_action) tools_menu = bar.addMenu("Tools") tools_menu.addAction("Paraxial Vignetting") tools_menu.triggered[QAction].connect(self.view_action) wnd_menu = bar.addMenu("Window") wnd_menu.addAction("Cascade") wnd_menu.addAction("Tiled") wnd_menu.addSeparator() wnd_menu.addAction("Light UI") wnd_menu.addAction("Dark UI") wnd_menu.addSeparator() dock.create_dock_windows(self) for pi in dock.panels.values(): wnd_menu.addAction(pi.menu_action) wnd_menu.triggered[QAction].connect(self.window_action) self.setWindowTitle("Ray Optics") self.show() path = Path(rayoptics.__file__).parent self.cur_dir = path / "models" if False: # create new model self.new_model() else: # restore a default model # self.cur_dir = path / "codev/tests" # self.open_file(path / "codev/tests/asp46.seq") # self.open_file(path / "codev/tests/dar_test.seq") # self.open_file(path / "codev/tests/paraboloid.seq") # self.open_file(path / "codev/tests/paraboloid_f8.seq") # self.open_file(path / "codev/tests/schmidt.seq") # self.open_file(path / "codev/tests/questar35.seq") # self.open_file(path / "codev/tests/rc_f16.seq") # self.open_file(path / "codev/tests/ag_dblgauss.seq") # self.open_file(path / "codev/tests/threemir.seq") # self.open_file(path / "codev/tests/folded_lenses.seq") # self.open_file(path / "codev/tests/lens_reflection_test.seq") # self.open_file(path / "codev/tests/dec_tilt_test.seq") # self.open_file(path / "codev/tests/landscape_lens.seq") # self.open_file(path / "codev/tests/mangin.seq") # self.open_file(path / "codev/tests/CODV_32327.seq") # self.open_file(path / "codev/tests/dar_test.seq") # self.open_file(path / "optical/tests/cell_phone_camera.roa") # self.open_file(path / "optical/tests/singlet_f3.roa") # self.cur_dir = path / "models" # self.open_file(path / "models/Cassegrain.roa") # self.open_file(path / "models/collimator.roa") # self.open_file(path / "models/Dall-Kirkham.roa") # self.open_file(path / "models/petzval.roa") # self.open_file(path / "models/Ritchey_Chretien.roa") # self.open_file(path / "models/Sasian Triplet.roa") # self.open_file(path / "models/singlet_f5.roa") # self.open_file(path / "models/thinlens.roa") # self.open_file(path / "models/telephoto.roa") # self.open_file(path / "models/thin_triplet.roa") # self.open_file(path / "models/TwoMirror.roa") # self.open_file(path / "models/TwoSphericalMirror.roa") self.cur_dir = path / "zemax/tests" # self.open_file(path / "zemax/tests/US08427765-1.ZMX") # self.open_file(path / "zemax/tests/US00583336-2-scaled.zmx") # self.open_file(path / "zemax/tests/HoO-V2C18Ex03.zmx") # self.open_file(path / "zemax/tests/HoO-V2C18Ex27.zmx") # self.open_file(path / "zemax/tests/HoO-V2C18Ex46.zmx") # self.open_file(path / "zemax/tests/HoO-V2C18Ex66.zmx") # self.open_file(path / "zemax/tests/US05831776-1.zmx") self.open_file(path / "zemax/tests/354710-C-Zemax(ZMX).zmx") # self.cur_dir = path / "zemax/models/telescopes" # self.open_file(path / "zemax/models/telescopes/Figure4.zmx") # self.open_file(path / "zemax/models/telescopes/HoO-V2C18Ex11.zmx") # self.cur_dir = path / "zemax/models/PhotoPrime" # self.open_file(path / "zemax/models/PhotoPrime/US05321554-4.ZMX") # self.open_file(path / "zemax/models/PhotoPrime/US06476982-1.ZMX") # self.open_file(path / "zemax/models/PhotoPrime/US07190532-1.ZMX") # self.open_file(path / "zemax/models/PhotoPrime/US04331391-1.zmx") # self.open_file(path / "zemax/models/PhotoPrime/US05331467-1.zmx") def add_subwindow(self, widget, model_info): sub_wind = self.mdi.addSubWindow(widget) self.app_manager.add_view(sub_wind, widget, model_info) MainWindow.count += 1 return sub_wind def delete_subwindow(self, sub_wind): self.app_manager.delete_view(sub_wind) self.mdi.removeSubWindow(sub_wind) MainWindow.count -= 1 def add_ipython_subwindow(self): try: create_ipython_console(self, 'iPython console', 800, 600) except MultipleInstanceError: logging.debug("Unable to open iPython console. " "MultipleInstanceError") except Exception as inst: print(type(inst)) # the exception instance print(inst.args) # arguments stored in .args print(inst) # __str__ allows args to be printed directly, pass # but may be overridden in exception subclasses def initial_window_offset(self): offset_x = 50 offset_y = 25 orig_x = (MainWindow.count - 1)*offset_x orig_y = (MainWindow.count - 1)*offset_y return orig_x, orig_y def file_action(self, q): if q.text() == "New": self.new_model() if q.text() == "Open...": options = QFileDialog.Options() # options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getOpenFileName( self, "QFileDialog.getOpenFileName()", str(self.cur_dir), "All files (*.seq *.zmx *.roa);;" "CODE V files (*.seq);;" "Ray-Optics files (*.roa);;" "Zemax files (*.zmx)", options=options) if fileName: logging.debug("open file: %s", fileName) filename = Path(fileName) self.cur_dir = filename.parent self.open_file(filename) if q.text() == "Save As...": options = QFileDialog.Options() # options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getSaveFileName( self, "QFileDialog.getSaveFileName()", "", "Ray-Optics Files (*.roa);;All Files (*)", options=options) if fileName: logging.debug("save file: %s", fileName) self.save_file(fileName) if q.text() == "Close": self.close_model() def new_model(self): iid = cmds.create_new_ideal_imager(gui_parent=self, conjugate_type='infinite') self.add_ipython_subwindow() self.refresh_app_ui() def open_file(self, file_name, **kwargs): self.cur_filename = file_name opt_model = cmds.open_model(file_name, **kwargs) self.app_manager.set_model(opt_model) self.is_changed = True self.create_lens_table() cmds.create_live_layout_view(self.app_manager.model, gui_parent=self) self.add_ipython_subwindow() self.refresh_app_ui() def save_file(self, file_name): self.app_manager.model.save_model(file_name) self.cur_filename = file_name self.is_changed = False def close_model(self): """ NOTE: this does not check to save a modified model """ self.app_manager.close_model(self.delete_subwindow) def view_action(self, q): opt_model = self.app_manager.model if q.text() == "Spec Sheet": cmds.create_new_ideal_imager(opt_model=opt_model, gui_parent=self) if q.text() == "Optical Layout": cmds.create_live_layout_view(opt_model, gui_parent=self) if q.text() == "Lens Table": self.create_lens_table() if q.text() == "Element Table": model = cmds.create_element_table_model(opt_model) self.create_table_view(model, "Element Table") if q.text() == "Glass Map": cmds.create_glass_map_view(opt_model, gui_parent=self) if q.text() == "Ray Fans": cmds.create_ray_fan_view(opt_model, "Ray", gui_parent=self) if q.text() == "OPD Fans": cmds.create_ray_fan_view(opt_model, "OPD", gui_parent=self) if q.text() == "Spot Diagram": cmds.create_ray_grid_view(opt_model, gui_parent=self) if q.text() == "Wavefront Map": cmds.create_wavefront_view(opt_model, gui_parent=self) if q.text() == "Astigmatism Curves": cmds.create_field_curves(opt_model, gui_parent=self) if q.text() == "3rd Order Aberrations": cmds.create_3rd_order_bar_chart(opt_model, gui_parent=self) if q.text() == "y-ybar View": cmds.create_paraxial_design_view_v2(opt_model, 'ht', gui_parent=self) if q.text() == "nu-nubar View": cmds.create_paraxial_design_view_v2(opt_model, 'slp', gui_parent=self) if q.text() == "yui Ray Table": model = cmds.create_parax_table_model(opt_model) self.create_table_view(model, "Paraxial Ray Table") if q.text() == "Paraxial Model": model = cmds.create_parax_model_table(opt_model) self.create_table_view(model, "Paraxial Model") if q.text() == "Ray Table": self.create_ray_table(opt_model) if q.text() == "Paraxial Vignetting": trace.apply_paraxial_vignetting(opt_model) self.refresh_gui() def window_action(self, q): if q.text() == "Cascade": self.mdi.cascadeSubWindows() if q.text() == "Tiled": self.mdi.tileSubWindows() if q.text() == "Light UI": self.is_dark = self.light_or_dark(False) self.app_manager.sync_light_or_dark(self.is_dark) if q.text() == "Dark UI": self.is_dark = self.light_or_dark(True) self.app_manager.sync_light_or_dark(self.is_dark) def light_or_dark(self, is_dark): """ set the UI to a light or dark scheme. Qt doesn't seem to support controlling the MdiArea's background from a style sheet. Set the widget directly and save the original color to reset defaults. """ if not hasattr(self, 'mdi_background'): self.mdi_background = self.mdi.background() if is_dark: self.qtapp.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) rgb = DarkPalette.color_palette() self.mdi.setBackground(QColor(rgb['COLOR_BACKGROUND_NORMAL'])) else: self.qtapp.setStyleSheet('') self.mdi.setBackground(self.mdi_background) return is_dark def create_lens_table(self): seq_model = self.app_manager.model.seq_model def set_stop_surface(stop_surface): seq_model.stop_surface = stop_surface self.refresh_gui() def handle_context_menu(point): try: vheader = view.verticalHeader() row = vheader.logicalIndexAt(point.y()) except NameError: pass else: # show menu about the row menu = QMenu(self) if row != seq_model.stop_surface: menu.addAction('Set Stop Surface', lambda: set_stop_surface(row)) if seq_model.stop_surface is not None: menu.addAction('Float Stop Surface', lambda: set_stop_surface(None)) menu.popup(vheader.mapToGlobal(point)) model = cmds.create_lens_table_model(seq_model) view = self.create_table_view(model, "Surface Data Table") vheader = view.verticalHeader() vheader.setContextMenuPolicy(qt.CustomContextMenu) vheader.customContextMenuRequested.connect(handle_context_menu) def create_ray_table(self, opt_model): osp = opt_model.optical_spec pupil = [0., 1.] fi = 0 wl = osp.spectral_region.reference_wvl fld, wvl, foc = osp.lookup_fld_wvl_focus(fi, wl) ray, ray_op, wvl = trace.trace_base(opt_model, pupil, fld, wvl) # ray, ray_op, wvl, opd = trace.trace_with_opd(opt_model, pupil, # fld, wvl, foc) # cr = trace.RayPkg(ray, ray_op, wvl) # s, t = trace.trace_coddington_fan(opt_model, cr, foc) ray = [RaySeg(*rs) for rs in ray] model = cmds.create_ray_table_model(opt_model, ray) self.create_table_view(model, "Ray Table") def create_table_view(self, table_model, table_title, close_callback=None): # construct the top level widget widget = QWidget() # construct the top level layout layout = QVBoxLayout(widget) table_view = TableView(table_model) table_view.setAlternatingRowColors(True) # Add table to box layout layout.addWidget(table_view) # set the layout on the widget widget.setLayout(layout) sub = self.add_subwindow(widget, ModelInfo(self.app_manager.model, cmds.update_table_view, (table_view,))) sub.setWindowTitle(table_title) sub.installEventFilter(self) table_view.setMinimumWidth(table_view.horizontalHeader().length() + table_view.horizontalHeader().height()) # The following line should work but returns 0 # table_view.verticalHeader().width()) view_width = table_view.width() view_ht = table_view.height() orig_x, orig_y = self.initial_window_offset() sub.setGeometry(orig_x, orig_y, view_width, view_ht) # table data updated successfully table_model.update.connect(self.on_data_changed) sub.show() return table_view def eventFilter(self, obj, event): """Used by table_view in response to installEventFilter.""" if (event.type() == QEvent.Close): print('close event received:', obj) return False def refresh_gui(self, **kwargs): self.app_manager.refresh_gui(**kwargs) def refresh_app_ui(self): dock.update_dock_windows(self) def handle_ideal_imager_command(self, iid, command, specsheet): ''' link Ideal Imager Dialog buttons to model actions iid: ideal imager dialog command: text field with the action - same as button label specsheet: the input specsheet used to drive the actions ''' if command == 'Apply': opt_model = self.app_manager.model opt_model.set_from_specsheet(specsheet) self.refresh_gui() elif command == 'Close': for view, info in self.app_manager.view_dict.items(): if iid == info[0]: self.delete_subwindow(view) view.close() break elif command == 'Update': opt_model = self.app_manager.model specsheet = opt_model.specsheet firstorder.specsheet_from_parax_data(opt_model, specsheet) iid.specsheet_dict[specsheet.conjugate_type] = specsheet iid.update_values() elif command == 'New': opt_model = cmds.create_new_optical_model_from_specsheet(specsheet) self.app_manager.set_model(opt_model) for view, info in self.app_manager.view_dict.items(): if iid == info[0]: w = iid mi = info[1] args = (iid, opt_model) new_mi = ModelInfo(model=opt_model, fct=mi.fct, args=args, kwargs=mi.kwargs) self.app_manager.view_dict[view] = w, new_mi self.refresh_gui() self.create_lens_table() cmds.create_live_layout_view(opt_model, gui_parent=self) cmds.create_paraxial_design_view_v2(opt_model, 'ht', gui_parent=self) self.refresh_gui() @pyqtSlot(object, int) def on_data_changed(self, rootObj, index): self.refresh_gui()