Coverage for larch/wxlib/cif_browser.py: 11%
570 statements
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2024-10-16 21:04 +0000
1#!/usr/bin/env python
2"""
3Browse CIF Files, maybe run Feff
4"""
6import os
7import sys
8import time
9import copy
10# from threading import Thread
11import numpy as np
12np.seterr(all='ignore')
13from pathlib import Path
14from functools import partial
15import wx
16import wx.lib.scrolledpanel as scrolled
17import wx.lib.agw.flatnotebook as fnb
18from wx.adv import AboutBox, AboutDialogInfo
19from matplotlib.ticker import FuncFormatter
21from wxmplot import PlotPanel
22from xraydb.chemparser import chemparse
23from xraydb import atomic_number
25import larch
26from larch import Group
27from larch.xafs import feff8l, feff6l
28from larch.xrd.cif2feff import cif_sites
29from larch.utils import read_textfile, mkdir
30from larch.utils.paths import unixpath
31from larch.utils.strutils import fix_filename, unique_name, strict_ascii
32from larch.site_config import user_larchdir
34from larch.wxlib import (LarchFrame, FloatSpin, EditableListBox,
35 FloatCtrl, SetTip, get_icon, SimpleText, pack,
36 Button, Popup, HLine, FileSave, FileOpen, Choice,
37 Check, MenuItem, CEN, LEFT, FRAMESTYLE,
38 Font, FONTSIZE, flatnotebook, LarchUpdaterDialog,
39 PeriodicTablePanel, FeffResultsPanel, LarchWxApp,
40 ExceptionPopup, set_color)
42from larch.xrd import CifStructure, get_amcsd, find_cifs, get_cif, parse_cif_file
44LEFT = wx.ALIGN_LEFT
45CEN |= wx.ALL
46FNB_STYLE = fnb.FNB_NO_X_BUTTON|fnb.FNB_SMART_TABS
47FNB_STYLE |= fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NODRAG
49MAINSIZE = (1150, 650)
51class CIFFrame(wx.Frame):
52 _about = """Larch Crystallographic Information File Browser
53 Data from American Mineralogist Crystal Structure Database
55 Matt Newville <newville @ cars.uchicago.edu>
56 """
58 def __init__(self, parent=None, _larch=None, with_feff=False,
59 with_fdmnes=False, usecif_callback=None, path_importer=None,
60 filename=None, **kws):
62 wx.Frame.__init__(self, parent, -1, size=MAINSIZE, style=FRAMESTYLE)
64 title = "Larch American Mineralogist CIF Browser"
65 self.with_feff = with_feff
66 self.with_fdmnes = with_fdmnes
67 self.usecif_callback = usecif_callback
68 self.larch = _larch
69 if _larch is None:
70 self.larch = larch.Interpreter()
71 self.larch.eval("# started CIF browser\n")
73 self.path_importer = path_importer
74 self.cifdb = get_amcsd()
75 self.all_minerals = self.cifdb.all_minerals()
76 self.subframes = {}
77 self.has_xrd1d = False
78 self.xrd1d_thread = None
79 self.current_cif = None
80 self.SetTitle(title)
81 self.SetSize(MAINSIZE)
82 self.SetFont(Font(FONTSIZE))
84 self.createMainPanel()
85 self.createMenus()
87 if with_feff:
88 self.larch.eval("if not hasattr('_sys', '_feffruns'): _sys._feffruns = {}")
89 self.feff_folder = unixpath(Path(user_larchdir, 'feff'))
90 mkdir(self.feff_folder)
91 self.feffruns_list = []
92 for fname in os.listdir(self.feff_folder):
93 full = Path(self.feff_folder, fname).absolute()
94 if full.is_dir():
95 self.feffruns_list.append(fname)
97 self.statusbar = self.CreateStatusBar(2, style=wx.STB_DEFAULT_STYLE)
98 self.statusbar.SetStatusWidths([-3, -1])
99 statusbar_fields = [" ", ""]
100 for i in range(len(statusbar_fields)):
101 self.statusbar.SetStatusText(statusbar_fields[i], i)
102 self.Show()
104 def createMainPanel(self):
105 display0 = wx.Display(0)
106 client_area = display0.ClientArea
107 xmin, ymin, xmax, ymax = client_area
108 xpos = int((xmax-xmin)*0.07) + xmin
109 ypos = int((ymax-ymin)*0.09) + ymin
110 self.SetPosition((xpos, ypos))
112 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
113 splitter.SetMinimumPaneSize(250)
115 leftpanel = wx.Panel(splitter, size=(375, -1))
116 self.ciflist = EditableListBox(leftpanel, self.onShowCIF, size=(375, -1))
117 set_color(self.ciflist, 'list_fg', bg='list_bg')
118 self.cif_selections = {}
120 sizer = wx.BoxSizer(wx.VERTICAL)
121 sizer.Add(self.ciflist, 1, LEFT|wx.GROW|wx.ALL, 1)
122 pack(leftpanel, sizer)
124 # right hand side
125 rightpanel = scrolled.ScrolledPanel(splitter)
126 panel = wx.Panel(rightpanel, size=(725, -1))
128 self.ciflist.SetMinSize((375, 250))
129 rightpanel.SetMinSize((400, 250))
131 sizer = wx.GridBagSizer(2, 2)
133 self.title = SimpleText(panel, 'Search American Mineralogical CIF Database:',
134 size=(700, -1), style=LEFT)
135 self.title.SetFont(Font(FONTSIZE+2))
136 wids = self.wids = {}
138 minlab = SimpleText(panel, ' Mineral Name: ')
139 minhint= SimpleText(panel, ' example: hem* ')
140 wids['mineral'] = wx.TextCtrl(panel, value='', size=(250, -1),
141 style=wx.TE_PROCESS_ENTER)
142 wids['mineral'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
144 authlab = SimpleText(panel, ' Author Name: ')
145 wids['author'] = wx.TextCtrl(panel, value='', size=(250, -1),
146 style=wx.TE_PROCESS_ENTER)
147 wids['author'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
149 journlab = SimpleText(panel, ' Journal Name: ')
150 wids['journal'] = wx.TextCtrl(panel, value='', size=(250, -1),
151 style=wx.TE_PROCESS_ENTER)
152 wids['journal'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
154 elemlab = SimpleText(panel, ' Include Elements: ')
155 elemhint= SimpleText(panel, ' example: O, Fe, Si ')
157 wids['contains_elements'] = wx.TextCtrl(panel, value='', size=(250, -1),
158 style=wx.TE_PROCESS_ENTER)
159 wids['contains_elements'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
161 exelemlab = SimpleText(panel, ' Exclude Elements: ')
162 wids['excludes_elements'] = wx.TextCtrl(panel, value='', size=(250, -1),
163 style=wx.TE_PROCESS_ENTER)
164 wids['excludes_elements'].Bind(wx.EVT_TEXT_ENTER, self.onSearch)
166 wids['excludes_elements'].Enable()
167 wids['strict_contains'] = Check(panel, default=False,
168 label='Include only the elements listed',
169 action=self.onStrict)
171 wids['full_occupancy'] = Check(panel, default=False,
172 label='Only Structures with Full Occupancy')
174 wids['search'] = Button(panel, 'Search for CIFs', action=self.onSearch)
177 ir = 0
178 sizer.Add(self.title, (0, 0), (1, 6), LEFT, 2)
180 ir += 1
181 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 6), LEFT, 3)
183 ir += 1
184 sizer.Add(minlab, (ir, 0), (1, 1), LEFT, 3)
185 sizer.Add(wids['mineral'], (ir, 1), (1, 3), LEFT, 3)
186 sizer.Add(minhint, (ir, 4), (1, 2), LEFT, 3)
187 ir += 1
188 sizer.Add(authlab, (ir, 0), (1, 1), LEFT, 3)
189 sizer.Add(wids['author'], (ir, 1), (1, 3), LEFT, 3)
191 ir += 1
192 sizer.Add(journlab, (ir, 0), (1, 1), LEFT, 3)
193 sizer.Add(wids['journal'], (ir, 1), (1, 3), LEFT, 3)
195 ir += 1
196 sizer.Add(elemlab, (ir, 0), (1, 1), LEFT, 3)
197 sizer.Add(wids['contains_elements'], (ir, 1), (1, 3), LEFT, 3)
198 sizer.Add(elemhint, (ir, 4), (1, 3), LEFT, 2)
200 ir += 1
201 sizer.Add(exelemlab, (ir, 0), (1, 1), LEFT, 3)
202 sizer.Add(wids['excludes_elements'], (ir, 1), (1, 3), LEFT, 3)
204 ir += 1
205 sizer.Add(wids['search'], (ir, 0), (1, 1), LEFT, 3)
206 sizer.Add(wids['strict_contains'], (ir, 1), (1, 4), LEFT, 3)
208 ir += 1
209 sizer.Add(wids['full_occupancy'], (ir, 1), (1, 4), LEFT, 3)
211 #
212 if self.with_feff:
213 wids['feff_runfolder'] = wx.TextCtrl(panel, value='calc1', size=(250, -1))
214 wids['feff_runbutton'] = Button(panel, ' Run Feff ', action=self.onRunFeff)
215 wids['feff_runbutton'].Disable()
216 wids['feff_without_h'] = Check(panel, default=True, label='Remove H atoms',
217 action=self.onGetFeff)
220 wids['feff_central_atom'] = Choice(panel, choices=['<empty>'], size=(80, -1),
221 action=self.onFeffCentralAtom)
222 wids['feff_edge'] = Choice(panel, choices=['K', 'L3', 'L2', 'L1',
223 'M5', 'M4'],
224 size=(80, -1),
225 action=self.onGetFeff)
227 wids['feffvers'] = Choice(panel, choices=['6', '8'], default=1,
228 size=(80, -1),
229 action=self.onGetFeff)
230 wids['feff_site'] = Choice(panel, choices=['1', '2', '3', '4'],
231 size=(80, -1),
232 action=self.onGetFeff)
233 wids['feff_cluster_size'] = FloatSpin(panel, value=7.0, digits=2,
234 increment=0.1, max_val=10,
235 action=self.onGetFeff)
236 wids['feff_central_atom'].Disable()
237 wids['feff_edge'].Disable()
238 wids['feff_cluster_size'].Disable()
240 ir += 1
241 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 6), LEFT, 3)
243 ir += 1
245 sizer.Add(SimpleText(panel, ' Absorbing Atom: '), (ir, 0), (1, 1), LEFT, 3)
246 sizer.Add(wids['feff_central_atom'], (ir, 1), (1, 1), LEFT, 3)
247 sizer.Add(SimpleText(panel, ' Crystal Site: '), (ir, 2), (1, 1), LEFT, 3)
248 sizer.Add(wids['feff_site'], (ir, 3), (1, 1), LEFT, 3)
249 sizer.Add(SimpleText(panel, ' Edge: '), (ir, 4), (1, 1), LEFT, 3)
250 sizer.Add(wids['feff_edge'], (ir, 5), (1, 1), LEFT, 3)
252 ir += 1
253 sizer.Add(SimpleText(panel, ' Cluster Size (\u212B): '), (ir, 0), (1, 1), LEFT, 3)
254 sizer.Add(wids['feff_cluster_size'], (ir, 1), (1, 1), LEFT, 3)
255 sizer.Add(SimpleText(panel, ' Feff Version:'), (ir, 2), (1, 1), LEFT, 3)
256 sizer.Add(wids['feffvers'], (ir, 3), (1, 1), LEFT, 3)
257 sizer.Add(wids['feff_without_h'], (ir, 4), (1, 2), LEFT, 3)
259 ir += 1
260 sizer.Add(SimpleText(panel, ' Feff Folder: '), (ir, 0), (1, 1), LEFT, 3)
261 sizer.Add(wids['feff_runfolder'], (ir, 1), (1, 4), LEFT, 3)
262 sizer.Add(wids['feff_runbutton'], (ir, 5), (1, 1), LEFT, 3)
264 if self.usecif_callback is not None:
265 wids['cif_use_button'] = Button(panel, ' Use This CIF', action=self.onUseCIF)
266 wids['cif_use_button'].Disable()
268 ir += 1
269 sizer.Add(wids['cif_use_button'], (ir, 5), (1, 1), LEFT, 3)
272 ir += 1
273 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 6), LEFT, 3)
275 pack(panel, sizer)
277 self.nb = flatnotebook(rightpanel, {}, on_change=self.onNBChanged)
280 def _swallow_plot_messages(s, panel=0):
281 pass
283 self.plotpanel = PlotPanel(rightpanel, messenger=_swallow_plot_messages)
284 try:
285 plotopts = self.larch.symtable._sys.wx.plotopts
286 self.plotpanel.conf.set_theme(plotopts['theme'])
287 self.plotpanel.conf.enable_grid(plotopts['show_grid'])
288 except:
289 pass
291 self.plotpanel.SetMinSize((250, 250))
292 self.plotpanel.SetMaxSize((675, 400))
293 self.plotpanel.onPanelExposed = self.showXRD1D
295 cif_panel = wx.Panel(rightpanel)
296 wids['cif_text'] = wx.TextCtrl(cif_panel, value='<CIF TEXT>',
297 style=wx.TE_MULTILINE|wx.TE_READONLY,
298 size=(700, 450))
299 wids['cif_text'].SetFont(Font(FONTSIZE+1))
300 cif_sizer = wx.BoxSizer(wx.VERTICAL)
301 cif_sizer.Add(wids['cif_text'], 0, LEFT, 1)
302 pack(cif_panel, cif_sizer)
305 self.nbpages = []
306 for label, page in (('CIF Text', cif_panel),
307 ('1-D XRD Pattern', self.plotpanel),
308 ):
309 self.nb.AddPage(page, label, True)
310 self.nbpages.append((label, page))
312 if self.with_feff:
313 self.feffresults = FeffResultsPanel(rightpanel,
314 path_importer=self.path_importer,
315 _larch=self.larch)
317 feffinp_panel = wx.Panel(rightpanel)
318 wids['feff_text'] = wx.TextCtrl(feffinp_panel,
319 value='<Feff Input Text>',
320 style=wx.TE_MULTILINE,
321 size=(700, 450))
322 wids['feff_text'].CanCopy()
324 feffinp_panel.onPanelExposed = self.onGetFeff
325 wids['feff_text'].SetFont(Font(FONTSIZE+1))
326 feff_sizer = wx.BoxSizer(wx.VERTICAL)
327 feff_sizer.Add(wids['feff_text'], 0, LEFT, 1)
328 pack(feffinp_panel, feff_sizer)
330 feffout_panel = wx.Panel(rightpanel)
331 wids['feffout_text'] = wx.TextCtrl(feffout_panel,
332 value='<Feff Output>',
333 style=wx.TE_MULTILINE,
334 size=(700, 450))
335 wids['feffout_text'].CanCopy()
336 wids['feffout_text'].SetFont(Font(FONTSIZE+1))
337 feffout_sizer = wx.BoxSizer(wx.VERTICAL)
338 feffout_sizer.Add(wids['feffout_text'], 0, LEFT, 1)
339 pack(feffout_panel, feffout_sizer)
341 for label, page in (('Feff Input Text', feffinp_panel),
342 ('Feff Output Text', feffout_panel),
343 ('Feff Results', self.feffresults),
344 ):
345 self.nb.AddPage(page, label, True)
346 self.nbpages.append((label, page))
347 self.nb.SetSelection(0)
349 r_sizer = wx.BoxSizer(wx.VERTICAL)
350 r_sizer.Add(panel, 0, LEFT|wx.GROW|wx.ALL)
351 r_sizer.Add(self.nb, 1, LEFT|wx.GROW, 2)
353 pack(rightpanel, r_sizer)
354 rightpanel.SetupScrolling()
355 splitter.SplitVertically(leftpanel, rightpanel, 1)
358 def get_nbpage(self, name):
359 "get nb page by name"
360 name = name.lower()
361 for i, dat in enumerate(self.nbpages):
362 label, page = dat
363 if name in label.lower():
364 return i, page
365 return (0, self.nbpages[0][1])
367 def onStrict(self, event=None):
368 strict = self.wids['strict_contains'].IsChecked()
369 self.wids['excludes_elements'].Enable(not strict)
371 def onSearch(self, event=None):
372 mineral_name = self.wids['mineral'].GetValue().strip()
373 if len(mineral_name) < 1:
374 mineral_name = None
375 author_name = self.wids['author'].GetValue().strip()
376 if len(author_name) < 1:
377 author_name = None
378 journal_name = self.wids['journal'].GetValue().strip()
379 if len(journal_name) < 1:
380 journal_name = None
381 contains_elements = self.wids['contains_elements'].GetValue().strip()
382 if len(contains_elements) < 1:
383 contains_elements = None
384 else:
385 contains_elements = [a.strip().title() for a in contains_elements.split(',')]
386 excludes_elements = self.wids['excludes_elements'].GetValue().strip()
387 if len(excludes_elements) < 1:
388 excludes_elements = None
389 else:
390 excludes_elements = [a.strip().title() for a in excludes_elements.split(',')]
391 strict_contains = self.wids['strict_contains'].IsChecked()
392 full_occupancy = self.wids['full_occupancy'].IsChecked()
393 all_cifs = find_cifs(mineral_name=mineral_name,
394 journal_name=journal_name,
395 author_name=author_name,
396 contains_elements=contains_elements,
397 excludes_elements=excludes_elements,
398 strict_contains=strict_contains,
399 full_occupancy=full_occupancy)
400 if len(all_cifs) == 0:
401 all_cifs = find_cifs(mineral_name=mineral_name + '*',
402 journal_name=journal_name,
403 author_name=author_name,
404 contains_elements=contains_elements,
405 excludes_elements=excludes_elements,
406 strict_contains=strict_contains,
407 full_occupancy=full_occupancy)
408 self.cif_selections = {}
409 self.ciflist.Clear()
410 for cif in all_cifs:
411 try:
412 label = cif.formula.replace(' ', '')
413 mineral = cif.get_mineralname()
414 year = cif.publication.year
415 journal= cif.publication.journalname
416 cid = cif.ams_id
417 label = f'{label}: {mineral}, {year} {journal} [{cid}]'
418 except:
419 label = None
420 if label is not None:
421 if label in self.cif_selections:
422 lorig, n = label, 1
423 while label in self.cif_selections and n < 10:
424 n += 1
425 label = f'{lorig} (v{n})'
427 self.cif_selections[label] = cif.ams_id
428 self.ciflist.Append(label)
430 def onShowCIF(self, event=None, cif_id=None):
431 if cif_id is not None:
432 cif = get_cif(cif_id)
433 self.cif_label = '%d' % cif_id
434 elif event is not None:
435 self.cif_label = event.GetString()
436 cif = get_cif(self.cif_selections[self.cif_label])
437 self.current_cif = cif
438 self.has_xrd1d = False
439 self.wids['cif_text'].SetValue(cif.ciftext)
441 if self.with_feff:
442 elems = chemparse(cif.formula.replace(' ', ''))
443 self.wids['feff_central_atom'].Enable()
444 self.wids['feff_edge'].Enable()
445 self.wids['feff_cluster_size'].Enable()
447 self.wids['feff_central_atom'].Clear()
448 self.wids['feff_central_atom'].AppendItems(list(elems.keys()))
449 self.wids['feff_central_atom'].Select(0)
451 el0 = list(elems.keys())[0]
452 edge_val = 'K' if atomic_number(el0) < 60 else 'L3'
453 self.wids['feff_edge'].SetStringSelection(edge_val)
455 sites = cif_sites(cif.ciftext, absorber=el0)
456 try:
457 sites = ['%d' % (i+1) for i in range(len(sites))]
458 except:
459 title = "Could not make sense of atomic sites"
460 message = [f"Elements: {list(elems.keys())}",
461 f"Sites: {sites}"]
462 ExceptionPopup(self, title, message)
464 self.wids['feff_site'].Clear()
465 self.wids['feff_site'].AppendItems(sites)
466 self.wids['feff_site'].Select(0)
468 if self.usecif_callback is not None:
469 self.wids['cif_use_button'].Enable()
471 i, p = self.get_nbpage('CIF Text')
472 self.nb.SetSelection(i)
474 def onUseCIF(self, event=None):
475 if self.usecif_callback is not None:
476 self.usecif_callback(cif=self.current_cif)
479 def onFeffCentralAtom(self, event=None):
480 cif = self.current_cif
481 if cif is None:
482 return
483 catom = event.GetString()
484 try:
485 sites = cif_sites(cif.ciftext, absorber=catom)
486 sites = ['%d' % (i+1) for i in range(len(sites))]
487 self.wids['feff_site'].Clear()
488 self.wids['feff_site'].AppendItems(sites)
489 self.wids['feff_site'].Select(0)
490 except:
491 self.write_message(f"could not get sites for central atom '{catom}'")
492 title = f"Could not get sites for central atom '{catom}'"
493 message = []
494 ExceptionPopup(self, title, message)
496 edge_val = 'K' if atomic_number(catom) < 60 else 'L3'
497 self.wids['feff_edge'].SetStringSelection(edge_val)
498 self.onGetFeff()
500 def onGetFeff(self, event=None):
501 cif = self.current_cif
502 if cif is None or not self.with_feff:
503 return
505 edge = self.wids['feff_edge'].GetStringSelection()
506 version8 = '8' == self.wids['feffvers'].GetStringSelection()
507 catom = self.wids['feff_central_atom'].GetStringSelection()
508 asite = int(self.wids['feff_site'].GetStringSelection())
509 csize = self.wids['feff_cluster_size'].GetValue()
510 with_h = not self.wids['feff_without_h'].IsChecked()
511 mineral = cif.get_mineralname()
512 folder = f'{catom:s}{asite:d}_{edge:s}_{mineral}_cif{cif.ams_id:d}'
513 folder = unique_name(fix_filename(folder), self.feffruns_list)
515 fefftext = cif.get_feffinp(catom, edge=edge, cluster_size=csize,
516 absorber_site=asite, version8=version8,
517 with_h=with_h)
519 self.wids['feff_runfolder'].SetValue(folder)
520 self.wids['feff_text'].SetValue(fefftext)
521 self.wids['feff_runbutton'].Enable()
522 i, p = self.get_nbpage('Feff Input')
523 self.nb.SetSelection(i)
525 def onRunFeff(self, event=None):
526 fefftext = self.wids['feff_text'].GetValue()
527 if len(fefftext) < 100 or 'ATOMS' not in fefftext or not self.with_feff:
528 return
530 ciftext = self.wids['cif_text'].GetValue()
531 cif = self.current_cif
532 cif_fname = None
533 if cif is not None and len(ciftext) > 100:
534 mineral = cif.get_mineralname()
535 cif_fname = f'{mineral}_cif{cif.ams_id:d}.cif'
537 # cc = self.current_cif
538 # edge = self.wids['feff_edge'].GetStringSelection()
539 # catom = self.wids['feff_central_atom'].GetStringSelection()
540 # asite = int(self.wids['feff_site'].GetStringSelection())
541 # mineral = cc.get_mineralname()
542 # folder = f'{catom:s}{asite:d}_{edge:s}_{mineral}_cif{cc.ams_id:d}'
543 # folder = unixpath(Path(self.feff_folder, folder))
544 version8 = '8' == self.wids['feffvers'].GetStringSelection()
546 fname = self.wids['feff_runfolder'].GetValue()
547 fname = unique_name(fix_filename(fname), self.feffruns_list)
548 self.feffruns_list.append(fname)
549 self.folder = folder = unixpath(Path(self.feff_folder, fname))
550 mkdir(self.folder)
551 ix, p = self.get_nbpage('Feff Output')
552 self.nb.SetSelection(ix)
554 out = self.wids['feffout_text']
555 out.Clear()
556 out.SetInsertionPoint(0)
557 out.WriteText(f'########\n###\n# Run Feff in folder: {folder}\n')
558 out.SetInsertionPoint(out.GetLastPosition())
559 out.WriteText('###\n########\n')
560 out.SetInsertionPoint(out.GetLastPosition())
562 fname = unixpath(Path(folder, 'feff.inp').absolute())
563 with open(fname, 'w', encoding=sys.getdefaultencoding()) as fh:
564 fh.write(strict_ascii(fefftext))
566 if cif_fname is not None:
567 cname = unixpath(Path(folder, fix_filename(cif_fname)))
568 with open(cname, 'w', encoding=sys.getdefaultencoding()) as fh:
569 fh.write(strict_ascii(ciftext))
570 wx.CallAfter(self.run_feff, folder, version8=version8)
572 def run_feff(self, folder=None, version8=True):
573 # print("RUN FEFF ", folder)
574 dname = Path(folder).name
575 prog, cmd = feff8l, 'feff8l'
576 if not version8:
577 prog, cmd = feff6l, 'feff6l'
578 command = f"{cmd:s}(folder='{folder:s}')"
579 self.larch.eval(f"## running Feff as:\n# {command:s}\n##\n")
581 prog(folder=folder, message_writer=self.feff_output)
582 self.larch.eval("## gathering results:\n")
583 self.larch.eval(f"_sys._feffruns['{dname:s}'] = get_feff_pathinfo('{folder:s}')")
584 this_feffrun = self.larch.symtable._sys._feffruns[f'{dname:s}']
585 self.feffresults.set_feffresult(this_feffrun)
586 ix, p = self.get_nbpage('Feff Results')
587 self.nb.SetSelection(ix)
589 # clean up unused, intermediate Feff files
590 for fname in os.listdir(folder):
591 if (fname.endswith('.json') or fname.endswith('.pad') or
592 fname.endswith('.bin') or fname.startswith('log') or
593 fname in ('chi.dat', 'xmu.dat', 'misc.dat')):
594 os.unlink(unixpath(Path(folder, fname).absolute()))
596 def feff_output(self, text):
597 out = self.wids['feffout_text']
598 ix, p = self.get_nbpage('Feff Output')
599 self.nb.SetSelection(ix)
600 pos0 = out.GetLastPosition()
601 if not text.endswith('\n'):
602 text = '%s\n' % text
603 out.WriteText(text)
604 out.SetInsertionPoint(out.GetLastPosition())
605 out.Update()
606 out.Refresh()
608 def onExportFeff(self, event=None):
609 if self.current_cif is None:
610 return
611 fefftext = self.wids['feff_text'].GetValue()
612 if len(fefftext) < 20:
613 return
614 cc = self.current_cif
615 minname = cc.get_mineralname()
616 fname = f'{minname}_cif{cc.ams_id:d}_feff.inp'
617 wildcard = 'Feff Inut files (*.inp)|*.inp|All files (*.*)|*.*'
618 path = FileSave(self, message='Save Feff File',
619 wildcard=wildcard,
620 default_file=fname)
621 path = unixpath(path)
622 if path is not None:
623 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
624 fh.write(fefftext)
625 self.write_message("Wrote Feff file %s" % path, 0)
628 def onExportCIF(self, event=None):
629 if self.current_cif is None:
630 return
631 cc = self.current_cif
632 minname = cc.get_mineralname()
633 fname = f'{minname}_cif{cc.ams_id:d}.cif'
634 wildcard = 'CIF files (*.cif)|*.cif|All files (*.*)|*.*'
635 path = FileSave(self, message='Save CIF File',
636 wildcard=wildcard,
637 default_file=fname)
638 path = unixpath(path)
639 if path is not None:
640 with open(path, 'w', encoding=sys.getdefaultencoding()) as fh:
641 fh.write(cc.ciftext)
642 self.write_message("Wrote CIF file %s" % path, 0)
644 def onImportCIF(self, event=None):
645 wildcard = 'CIF files (*.cif)|*.cif|All files (*.*)|*.*'
646 path = FileOpen(self, message='Open CIF File',
647 wildcard=wildcard, default_file='My.cif')
648 path = unixpath(path)
649 if path is not None:
650 try:
651 cif_data = parse_cif_file(path)
652 except:
653 title = f"Cannot parse CIF file '{path}'"
654 message = [f"Error reading CIF File: {path}"]
655 ExceptionPopup(self, title, message)
656 return
658 try:
659 cif_id = self.cifdb.add_ciffile(path)
660 except:
661 title = f"Cannot add CIF from '{path}' to CIF database"
662 message = [f"Error adding CIF File to database: {path}"]
663 ExceptionPopup(self, title, message)
664 return
666 try:
667 self.onShowCIF(cif_id=cif_id)
668 except:
669 title = f"Cannot show CIF from '{path}'"
670 message = [f"Error displaying CIF File: {path}"]
671 ExceptionPopup(self, title, message)
673 def onImportFeff(self, event=None):
674 if not self.with_feff:
675 return
676 wildcard = 'Feff input files (*.inp)|*.inp|All files (*.*)|*.*'
677 path = FileOpen(self, message='Open Feff Input File',
678 wildcard=wildcard, default_file='feff.inp')
679 path = unixpath(path)
680 if path is not None:
681 fefftext = None
682 fname = Path(path).name
683 fname = fname.replace('.inp', '_run')
684 fname = unique_name(fix_filename(fname), self.feffruns_list)
685 fefftext = read_textfile(path)
686 if fefftext is not None:
687 self.wids['feff_text'].SetValue(fefftext)
688 self.wids['feff_runfolder'].SetValue(fname)
689 self.wids['feff_runbutton'].Enable()
690 i, p = self.get_nbpage('Feff Input')
691 self.nb.SetSelection(i)
693 def onFeffFolder(self, eventa=None):
694 "prompt for Feff Folder"
695 dlg = wx.DirDialog(self, 'Select Main Folder for Feff Calculations',
696 style=wx.DD_DEFAULT_STYLE|wx.DD_CHANGE_DIR)
698 dlg.SetPath(self.feff_folder)
699 if dlg.ShowModal() == wx.ID_CANCEL:
700 return None
701 self.feff_folder = Path(dlg.GetPath()).absolute().as_posix()
702 mkdir(self.feff_folder)
704 def onNBChanged(self, event=None):
705 callback = getattr(self.nb.GetCurrentPage(), 'onPanelExposed', None)
706 if callable(callback):
707 callback()
709 def showXRD1D(self, event=None):
710 if self.has_xrd1d or self.current_cif is None:
711 return
713 def display_xrd1d():
714 t0 = time.time()
715 sfact = self.current_cif.get_structure_factors()
716 try:
717 self.cifdb.set_hkls(self.current_cif.ams_id, sfact.hkls)
718 except:
719 pass
721 max_ = sfact.intensity.max()
722 mask = np.where(sfact.intensity>max_/10.0)[0]
723 qval = sfact.q[mask]
724 ival = sfact.intensity[mask]
725 ival = ival/(1.0*ival.max())
727 def qd_formatter(q, pos):
728 qval = float(q)
729 dval = '\n[%.2f]' % (2*np.pi/max(qval, 1.e-6))
730 return r"%.2f%s" % (qval, dval)
732 qd_label = r'$Q\rm\,(\AA^{-1}) \,\> [d \rm\,(\AA)]$'
733 title = self.cif_label + '\n' + '(cif %d)' % (self.current_cif.ams_id)
734 ppan = self.plotpanel
735 ppan.plot(qval, ival, linewidth=0, marker='o', markersize=2,
736 xlabel=qd_label, ylabel='Relative Intensity',
737 title=title, titlefontsize=8, delay_draw=True)
739 ppan.axes.bar(qval, ival, 0.1, color='blue')
740 ppan.axes.xaxis.set_major_formatter(FuncFormatter(qd_formatter))
741 ppan.canvas.draw()
742 self.has_xrd1d = True
744 display_xrd1d()
745# self.xrd1d_thread = Thread(target=display_xrd1d)
746# self.xrd1d_thread.start()
747# time.sleep(0.25)
748# self.xrd1d_thread.join()
751 def onSelAll(self, event=None):
752 self.controller.filelist.select_all()
754 def onSelNone(self, event=None):
755 self.controller.filelist.select_none()
757 def write_message(self, msg, panel=0):
758 """write a message to the Status Bar"""
759 self.statusbar.SetStatusText(msg, panel)
761 def createMenus(self):
762 # ppnl = self.plotpanel
763 self.menubar = wx.MenuBar()
764 fmenu = wx.Menu()
765 group_menu = wx.Menu()
766 data_menu = wx.Menu()
767 ppeak_menu = wx.Menu()
768 m = {}
770 MenuItem(self, fmenu, "&Open CIF File\tCtrl+O",
771 "Open CIF File", self.onImportCIF)
773 MenuItem(self, fmenu, "&Save CIF File\tCtrl+S",
774 "Save CIF File", self.onExportCIF)
776 MenuItem(self, fmenu, "Open Feff Input File",
777 "Open Feff input File", self.onImportFeff)
779 MenuItem(self, fmenu, "Save &Feff Inp File\tCtrl+F",
780 "Save Feff6 File", self.onExportFeff)
782 fmenu.AppendSeparator()
783 MenuItem(self, fmenu, "Select Main Feff Folder",
784 "Select Main Folder for running Feff",
785 self.onFeffFolder)
786 fmenu.AppendSeparator()
787 MenuItem(self, fmenu, "Quit", "Exit", self.onClose)
789 self.menubar.Append(fmenu, "&File")
791 self.SetMenuBar(self.menubar)
792 self.Bind(wx.EVT_CLOSE, self.onClose)
794 def onClose(self, event=None):
795 self.Destroy()
798class CIFViewer(LarchWxApp):
799 def __init__(self, filename=None, version_info=None, **kws):
800 self.filename = filename
801 LarchWxApp.__init__(self, version_info=version_info, **kws)
803 def createApp(self):
804 frame = CIFFrame(filename=self.filename,
805 version_info=self.version_info)
806 self.SetTopWindow(frame)
807 return True
809def cif_viewer(**kws):
810 CIFViewer(**kws)
812if __name__ == '__main__':
813 CIFViewer().MainLoop()