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

1#!/usr/bin/env python 

2""" 

3Browse CIF Files, maybe run Feff 

4""" 

5 

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 

20 

21from wxmplot import PlotPanel 

22from xraydb.chemparser import chemparse 

23from xraydb import atomic_number 

24 

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 

33 

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) 

41 

42from larch.xrd import CifStructure, get_amcsd, find_cifs, get_cif, parse_cif_file 

43 

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 

48 

49MAINSIZE = (1150, 650) 

50 

51class CIFFrame(wx.Frame): 

52 _about = """Larch Crystallographic Information File Browser 

53 Data from American Mineralogist Crystal Structure Database 

54 

55 Matt Newville <newville @ cars.uchicago.edu> 

56 """ 

57 

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): 

61 

62 wx.Frame.__init__(self, parent, -1, size=MAINSIZE, style=FRAMESTYLE) 

63 

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") 

72 

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)) 

83 

84 self.createMainPanel() 

85 self.createMenus() 

86 

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) 

96 

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() 

103 

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)) 

111 

112 splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) 

113 splitter.SetMinimumPaneSize(250) 

114 

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 = {} 

119 

120 sizer = wx.BoxSizer(wx.VERTICAL) 

121 sizer.Add(self.ciflist, 1, LEFT|wx.GROW|wx.ALL, 1) 

122 pack(leftpanel, sizer) 

123 

124 # right hand side 

125 rightpanel = scrolled.ScrolledPanel(splitter) 

126 panel = wx.Panel(rightpanel, size=(725, -1)) 

127 

128 self.ciflist.SetMinSize((375, 250)) 

129 rightpanel.SetMinSize((400, 250)) 

130 

131 sizer = wx.GridBagSizer(2, 2) 

132 

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 = {} 

137 

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) 

143 

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) 

148 

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) 

153 

154 elemlab = SimpleText(panel, ' Include Elements: ') 

155 elemhint= SimpleText(panel, ' example: O, Fe, Si ') 

156 

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) 

160 

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) 

165 

166 wids['excludes_elements'].Enable() 

167 wids['strict_contains'] = Check(panel, default=False, 

168 label='Include only the elements listed', 

169 action=self.onStrict) 

170 

171 wids['full_occupancy'] = Check(panel, default=False, 

172 label='Only Structures with Full Occupancy') 

173 

174 wids['search'] = Button(panel, 'Search for CIFs', action=self.onSearch) 

175 

176 

177 ir = 0 

178 sizer.Add(self.title, (0, 0), (1, 6), LEFT, 2) 

179 

180 ir += 1 

181 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 6), LEFT, 3) 

182 

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) 

190 

191 ir += 1 

192 sizer.Add(journlab, (ir, 0), (1, 1), LEFT, 3) 

193 sizer.Add(wids['journal'], (ir, 1), (1, 3), LEFT, 3) 

194 

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) 

199 

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) 

203 

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) 

207 

208 ir += 1 

209 sizer.Add(wids['full_occupancy'], (ir, 1), (1, 4), LEFT, 3) 

210 

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) 

218 

219 

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) 

226 

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() 

239 

240 ir += 1 

241 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 6), LEFT, 3) 

242 

243 ir += 1 

244 

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) 

251 

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) 

258 

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) 

263 

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() 

267 

268 ir += 1 

269 sizer.Add(wids['cif_use_button'], (ir, 5), (1, 1), LEFT, 3) 

270 

271 

272 ir += 1 

273 sizer.Add(HLine(panel, size=(650, 2)), (ir, 0), (1, 6), LEFT, 3) 

274 

275 pack(panel, sizer) 

276 

277 self.nb = flatnotebook(rightpanel, {}, on_change=self.onNBChanged) 

278 

279 

280 def _swallow_plot_messages(s, panel=0): 

281 pass 

282 

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 

290 

291 self.plotpanel.SetMinSize((250, 250)) 

292 self.plotpanel.SetMaxSize((675, 400)) 

293 self.plotpanel.onPanelExposed = self.showXRD1D 

294 

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) 

303 

304 

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)) 

311 

312 if self.with_feff: 

313 self.feffresults = FeffResultsPanel(rightpanel, 

314 path_importer=self.path_importer, 

315 _larch=self.larch) 

316 

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() 

323 

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) 

329 

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) 

340 

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) 

