Source code for crystal_builder_step.tk_crystal_builder

# -*- coding: utf-8 -*-

"""The graphical part of a Crystal Builder step"""

import fnmatch
import tkinter as tk
import tkinter.ttk as ttk

import seamm
import seamm_widgets as sw
import crystal_builder_step  # noqa: F401


[docs]class TkCrystalBuilder(seamm.TkNode): """ The graphical part of a Crystal Builder step in a flowchart. Attributes ---------- namespace : str The namespace of the current step. node : Node The corresponding node of the non-graphical flowchart dialog : Dialog The Pmw dialog object sub_tk_flowchart : TkFlowchart A graphical Flowchart representing a subflowchart self[widget] : dict A dictionary of tk widgets built using the information contained in Crystal Builder_parameters.py See Also -------- CrystalBuilder, TkCrystalBuilder, CrystalBuilderParameters, """ def __init__( self, tk_flowchart=None, node=None, canvas=None, x=None, y=None, w=200, h=50 ): """ Initialize a graphical node. Parameters ---------- tk_flowchart: Tk_Flowchart The graphical flowchart that we are in. node: Node The non-graphical node for this step. namespace: str The stevedore namespace for finding sub-nodes. canvas: Canvas The Tk canvas to draw on. x: float The x position of the nodes center on the canvas. y: float The y position of the nodes cetner on the canvas. w: float The nodes graphical width, in pixels. h: float The nodes graphical height, in pixels. """ self.dialog = None self._in_reset = False self._last_aflow_prototype = None self._last_prototype_group = None self._last_prototype = None self._last_n_sites = None self._last_n_elements = None self._last_spacegroup = None self._last_pearson_symbol = None super().__init__( tk_flowchart=tk_flowchart, node=node, canvas=canvas, x=x, y=y, w=w, h=h )
[docs] def create_dialog(self): """ Create the dialog. A set of widgets will be chosen by default based on what is specified in the Crystal Builder_parameters module. See Also -------- TkCrystalBuilder.reset_dialog """ frame = super().create_dialog(title="Crystal Builder") # Shortcut for parameters P = self.node.parameters # Create the frames for information, cell, and atom sites info_frame = self["info_frame"] = ttk.LabelFrame( self["frame"], borderwidth=4, relief="sunken", text="Information", labelanchor="n", padding=10, ) cell_frame = self["cell_frame"] = ttk.LabelFrame( self["frame"], borderwidth=4, relief="sunken", text="Cell", labelanchor="n", padding=10, ) self["site_frame"] = ttk.LabelFrame( self["frame"], borderwidth=4, relief="sunken", text="Atom Sites", labelanchor="n", padding=10, ) # The create the widgets for key in ( "prototype_group", "prototype", "n_sites", "n_elements", "spacegroup", "pearson_symbol", ): self[key] = P[key].widget(frame) self[key].bind("<<ComboboxSelected>>", self.reset_dialog) self[key].bind("<Return>", self.reset_dialog) self[key].bind("<FocusOut>", self.reset_dialog) for key in ("a", "b", "c", "alpha", "beta", "gamma"): self[key] = P[key].widget(cell_frame) # Patch up the spacegroups in the pulldown spacegroups = ["any"] spacegroups.extend(crystal_builder_step.spacegroups) self["spacegroup"].configure(values=spacegroups) for key in ( "AFLOW prototype", "Prototype", "# Elements", "# Sites", "# Atoms", "Pearson symbol", "Strukturbericht designation", "Space group", "Space group number", "Description", ): self["label " + key] = ttk.Label(info_frame, text=key + ":", anchor=tk.E) self["value " + key] = ttk.Label(info_frame, text="", anchor=tk.W) self["label AFLOW prototype"].grid(row=0, column=0, sticky=tk.E) self["value AFLOW prototype"].grid(row=0, column=1, sticky=tk.EW) self["label Space group"].grid(row=0, column=3, sticky=tk.E) self["value Space group"].grid(row=0, column=4, sticky=tk.EW) self["label # Elements"].grid(row=0, column=6, sticky=tk.E) self["value # Elements"].grid(row=0, column=7, sticky=tk.EW) self["label Prototype"].grid(row=1, column=0, sticky=tk.E) self["value Prototype"].grid(row=1, column=1, sticky=tk.EW) self["label Space group number"].grid(row=1, column=3, sticky=tk.E) self["value Space group number"].grid(row=1, column=4, sticky=tk.EW) self["label # Sites"].grid(row=1, column=6, sticky=tk.E) self["value # Sites"].grid(row=1, column=7, sticky=tk.EW) self["label Strukturbericht designation"].grid(row=2, column=0, sticky=tk.E) self["value Strukturbericht designation"].grid(row=2, column=1, sticky=tk.EW) self["label Pearson symbol"].grid(row=2, column=3, sticky=tk.E) self["value Pearson symbol"].grid(row=2, column=4, sticky=tk.EW) self["label # Atoms"].grid(row=2, column=6, sticky=tk.E) self["value # Atoms"].grid(row=2, column=7, sticky=tk.EW) self["label Description"].grid(row=5, column=0, sticky=tk.E) self["value Description"].grid(row=5, column=1, columnspan=7, sticky=tk.EW) info_frame.rowconfigure(4, minsize=10) info_frame.columnconfigure(2, weight=1, minsize=20) info_frame.columnconfigure(5, weight=1, minsize=20) # and lay them out self.reset_dialog()
[docs] def reset_dialog(self, widget=None): """Layout the widgets in the dialog. The widgets are chosen by default from the information in Crystal Builder_parameter. This function simply lays them out row by row with aligned labels. You may wish a more complicated layout that is controlled by values of some of the control parameters. Parameters ---------- widget See Also -------- TkCrystalBuilder.create_dialog """ # Not sure if this is needed, but prevents re-entering if self._in_reset: return prototype_group = self["prototype_group"].get() prototype = self["prototype"].get() n_sites = self["n_sites"].get() n_elements = self["n_elements"].get() spacegroup = self["spacegroup"].get() pearson_symbol = self["pearson_symbol"].get() # If nothing has changed, return if ( prototype_group == self._last_prototype_group and prototype == self._last_prototype and n_sites == self._last_n_sites and n_elements == self._last_n_elements and spacegroup == self._last_spacegroup and pearson_symbol == self._last_pearson_symbol ): return # Filter the prototypes try: spacegroup_number = int(spacegroup) except Exception: spacegroup_number = None self._tmp = {} prototypes = [] if prototype_group == "common": prototypes = [*crystal_builder_step.common_prototypes] self._tmp = { p: v for p, v in crystal_builder_step.common_prototypes.items() } self["prototype"].combobox.config(values=prototypes) elif prototype_group == "Strukturbericht": for aflow, data in crystal_builder_step.prototype_data.items(): struk = data["strukturbericht"] if struk is None: continue if n_sites != "any" and data["n_sites"] != int(n_sites): continue if n_elements != "any" and data["n_elements"] != int(n_elements): continue if pearson_symbol != "any" and not fnmatch.fnmatchcase( data["pearson_symbol"], pearson_symbol ): continue if spacegroup_number is None: if spacegroup != "any" and not fnmatch.fnmatchcase( data["simple_spacegroup"], spacegroup ): continue else: if data["spacegroup_number"] != spacegroup_number: continue key = f"{struk}: {data['description']}" prototypes.append(key) self._tmp[key] = aflow elif prototype_group == "prototype": for aflow, data in crystal_builder_step.prototype_data.items(): if n_sites != "any" and data["n_sites"] != int(n_sites): continue if n_elements != "any" and data["n_elements"] != int(n_elements): continue if pearson_symbol != "any" and not fnmatch.fnmatchcase( data["pearson_symbol"], pearson_symbol ): continue if spacegroup_number is None: if spacegroup != "any" and not fnmatch.fnmatchcase( data["simple_spacegroup"], spacegroup ): continue else: if data["spacegroup_number"] != spacegroup_number: continue key = f"{data['prototype']}: {data['description']}" prototypes.append(key) self._tmp[key] = aflow elif prototype_group == "description": for aflow, data in crystal_builder_step.prototype_data.items(): if n_sites != "any" and data["n_sites"] != int(n_sites): continue if n_elements != "any" and data["n_elements"] != int(n_elements): continue if pearson_symbol != "any" and not fnmatch.fnmatchcase( data["pearson_symbol"], pearson_symbol ): continue if spacegroup_number is None: if spacegroup != "any" and not fnmatch.fnmatchcase( data["simple_spacegroup"], spacegroup ): continue else: if data["spacegroup_number"] != spacegroup_number: continue key = f"{data['description']}" prototypes.append(key) self._tmp[key] = aflow else: for aflow, data in crystal_builder_step.prototype_data.items(): if n_sites != "any" and data["n_sites"] != int(n_sites): continue if n_elements != "any" and data["n_elements"] != int(n_elements): continue if pearson_symbol != "any" and not fnmatch.fnmatchcase( data["pearson_symbol"], pearson_symbol ): continue if spacegroup_number is None: if spacegroup != "any" and not fnmatch.fnmatchcase( data["simple_spacegroup"], spacegroup ): continue else: if data["spacegroup_number"] != spacegroup_number: continue key = f"{aflow}: {data['description']}" prototypes.append(key) self._tmp[key] = aflow if len(prototypes) == 0: self["prototype_group"].set(self._last_prototype_group) self["prototype"].set(self._last_prototype) self["n_sites"].set(self._last_n_sites) self["n_elements"].set(self._last_n_elements) self["spacegroup"].set(self._last_spacegroup) self["pearson_symbol"].set(self._last_pearson_symbol) tk.messagebox.showwarning( title="No matching prototypes!", message=( "The criteria you gave for filtering the prototypes were " "to strict. The last change was reversed." ), ) return # Save the control parameters self._last_prototype_group = prototype_group self._last_prototype = prototype self._last_n_sites = n_sites self._last_n_elements = n_elements self._last_spacegroup = spacegroup self._last_pearson_symbol = pearson_symbol self._in_reset = True # and proceed prototypes.sort() # Remove any widgets previously packed frame = self["frame"] for slave in frame.grid_slaves(): slave.grid_forget() for slave in self["cell_frame"].grid_slaves(): slave.grid_forget() for slave in self["site_frame"].grid_slaves(): slave.grid_forget() self["prototype"].combobox.config(values=prototypes) width = 80 self["prototype"].config(width=width) if prototype in prototypes: self["prototype"].set(prototype) else: self["prototype"].combobox.current(0) prototype = self["prototype"].get() self._tmp["AFLOW prototype"] = self._tmp[prototype] # Access the metadata aflow_prototype = self._tmp[prototype] cb_data = crystal_builder_step.prototype_data[aflow_prototype] # keep track of the row in a variable, so that the layout is flexible # if e.g. rows are skipped to control such as 'method' here row = 0 widgets = [] self["prototype_group"].grid(row=row, column=0, sticky=tk.EW) widgets.append(self["prototype_group"]) row += 1 if prototype_group != "common": for key in ("n_sites", "n_elements", "spacegroup", "pearson_symbol"): self[key].grid(row=row, column=0, sticky=tk.EW) widgets.append(self[key]) row += 1 self["prototype"].grid(row=row, column=0, sticky=tk.EW) widgets.append(self["prototype"]) row += 1 # Align the labels sw.align_labels(widgets) # The information about the crystal self["info_frame"].grid(row=row, column=0, sticky=tk.EW) row += 1 self["value AFLOW prototype"].configure(text=cb_data["aflow"]) self["value Prototype"].configure(text=cb_data["prototype"]) self["value # Elements"].configure(text=cb_data["n_elements"]) self["value # Sites"].configure(text=cb_data["n_sites"]) self["value # Atoms"].configure(text=cb_data["n_atoms"]) self["value Pearson symbol"].configure(text=cb_data["pearson_symbol"]) if cb_data["strukturbericht"] is None: text = "" else: text = cb_data["strukturbericht"] self["value Strukturbericht designation"].configure(text=text) self["value Space group"].configure(text=cb_data["spacegroup"]) self["value Space group number"].configure(text=cb_data["spacegroup_number"]) self["value Description"].configure(text=cb_data["description"]) # And now the cell parameters self["cell_frame"].grid(row=row, column=0, sticky=tk.EW) row += 1 cell_data = cb_data["cell"] site_data = cb_data["sites"] subrow = 0 widgets = [] for parameter, value in cell_data: w = self[parameter] w.grid(row=subrow, sticky=tk.EW) subrow += 1 if aflow_prototype != self._last_aflow_prototype: if parameter in ("a", "b", "c"): w.set(value, "Å") else: w.set(value, "degree") widgets.append(w) sw.align_labels(widgets) # And the sites sf = self["site_frame"] sf.grid(row=row, column=0, sticky=tk.EW) row += 1 subrow = 0 widgets = [] for site, mult, symbol, x, xmove, y, ymove, z, zmove in site_data: i = subrow + 1 key = f"site {i}" if key not in self: self[key] = sw.LabeledEntry(sf, labeltext=key) self["x " + key] = ttk.Entry(sf) self["y " + key] = ttk.Entry(sf) self["z " + key] = ttk.Entry(sf) w = self[key] w.grid(row=subrow, sticky=tk.EW) if aflow_prototype != self._last_aflow_prototype: w.set(symbol) label = f"Site {i} -- {mult}{site}:" w.config(labeltext=label) widgets.append(w) w = self["x " + key] w.configure(state=tk.NORMAL) w.delete(0, tk.END) w.insert(0, x) if not xmove: w.configure(state="disabled") w.grid(row=subrow, column=1, sticky=tk.EW) w = self["y " + key] w.configure(state=tk.NORMAL) w.delete(0, tk.END) w.insert(0, y) if not ymove: w.configure(state="disabled") w.grid(row=subrow, column=2, sticky=tk.EW) w = self["z " + key] w.configure(state=tk.NORMAL) w.delete(0, tk.END) w.insert(0, z) if not zmove: w.configure(state="disabled") w.grid(row=subrow, column=3, sticky=tk.EW) subrow += 1 sw.align_labels(widgets) # Remember the last prototype self._last_aflow_prototype = aflow_prototype self["frame"].grid_columnconfigure(0, weight=1, minsize=500) # All done resetting, so turn bindings back on. self._in_reset = False
[docs] def right_click(self, event): """ Handles the right click event on the node. See Also -------- TkCrystalBuilder.edit """ super().right_click(event) self.popup_menu.add_command(label="Edit..", command=self.edit) self.popup_menu.tk_popup(event.x_root, event.y_root, 0)
[docs] def edit(self): """Present a dialog for editing the Crystal Builder input See Also -------- TkCrystalBuilder.right_click """ if self.dialog is None: P = self.node.parameters self._last_aflow_prototype = P["AFLOW prototype"].value self.create_dialog() self.dialog.activate(geometry="centerscreenfirst")
[docs] def handle_dialog(self, result): """Handle the closing of the edit dialog What to do depends on the button used to close the dialog. If the user closes it by clicking the 'x' of the dialog window, None is returned, which we take as equivalent to cancel. Parameters ---------- result : None or str The value of this variable depends on what the button the user clicked. """ if result is None or result == "Cancel": self.dialog.deactivate(result) return if result == "Help": # display help!!! return if result != "OK": self.dialog.deactivate(result) raise RuntimeError("Don't recognize dialog result '{}'".format(result)) self.dialog.deactivate(result) # Shortcut for parameters P = self.node.parameters # Get the values for all the widgets. This may be overkill, but # it is easy! You can sort out what it all means later, or # be a bit more selective. for key in P: if key not in ("coordinates", "elements"): P[key].set_from_widget() P["AFLOW prototype"].set(self._tmp["AFLOW prototype"]) aflow_prototype = self._last_aflow_prototype cb_data = crystal_builder_step.prototype_data[aflow_prototype] site_data = cb_data["sites"] i = 0 elements = [] coords = [] for site, mult, symbol, x, xmove, y, ymove, z, zmove in site_data: i += 1 key = f"site {i}" elements.append(self[key].get()) newx = self["x " + key].get() newy = self["y " + key].get() newz = self["z " + key].get() coords.append([newx, newy, newz]) P["coordinates"].set(coords) P["elements"].set(elements) self._tmp = {}
[docs] def handle_help(self): """Shows the help to the user when click on help button.""" print("Help not implemented yet for Crystal Builder!")