348 

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) 

352 

353 pack(rightpanel, r_sizer) 

354 rightpanel.SetupScrolling() 

355 splitter.SplitVertically(leftpanel, rightpanel, 1) 

356 

357 

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]) 

366 

367 def onStrict(self, event=None): 

368 strict = self.wids['strict_contains'].IsChecked() 

369 self.wids['excludes_elements'].Enable(not strict) 

370 

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})' 

426 

427 self.cif_selections[label] = cif.ams_id 

428 self.ciflist.Append(label) 

429 

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) 

440 

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() 

446 

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) 

450 

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) 

454 

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) 

463 

464 self.wids['feff_site'].Clear() 

465 self.wids['feff_site'].AppendItems(sites) 

466 self.wids['feff_site'].Select(0) 

467 

468 if self.usecif_callback is not None: 

469 self.wids['cif_use_button'].Enable() 

470 

471 i, p = self.get_nbpage('CIF Text') 

472 self.nb.SetSelection(i) 

473 

474 def onUseCIF(self, event=None): 

475 if self.usecif_callback is not None: 

476 self.usecif_callback(cif=self.current_cif) 

477 

478 

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) 

495 

496 edge_val = 'K' if atomic_number(catom) < 60 else 'L3' 

497 self.wids['feff_edge'].SetStringSelection(edge_val) 

498 self.onGetFeff() 

499 

500 def onGetFeff(self, event=None): 

501 cif = self.current_cif 

502 if cif is None or not self.with_feff: 

503 return 

504 

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) 

514 

515 fefftext = cif.get_feffinp(catom, edge=edge, cluster_size=csize, 

516 absorber_site=asite, version8=version8, 

517 with_h=with_h) 

518 

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) 

524 

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 

529 

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' 

536 

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() 

545 

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) 

553 

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()) 

561 

562 fname = unixpath(Path(folder, 'feff.inp').absolute()) 

563 with open(fname, 'w', encoding=sys.getdefaultencoding()) as fh: 

564 fh.write(strict_ascii(fefftext)) 

565 

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) 

571 

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") 

580 

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) 

588 

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())) 

595 

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() 

607 

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) 

626 

627 

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) 

643 

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 

657 

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 

665 

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) 

672 

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) 

692 

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) 

697 

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) 

703 

704 def onNBChanged(self, event=None): 

705 callback = getattr(self.nb.GetCurrentPage(), 'onPanelExposed', None) 

706 if callable(callback): 

707 callback() 

708 

709 def showXRD1D(self, event=None): 

710 if self.has_xrd1d or self.current_cif is None: 

711 return 

712 

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 

720 

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()) 

726 

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) 

731 

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) 

738 

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 

743 

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() 

749 

750 

751 def onSelAll(self, event=None): 

752 self.controller.filelist.select_all() 

753 

754 def onSelNone(self, event=None): 

755 self.controller.filelist.select_none() 

756 

757 def write_message(self, msg, panel=0): 

758 """write a message to the Status Bar""" 

759 self.statusbar.SetStatusText(msg, panel) 

760 

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 = {} 

769 

770 MenuItem(self, fmenu, "&Open CIF File\tCtrl+O", 

771 "Open CIF File", self.onImportCIF) 

772 

773 MenuItem(self, fmenu, "&Save CIF File\tCtrl+S", 

774 "Save CIF File", self.onExportCIF) 

775 

776 MenuItem(self, fmenu, "Open Feff Input File", 

777 "Open Feff input File", self.onImportFeff) 

778 

779 MenuItem(self, fmenu, "Save &Feff Inp File\tCtrl+F", 

780 "Save Feff6 File", self.onExportFeff) 

781 

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) 

788 

789 self.menubar.Append(fmenu, "&File") 

790 

791 self.SetMenuBar(self.menubar) 

792 self.Bind(wx.EVT_CLOSE, self.onClose) 

793 

794 def onClose(self, event=None): 

795 self.Destroy() 

796 

797 

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) 

802 

803 def createApp(self): 

804 frame = CIFFrame(filename=self.filename, 

805 version_info=self.version_info) 

806 self.SetTopWindow(frame) 

807 return True 

808 

809def cif_viewer(**kws): 

810 CIFViewer(**kws) 

811 

812if __name__ == '__main__': 

813 CIFViewer().MainLoop()