source: trunk/exports/G2export_CIF.py @ 3464

Last change on this file since 3464 was 3464, checked in by vondreele, 5 years ago

fix Unit cell load/import cells to force R3-H fro rhombohedral cases
start on some stuff for mag. cif export

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 142.6 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2018-07-09 18:35:38 +0000 (Mon, 09 Jul 2018) $
5# $Author: vondreele $
6# $Revision: 3464 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 3464 2018-07-09 18:35:38Z vondreele $
9########### SVN repository information ###################
10'''
11*Module G2export_CIF: CIF Exports*
12------------------------------------------------------
13
14This implements a complex exporter :class:`ExportCIF` that can implement an
15entire project in a complete CIF intended for submission as a
16publication. In addition, there are three subclasses of :class:`ExportCIF`:
17:class:`ExportProjectCIF`,
18:class:`ExportPhaseCIF` and :class:`ExportDataCIF` where extra parameters
19for the _Exporter() determine if a project, single phase or data set are written.
20'''
21
22from __future__ import division, print_function
23import platform
24import datetime as dt
25import os.path
26import sys
27import numpy as np
28if '2' in platform.python_version_tuple()[0]:
29    import cPickle as pickle
30else:
31    import pickle
32import copy
33import re
34try:
35    import wx
36    import wx.lib.scrolledpanel as wxscroll
37    import wx.lib.resizewidget as rw
38except ImportError:
39    # Avoid wx dependency for CLI
40    class Placeholder(object):
41        def __init__(self):
42            self.BoxSizer = object
43            self.Button = object
44            self.Dialog = object
45            self.ScrolledPanel = object
46    wx = Placeholder()
47    wxscroll = Placeholder()
48import GSASIIpath
49GSASIIpath.SetVersionNumber("$Revision: 3464 $")
50import GSASIIIO as G2IO
51try:
52    import GSASIIctrlGUI as G2G
53except ImportError:
54    pass
55import GSASIIobj as G2obj
56import GSASIImath as G2mth
57import GSASIIspc as G2spc
58import GSASIIstrMain as G2stMn
59import GSASIImapvars as G2mv
60
61DEBUG = False    #True to skip printing of reflection/powder profile lists
62
63CIFdic = None
64
65# Refactored over here to allow access by GSASIIscriptable.py
66def WriteCIFitem(fp, name, value=''):
67    '''Helper function for writing CIF output. Translated from exports/G2export_CIF.py'''
68    # Ignore unicode issues
69    if value:
70        if "\n" in value or len(value)> 70:
71            if name.strip():
72                fp.write(name+'\n')
73            fp.write('; '+value+'\n')
74            fp.write('; '+'\n')
75        elif " " in value:
76            if len(name)+len(value) > 65:
77                fp.write(name + '\n   ' + '"' + str(value) + '"'+'\n')
78            else:
79                fp.write(name + '  ' + '"' + str(value) + '"'+'\n')
80        else:
81            if len(name)+len(value) > 65:
82                fp.write(name+'\n   ' + value+'\n')
83            else:
84                fp.write(name+'  ' + value+'\n')
85    else:
86        fp.write(name+'\n')
87
88
89# Refactored over here to allow access by GSASIIscriptable.py
90def WriteAtomsNuclear(fp, phasedict, phasenam, parmDict, sigDict, labellist):
91    'Write atom positions to CIF'
92    # phasedict = self.Phases[phasenam] # pointer to current phase info
93    General = phasedict['General']
94    cx,ct,cs,cia = General['AtomPtrs']
95    Atoms = phasedict['Atoms']
96    cfrac = cx+3
97    fpfx = str(phasedict['pId'])+'::Afrac:'
98    for i,at in enumerate(Atoms):
99        fval = parmDict.get(fpfx+str(i),at[cfrac])
100        if fval != 0.0:
101            break
102    else:
103        WriteCIFitem(fp, '\n# PHASE HAS NO ATOMS!')
104        return
105
106    WriteCIFitem(fp, '\n# ATOMIC COORDINATES AND DISPLACEMENT PARAMETERS')
107    WriteCIFitem(fp, 'loop_ '+
108                 '\n   _atom_site_label'+
109                 '\n   _atom_site_type_symbol'+
110                 '\n   _atom_site_fract_x'+
111                 '\n   _atom_site_fract_y'+
112                 '\n   _atom_site_fract_z'+
113                 '\n   _atom_site_occupancy'+
114                 '\n   _atom_site_adp_type'+
115                 '\n   _atom_site_U_iso_or_equiv'+
116                 '\n   _atom_site_symmetry_multiplicity')
117
118    varnames = {cx:'Ax',cx+1:'Ay',cx+2:'Az',cx+3:'Afrac',
119                cia+1:'AUiso',cia+2:'AU11',cia+3:'AU22',cia+4:'AU33',
120                cia+5:'AU12',cia+6:'AU13',cia+7:'AU23'}
121    # Empty the labellist
122    while labellist:
123        labellist.pop()
124
125    pfx = str(phasedict['pId'])+'::'
126    # loop over all atoms
127    naniso = 0
128    for i,at in enumerate(Atoms):
129        if phasedict['General']['Type'] == 'macromolecular':
130            label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
131            s = PutInCol(MakeUniqueLabel(label,labellist),15) # label
132        else:
133            s = PutInCol(MakeUniqueLabel(at[ct-1],labellist),6) # label
134        fval = parmDict.get(fpfx+str(i),at[cfrac])
135        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
136        s += PutInCol(FmtAtomType(at[ct]),4) # type
137        if at[cia] == 'I':
138            adp = 'Uiso '
139        else:
140            adp = 'Uani '
141            naniso += 1
142            # compute Uequiv crudely
143            # correct: Defined as "1/3 trace of diagonalized U matrix".
144            # SEE cell2GS & Uij2Ueqv to GSASIIlattice. Former is needed to make the GS matrix used by the latter.
145            t = 0.0
146            for j in (2,3,4):
147                var = pfx+varnames[cia+j]+":"+str(i)
148                t += parmDict.get(var,at[cia+j])
149        for j in (cx,cx+1,cx+2,cx+3,cia,cia+1):
150            if j in (cx,cx+1,cx+2):
151                dig = 11
152                sigdig = -0.00009
153            else:
154                dig = 10
155                sigdig = -0.009
156            if j == cia:
157                s += adp
158            else:
159                var = pfx+varnames[j]+":"+str(i)
160                dvar = pfx+"d"+varnames[j]+":"+str(i)
161                if dvar not in sigDict:
162                    dvar = var
163                if j == cia+1 and adp == 'Uani ':
164                    val = t/3.
165                    sig = sigdig
166                else:
167                    #print var,(var in parmDict),(var in sigDict)
168                    val = parmDict.get(var,at[j])
169                    sig = sigDict.get(dvar,sigdig)
170                    if dvar in G2mv.GetDependentVars(): # do not include an esd for dependent vars
171                        sig = -abs(sig)
172                s += PutInCol(G2mth.ValEsd(val,sig),dig)
173        s += PutInCol(at[cs+1],3)
174        WriteCIFitem(fp, s)
175    if naniso == 0: return
176    # now loop over aniso atoms
177    WriteCIFitem(fp, '\nloop_' + '\n   _atom_site_aniso_label' +
178                 '\n   _atom_site_aniso_U_11' + '\n   _atom_site_aniso_U_22' +
179                 '\n   _atom_site_aniso_U_33' + '\n   _atom_site_aniso_U_12' +
180                 '\n   _atom_site_aniso_U_13' + '\n   _atom_site_aniso_U_23')
181    for i,at in enumerate(Atoms):
182        fval = parmDict.get(fpfx+str(i),at[cfrac])
183        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
184        if at[cia] == 'I': continue
185        s = PutInCol(labellist[i],6) # label
186        for j in (2,3,4,5,6,7):
187            sigdig = -0.0009
188            var = pfx+varnames[cia+j]+":"+str(i)
189            val = parmDict.get(var,at[cia+j])
190            sig = sigDict.get(var,sigdig)
191            s += PutInCol(G2mth.ValEsd(val,sig),11)
192        WriteCIFitem(fp, s)
193
194def WriteAtomsMagnetic(fp, phasedict, phasenam, parmDict, sigDict, labellist):
195    'Write atom positions to CIF'
196    # phasedict = self.Phases[phasenam] # pointer to current phase info
197    General = phasedict['General']
198    cx,ct,cs,cia = General['AtomPtrs']
199    Atoms = phasedict['Atoms']
200    cfrac = cx+3
201    fpfx = str(phasedict['pId'])+'::Afrac:'
202    for i,at in enumerate(Atoms):
203        fval = parmDict.get(fpfx+str(i),at[cfrac])
204        if fval != 0.0:
205            break
206    else:
207        WriteCIFitem(fp, '\n# PHASE HAS NO ATOMS!')
208        return
209
210    WriteCIFitem(fp, '\n# ATOMIC COORDINATES AND DISPLACEMENT PARAMETERS')
211    WriteCIFitem(fp, 'loop_ '+
212                 '\n   _atom_site_label'+
213                 '\n   _atom_site_type_symbol'+
214                 '\n   _atom_site_fract_x'+
215                 '\n   _atom_site_fract_y'+
216                 '\n   _atom_site_fract_z'+
217                 '\n   _atom_site_occupancy'+
218                 '\n   _atom_site_adp_type'+
219                 '\n   _atom_site_U_iso_or_equiv'+
220                 '\n   _atom_site_symmetry_multiplicity')
221
222    varnames = {cx:'Ax',cx+1:'Ay',cx+2:'Az',cx+3:'Afrac',
223                cx+4:'AMx',cx+5:'AMy',cx+6:'AMz',
224                cia+1:'AUiso',cia+2:'AU11',cia+3:'AU22',cia+4:'AU33',
225                cia+5:'AU12',cia+6:'AU13',cia+7:'AU23'}
226    # Empty the labellist
227    while labellist:
228        labellist.pop()
229
230    pfx = str(phasedict['pId'])+'::'
231    # loop over all atoms
232    naniso = 0
233    for i,at in enumerate(Atoms):
234        if phasedict['General']['Type'] == 'macromolecular':
235            label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
236            s = PutInCol(MakeUniqueLabel(label,labellist),15) # label
237        else:
238            s = PutInCol(MakeUniqueLabel(at[ct-1],labellist),6) # label
239        fval = parmDict.get(fpfx+str(i),at[cfrac])
240        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
241        s += PutInCol(FmtAtomType(at[ct]),4) # type
242        if at[cia] == 'I':
243            adp = 'Uiso '
244        else:
245            adp = 'Uani '
246            naniso += 1
247            # compute Uequiv crudely
248            # correct: Defined as "1/3 trace of diagonalized U matrix".
249            # SEE cell2GS & Uij2Ueqv to GSASIIlattice. Former is needed to make the GS matrix used by the latter.
250            t = 0.0
251            for j in (2,3,4):
252                var = pfx+varnames[cia+j]+":"+str(i)
253                t += parmDict.get(var,at[cia+j])
254        for j in (cx,cx+1,cx+2,cx+3,cia,cia+1):
255            if j in (cx,cx+1,cx+2):
256                dig = 11
257                sigdig = -0.00009
258            else:
259                dig = 10
260                sigdig = -0.009
261            if j == cia:
262                s += adp
263            else:
264                var = pfx+varnames[j]+":"+str(i)
265                dvar = pfx+"d"+varnames[j]+":"+str(i)
266                if dvar not in sigDict:
267                    dvar = var
268                if j == cia+1 and adp == 'Uani ':
269                    val = t/3.
270                    sig = sigdig
271                else:
272                    #print var,(var in parmDict),(var in sigDict)
273                    val = parmDict.get(var,at[j])
274                    sig = sigDict.get(dvar,sigdig)
275                    if dvar in G2mv.GetDependentVars(): # do not include an esd for dependent vars
276                        sig = -abs(sig)
277                s += PutInCol(G2mth.ValEsd(val,sig),dig)
278        s += PutInCol(at[cs+1],3)
279        WriteCIFitem(fp, s)
280    if naniso == 0: return
281    # now loop over aniso atoms
282    WriteCIFitem(fp, '\nloop_' + '\n   _atom_site_aniso_label' +
283                 '\n   _atom_site_aniso_U_11' + '\n   _atom_site_aniso_U_22' +
284                 '\n   _atom_site_aniso_U_33' + '\n   _atom_site_aniso_U_12' +
285                 '\n   _atom_site_aniso_U_13' + '\n   _atom_site_aniso_U_23')
286    for i,at in enumerate(Atoms):
287        fval = parmDict.get(fpfx+str(i),at[cfrac])
288        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
289        if at[cia] == 'I': continue
290        s = PutInCol(labellist[i],6) # label
291        for j in (2,3,4,5,6,7):
292            sigdig = -0.0009
293            var = pfx+varnames[cia+j]+":"+str(i)
294            val = parmDict.get(var,at[cia+j])
295            sig = sigDict.get(var,sigdig)
296            s += PutInCol(G2mth.ValEsd(val,sig),11)
297        WriteCIFitem(fp, s)
298    # now loop over mag atoms (e.g. all of them)
299    WriteCIFitem(fp, '\nloop_' + '\n   _atom_site_moment.label' +
300                 '\n   _atom_site_moment.crystalaxis_x' +
301                 '\n   _atom_site_moment.crystalaxis_y' +
302                 '\n   _atom_site_moment.crystalaxis_z')
303    for i,at in enumerate(Atoms):
304        fval = parmDict.get(fpfx+str(i),at[cfrac])
305        if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
306        s = PutInCol(labellist[i],6) # label
307        for j in (cx+4,cx+5,cx+6):
308            sigdig = -0.0009
309            var = pfx+varnames[j]+":"+str(i)
310            val = parmDict.get(var,at[j])
311            sig = sigDict.get(var,sigdig)
312            s += PutInCol(G2mth.ValEsd(val,sig),11)
313        WriteCIFitem(fp, s)
314
315# Refactored over here to allow access by GSASIIscriptable.py
316def MakeUniqueLabel(lbl, labellist):
317    lbl = lbl.strip()
318    if not lbl: # deal with a blank label
319        lbl = 'A_1'
320    if lbl not in labellist:
321        labellist.append(lbl)
322        return lbl
323    i = 1
324    prefix = lbl
325    if '_' in lbl:
326        prefix = lbl[:lbl.rfind('_')]
327        suffix = lbl[lbl.rfind('_')+1:]
328        try:
329            i = int(suffix)+1
330        except:
331            pass
332    while prefix+'_'+str(i) in labellist:
333        i += 1
334    else:
335        lbl = prefix+'_'+str(i)
336        labellist.append(lbl)
337
338
339# Refactored over here to allow access by GSASIIscriptable.py
340def HillSortElements(elmlist):
341    '''Sort elements in "Hill" order: C, H, others, (where others
342    are alphabetical).
343
344    :params list elmlist: a list of element strings
345
346    :returns: a sorted list of element strings
347    '''
348    newlist = []
349    oldlist = elmlist[:]
350    for elm in ('C','H'):
351        if elm in elmlist:
352            newlist.append(elm)
353            oldlist.pop(oldlist.index(elm))
354    return newlist+sorted(oldlist)
355
356
357# Refactored over here to allow access by GSASIIscriptable.py
358def FmtAtomType(sym):
359    'Reformat a GSAS-II atom type symbol to match CIF rules'
360    sym = sym.replace('_','') # underscores are not allowed: no isotope designation?
361    # in CIF, oxidation state sign symbols come after, not before
362    if '+' in sym:
363        sym = sym.replace('+','') + '+'
364    elif '-' in sym:
365        sym = sym.replace('-','') + '-'
366    return sym
367
368
369# Refactored over here to allow access by GSASIIscriptable.py
370def PutInCol(val, wid):
371    val = str(val).replace(' ', '')
372    if not val: val = '?'
373    fmt = '{:' + str(wid) + '} '
374    return fmt.format(val)
375
376
377# Refactored over here to allow access by GSASIIscriptable.py
378def WriteComposition(fp, phasedict, phasenam, parmDict):
379    '''determine the composition for the unit cell, crudely determine Z and
380    then compute the composition in formula units
381    '''
382    General = phasedict['General']
383    Z = General.get('cellZ',0.0)
384    cx,ct,cs,cia = General['AtomPtrs']
385    Atoms = phasedict['Atoms']
386    fpfx = str(phasedict['pId'])+'::Afrac:'
387    cfrac = cx+3
388    cmult = cs+1
389    compDict = {} # combines H,D & T
390    sitemultlist = []
391    massDict = dict(zip(General['AtomTypes'],General['AtomMass']))
392    cellmass = 0
393    for i,at in enumerate(Atoms):
394        atype = at[ct].strip()
395        if atype.find('-') != -1: atype = atype.split('-')[0]
396        if atype.find('+') != -1: atype = atype.split('+')[0]
397        atype = atype[0].upper()+atype[1:2].lower() # force case conversion
398        if atype == "D" or atype == "D": atype = "H"
399        fvar = fpfx+str(i)
400        fval = parmDict.get(fvar,at[cfrac])
401        mult = at[cmult]
402        if not massDict.get(at[ct]):
403            print('Error: No mass found for atom type '+at[ct])
404            print('Will not compute cell contents for phase '+phasenam)
405            return
406        cellmass += massDict[at[ct]]*mult*fval
407        compDict[atype] = compDict.get(atype,0.0) + mult*fval
408        if fval == 1: sitemultlist.append(mult)
409    if len(compDict.keys()) == 0: return # no elements!
410    if Z < 1: # Z has not been computed or set by user
411        Z = 1
412        if not sitemultlist:
413            General['cellZ'] = 1
414            return
415        for i in range(2,min(sitemultlist)+1):
416            for m in sitemultlist:
417                if m % i != 0:
418                    break
419                else:
420                    Z = i
421        General['cellZ'] = Z # save it
422
423    # when scattering factors are included in the CIF, this needs to be
424    # added to the loop here but only in the one-block case.
425    # For multiblock CIFs, scattering factors go in the histogram
426    # blocks  (for all atoms in all appropriate phases) - an example?:
427    #loop_
428    #    _atom_type_symbol
429    #    _atom_type_description
430    #    _atom_type_scat_dispersion_real
431    #    _atom_type_scat_dispersion_imag
432    #    _atom_type_scat_source
433    #    'C' 'C' 0.0033 0.0016
434    #                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
435    #    'H' 'H' 0.0000 0.0000
436    #                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
437    #    'P' 'P' 0.1023 0.0942
438    #                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
439    #    'Cl' 'Cl' 0.1484 0.1585
440    #                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
441    #    'Cu' 'Cu' 0.3201 1.2651
442    #                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
443
444    #if oneblock: # add scattering factors for current phase here
445    WriteCIFitem(fp, '\nloop_  _atom_type_symbol _atom_type_number_in_cell')
446    formula = ''
447    for elem in HillSortElements(list(compDict.keys())):
448        WriteCIFitem(fp, '  ' + PutInCol(elem,4) +
449                     G2mth.ValEsd(compDict[elem],-0.009,True))
450        if formula: formula += " "
451        formula += elem
452        if compDict[elem] == Z: continue
453        formula += G2mth.ValEsd(compDict[elem]/Z,-0.009,True)
454    WriteCIFitem(fp,  '\n# Note that Z affects _cell_formula_sum and _weight')
455    WriteCIFitem(fp,  '_cell_formula_units_Z',str(Z))
456    WriteCIFitem(fp,  '_chemical_formula_sum',formula)
457    WriteCIFitem(fp,  '_chemical_formula_weight',
458                  G2mth.ValEsd(cellmass/Z,-0.09,True))
459
460
461class ExportCIF(G2IO.ExportBaseclass):
462    '''Base class for CIF exports
463    '''
464    def __init__(self,G2frame,formatName,extension,longFormatName=None,):
465        G2IO.ExportBaseclass.__init__(self,G2frame,formatName,extension,longFormatName=None)
466        self.exporttype = []
467        self.author = ''
468        self.CIFname = ''
469
470    def ValidateAscii(self,checklist):
471        '''Validate items as ASCII'''
472        msg = ''
473        for lbl,val in checklist:
474            if not all(ord(c) < 128 for c in val):
475                if msg: msg += '\n'
476                msg += lbl + " contains unicode characters: " + val
477        if msg:
478            G2G.G2MessageBox(self.G2frame,
479                             'Error: CIFs can contain only ASCII characters. Please change item(s) below:\n\n'+msg,
480                             'Unicode not valid for CIF')
481            return True
482
483    def _Exporter(self,event=None,phaseOnly=None,histOnly=None,IncludeOnlyHist=None):
484        '''Basic code to export a CIF. Export can be full or simple, as set by
485        phaseOnly and histOnly which skips distances & angles, etc.
486
487          phaseOnly: used to export only one phase
488          histOnly: used to export only one histogram
489          IncludeOnlyHist: used for a full CIF that includes only one of the
490            histograms (from a sequential fit) #TODO: needs lots of work!
491        '''
492
493#***** define functions for export method =======================================
494        def WriteAudit():
495            'Write the CIF audit values. Perhaps should be in a single element loop.'
496            WriteCIFitem(self.fp, '_audit_creation_method',
497                         'created in GSAS-II')
498            WriteCIFitem(self.fp, '_audit_creation_date',self.CIFdate)
499            if self.author:
500                WriteCIFitem(self.fp, '_audit_author_name',self.author)
501            WriteCIFitem(self.fp, '_audit_update_record',
502                         self.CIFdate+'  Initial software-generated CIF')
503
504        def WriteOverall():
505            '''Write out overall refinement information.
506
507            More could be done here, but this is a good start.
508            '''
509            if self.ifPWDR:
510                WriteCIFitem(self.fp, '_pd_proc_info_datetime', self.CIFdate)
511                WriteCIFitem(self.fp, '_pd_calc_method', 'Rietveld Refinement')
512            #WriteCIFitem(self.fp, '_refine_ls_shift/su_max',DAT1)
513            #WriteCIFitem(self.fp, '_refine_ls_shift/su_mean',DAT2)
514            WriteCIFitem(self.fp, '_computing_structure_refinement','GSAS-II (Toby & Von Dreele, J. Appl. Cryst. 46, 544-549, 2013)')
515            if self.ifHKLF:
516                controls = self.OverallParms['Controls']
517                if controls['F**2']:
518                    thresh = 'F**2>%.1fu(F**2)'%(controls['UsrReject']['minF/sig'])
519                else:
520                    thresh = 'F>%.1fu(F)'%(controls['UsrReject']['minF/sig'])
521                WriteCIFitem(self.fp, '_reflns_threshold_expression', thresh)
522            try:
523                vars = str(len(self.OverallParms['Covariance']['varyList']))
524            except:
525                vars = '?'
526            WriteCIFitem(self.fp, '_refine_ls_number_parameters',vars)
527            try:
528                GOF = G2mth.ValEsd(self.OverallParms['Covariance']['Rvals']['GOF'],-0.009)
529            except:
530                GOF = '?'
531            WriteCIFitem(self.fp, '_refine_ls_goodness_of_fit_all',GOF)
532
533            # get restraint info
534            # restraintDict = self.OverallParms.get('Restraints',{})
535            # for i in  self.OverallParms['Constraints']:
536            #     print i
537            #     for j in self.OverallParms['Constraints'][i]:
538            #         print j
539            #WriteCIFitem(self.fp, '_refine_ls_number_restraints',TEXT)
540            # other things to consider reporting
541            # _refine_ls_number_reflns
542            # _refine_ls_goodness_of_fit_obs
543            # _refine_ls_wR_factor_obs
544            # _refine_ls_restrained_S_all
545            # _refine_ls_restrained_S_obs
546
547            # include an overall profile r-factor, if there is more than one powder histogram
548            R = '%.5f'%(self.OverallParms['Covariance']['Rvals']['Rwp']/100.)
549            WriteCIFitem(self.fp, '\n# OVERALL WEIGHTED R-FACTOR')
550            WriteCIFitem(self.fp, '_refine_ls_wR_factor_obs',R)
551                # _refine_ls_R_factor_all
552                # _refine_ls_R_factor_obs
553            WriteCIFitem(self.fp, '_refine_ls_matrix_type','full')
554            #WriteCIFitem(self.fp, '_refine_ls_matrix_type','userblocks')
555
556        def writeCIFtemplate(G2dict,tmplate,defaultname=''):
557            '''Write out the selected or edited CIF template
558            An unedited CIF template file is copied, comments intact; an edited
559            CIF template is written out from PyCifRW which of course strips comments.
560            In all cases the initial data_ header is stripped (there should only be one!)
561            '''
562            CIFobj = G2dict.get("CIF_template")
563            if defaultname:
564                defaultname = G2obj.StripUnicode(defaultname)
565                defaultname = re.sub(r'[^a-zA-Z0-9_-]','',defaultname)
566                defaultname = tmplate + "_" + defaultname + ".cif"
567            else:
568                defaultname = ''
569            templateDefName = 'template_'+tmplate+'.cif'
570            if not CIFobj: # copying a template
571                for pth in [os.getcwd()]+sys.path:
572                    fil = os.path.join(pth,defaultname)
573                    if os.path.exists(fil) and defaultname: break
574                else:
575                    for pth in sys.path:
576                        fil = os.path.join(pth,templateDefName)
577                        if os.path.exists(fil): break
578                    else:
579                        print(CIFobj+' not found in path!')
580                        return
581                fp = open(fil,'r')
582                txt = fp.read()
583                fp.close()
584            elif type(CIFobj) is not list and type(CIFobj) is not tuple:
585                if not os.path.exists(CIFobj):
586                    print("Error: requested template file has disappeared: "+CIFobj)
587                    return
588                fp = open(CIFobj,'r')
589                txt = fp.read()
590                fp.close()
591            else:
592                txt = dict2CIF(CIFobj[0],CIFobj[1]).WriteOut()
593            # remove the PyCifRW header, if present
594            #if txt.find('PyCifRW') > -1 and txt.find('data_') > -1:
595            txt = "# GSAS-II edited template follows "+txt[txt.index("data_")+5:]
596            #txt = txt.replace('data_','#')
597            WriteCIFitem(self.fp, txt)
598
599        def FormatSH(phasenam):
600            'Format a full spherical harmonics texture description as a string'
601            phasedict = self.Phases[phasenam] # pointer to current phase info
602            pfx = str(phasedict['pId'])+'::'
603            s = ""
604            textureData = phasedict['General']['SH Texture']
605            if textureData.get('Order'):
606                s += "Spherical Harmonics correction. Order = "+str(textureData['Order'])
607                s += " Model: " + str(textureData['Model']) + "\n    Orientation angles: "
608                for name in ['omega','chi','phi']:
609                    aname = pfx+'SH '+name
610                    s += name + " = "
611                    sig = self.sigDict.get(aname,-0.09)
612                    s += G2mth.ValEsd(self.parmDict[aname],sig)
613                    s += "; "
614                s += "\n"
615                s1 = "    Coefficients:  "
616                for name in textureData['SH Coeff'][1]:
617                    aname = pfx+name
618                    if len(s1) > 60:
619                        s += s1 + "\n"
620                        s1 = "    "
621                    s1 += aname + ' = '
622                    sig = self.sigDict.get(aname,-0.0009)
623                    s1 += G2mth.ValEsd(self.parmDict[aname],sig)
624                    s1 += "; "
625                s += s1
626            return s
627
628        def FormatHAPpo(phasenam):
629            '''return the March-Dollase/SH correction for every
630            histogram in the current phase formatted into a
631            character string
632            '''
633            phasedict = self.Phases[phasenam] # pointer to current phase info
634            s = ''
635            for histogram in sorted(phasedict['Histograms']):
636                if histogram.startswith("HKLF"): continue # powder only
637                Histogram = self.Histograms.get(histogram)
638                if not Histogram: continue
639                hapData = phasedict['Histograms'][histogram]
640                if hapData['Pref.Ori.'][0] == 'MD':
641                    aname = str(phasedict['pId'])+':'+str(Histogram['hId'])+':MD'
642                    if self.parmDict.get(aname,1.0) != 1.0: continue
643                    sig = self.sigDict.get(aname,-0.009)
644                    if s != "": s += '\n'
645                    s += 'March-Dollase correction'
646                    if len(self.powderDict) > 1:
647                        s += ', histogram '+str(Histogram['hId']+1)
648                    s += ' coef. = ' + G2mth.ValEsd(self.parmDict[aname],sig)
649                    s += ' axis = ' + str(hapData['Pref.Ori.'][3])
650                else: # must be SH
651                    if s != "": s += '\n'
652                    s += 'Simple spherical harmonic correction'
653                    if len(self.powderDict) > 1:
654                        s += ', histogram '+str(Histogram['hId']+1)
655                    s += ' Order = '+str(hapData['Pref.Ori.'][4])+'\n'
656                    s1 = "    Coefficients:  "
657                    for item in hapData['Pref.Ori.'][5]:
658                        aname = str(phasedict['pId'])+':'+str(Histogram['hId'])+':'+item
659                        if len(s1) > 60:
660                            s += s1 + "\n"
661                            s1 = "    "
662                        s1 += aname + ' = '
663                        sig = self.sigDict.get(aname,-0.0009)
664                        s1 += G2mth.ValEsd(self.parmDict[aname],sig)
665                        s1 += "; "
666                    s += s1
667            return s
668
669        def FormatBackground(bkg,hId):
670            '''Display the Background information as a descriptive text string.
671
672            TODO: this needs to be expanded to show the diffuse peak and
673            Debye term information as well. (Bob)
674
675            :returns: the text description (str)
676            '''
677            hfx = ':'+str(hId)+':'
678            fxn, bkgdict = bkg
679            terms = fxn[2]
680            txt = 'Background function: "'+fxn[0]+'" function with '+str(terms)+' terms:\n'
681            l = "    "
682            for i,v in enumerate(fxn[3:]):
683                name = '%sBack;%d'%(hfx,i)
684                sig = self.sigDict.get(name,-0.009)
685                if len(l) > 60:
686                    txt += l + '\n'
687                    l = '    '
688                l += G2mth.ValEsd(v,sig)+', '
689            txt += l
690            if bkgdict['nDebye']:
691                txt += '\n  Background Debye function parameters: A, R, U:'
692                names = ['A;','R;','U;']
693                for i in range(bkgdict['nDebye']):
694                    txt += '\n    '
695                    for j in range(3):
696                        name = hfx+'Debye'+names[j]+str(i)
697                        sig = self.sigDict.get(name,-0.009)
698                        txt += G2mth.ValEsd(bkgdict['debyeTerms'][i][2*j],sig)+', '
699            if bkgdict['nPeaks']:
700                txt += '\n  Background peak parameters: pos, int, sig, gam:'
701                names = ['pos;','int;','sig;','gam;']
702                for i in range(bkgdict['nPeaks']):
703                    txt += '\n    '
704                    for j in range(4):
705                        name = hfx+'BkPk'+names[j]+str(i)
706                        sig = self.sigDict.get(name,-0.009)
707                        txt += G2mth.ValEsd(bkgdict['peaksList'][i][2*j],sig)+', '
708            return txt
709
710        def FormatInstProfile(instparmdict,hId):
711            '''Format the instrumental profile parameters with a
712            string description. Will only be called on PWDR histograms
713            '''
714            s = ''
715            inst = instparmdict[0]
716            hfx = ':'+str(hId)+':'
717            if 'C' in inst['Type'][0]:
718                s = 'Finger-Cox-Jephcoat function parameters U, V, W, X, Y, SH/L:\n'
719                s += '  peak variance(Gauss) = Utan(Th)^2^+Vtan(Th)+W:\n'
720                s += '  peak HW(Lorentz) = X/cos(Th)+Ytan(Th); SH/L = S/L+H/L\n'
721                s += '  U, V, W in (centideg)^2^, X & Y in centideg\n    '
722                for item in ['U','V','W','X','Y','SH/L']:
723                    name = hfx+item
724                    sig = self.sigDict.get(name,-0.009)
725                    s += G2mth.ValEsd(inst[item][1],sig)+', '
726            elif 'T' in inst['Type'][0]:    #to be tested after TOF Rietveld done
727                s = 'Von Dreele-Jorgenson-Windsor function parameters\n'+ \
728                    '   alpha, beta-0, beta-1, beta-q, sig-0, sig-1, sig-2, sig-q, X, Y:\n    '
729                for item in ['alpha','beta-0','beta-1','beta-q','sig-0','sig-1','sig-2','sig-q','X','Y']:
730                    name = hfx+item
731                    sig = self.sigDict.get(name,-0.009)
732                    s += G2mth.ValEsd(inst[item][1],sig)+', '
733            return s
734
735        def FormatPhaseProfile(phasenam):
736            '''Format the phase-related profile parameters (size/strain)
737            with a string description.
738            return an empty string or None if there are no
739            powder histograms for this phase.
740            '''
741            s = ''
742            phasedict = self.Phases[phasenam] # pointer to current phase info
743            SGData = phasedict['General'] ['SGData']
744            for histogram in sorted(phasedict['Histograms']):
745                if histogram.startswith("HKLF"): continue # powder only
746                Histogram = self.Histograms.get(histogram)
747                if not Histogram: continue
748                hapData = phasedict['Histograms'][histogram]
749                pId = phasedict['pId']
750                hId = Histogram['hId']
751                phfx = '%d:%d:'%(pId,hId)
752                size = hapData['Size']
753                mustrain = hapData['Mustrain']
754                hstrain = hapData['HStrain']
755                if len(self.powderDict) > 1:
756                    if s:
757                        s += '\n'
758                    else:
759                        s += '  Crystallite size model "%s" for %s (microns)\n  '%(size[0],phasenam)
760                    s += '  Parameters for histogram #'+str(hId)+' '+str(histogram)+'\n'
761                else:
762                    s += '  Crystallite size model "%s" for %s (microns)\n  '%(size[0],phasenam)
763
764                names = ['Size;i','Size;mx']
765                if 'uniax' in size[0]:
766                    names = ['Size;i','Size;a','Size;mx']
767                    s += 'anisotropic axis is %s\n  '%(str(size[3]))
768                    s += 'parameters: equatorial size, axial size, G/L mix\n    '
769                    for i,item in enumerate(names):
770                        name = phfx+item
771                        sig = self.sigDict.get(name,-0.009)
772                        s += G2mth.ValEsd(size[1][i],sig)+', '
773                elif 'ellip' in size[0]:
774                    s += 'parameters: S11, S22, S33, S12, S13, S23, G/L mix\n    '
775                    for i in range(6):
776                        name = phfx+'Size:'+str(i)
777                        sig = self.sigDict.get(name,-0.009)
778                        s += G2mth.ValEsd(size[4][i],sig)+', '
779                    sig = self.sigDict.get(phfx+'Size;mx',-0.009)
780                    s += G2mth.ValEsd(size[1][2],sig)+', '
781                else:       #isotropic
782                    s += 'parameters: Size, G/L mix\n    '
783                    i = 0
784                    for item in names:
785                        name = phfx+item
786                        sig = self.sigDict.get(name,-0.009)
787                        s += G2mth.ValEsd(size[1][i],sig)+', '
788                        i = 2    #skip the aniso value
789                s += '\n  Mustrain model "%s" for %s (10^6^)\n  '%(mustrain[0],phasenam)
790                names = ['Mustrain;i','Mustrain;mx']
791                if 'uniax' in mustrain[0]:
792                    names = ['Mustrain;i','Mustrain;a','Mustrain;mx']
793                    s += 'anisotropic axis is %s\n  '%(str(size[3]))
794                    s += 'parameters: equatorial mustrain, axial mustrain, G/L mix\n    '
795                    for i,item in enumerate(names):
796                        name = phfx+item
797                        sig = self.sigDict.get(name,-0.009)
798                        s += G2mth.ValEsd(mustrain[1][i],sig)+', '
799                elif 'general' in mustrain[0]:
800                    names = 'parameters: '
801                    for i,name in enumerate(G2spc.MustrainNames(SGData)):
802                        names += name+', '
803                        if i == 9:
804                            names += '\n  '
805                    names += 'G/L mix\n    '
806                    s += names
807                    txt = ''
808                    for i in range(len(mustrain[4])):
809                        name = phfx+'Mustrain:'+str(i)
810                        sig = self.sigDict.get(name,-0.009)
811                        if len(txt) > 60:
812                            s += txt+'\n    '
813                            txt = ''
814                        txt += G2mth.ValEsd(mustrain[4][i],sig)+', '
815                    s += txt
816                    sig = self.sigDict.get(phfx+'Mustrain;mx',-0.009)
817                    s += G2mth.ValEsd(mustrain[1][2],sig)+', '
818
819                else:       #isotropic
820                    s += '  parameters: Mustrain, G/L mix\n    '
821                    i = 0
822                    for item in names:
823                        name = phfx+item
824                        sig = self.sigDict.get(name,-0.009)
825                        s += G2mth.ValEsd(mustrain[1][i],sig)+', '
826                        i = 2    #skip the aniso value
827                s1 = \n  Macrostrain parameters: '
828                names = G2spc.HStrainNames(SGData)
829                for name in names:
830                    s1 += name+', '
831                s1 += '\n    '
832                macrostrain = False
833                for i in range(len(names)):
834                    name = phfx+name[i]
835                    sig = self.sigDict.get(name,-0.009)
836                    s1 += G2mth.ValEsd(hstrain[0][i],sig)+', '
837                    if hstrain[0][i]: macrostrain = True
838                if macrostrain:
839                    s += s1 + '\n'
840                    # show revised lattice parameters here someday
841                else:
842                    s += '\n'
843            return s
844
845        def MakeUniqueLabel(lbl,labellist):
846            'Make sure that every atom label is unique'
847            lbl = lbl.strip()
848            if not lbl: # deal with a blank label
849                lbl = 'A_1'
850            if lbl not in labellist:
851                labellist.append(lbl)
852                return lbl
853            i = 1
854            prefix = lbl
855            if '_' in lbl:
856                prefix = lbl[:lbl.rfind('_')]
857                suffix = lbl[lbl.rfind('_')+1:]
858                try:
859                    i = int(suffix)+1
860                except:
861                    pass
862            while prefix+'_'+str(i) in labellist:
863                i += 1
864            else:
865                lbl = prefix+'_'+str(i)
866                labellist.append(lbl)
867
868        # Factored out to above by Jack O'Donnell, so it
869        # could be accessed by GSASIIscriptable.py
870        #  def WriteAtomsNuclear(phasenam):
871        #      'Write atom positions to CIF'
872        #      phasedict = self.Phases[phasenam] # pointer to current phase info
873        #      General = phasedict['General']
874        #      cx,ct,cs,cia = General['AtomPtrs']
875        #      Atoms = phasedict['Atoms']
876        #      cfrac = cx+3
877        #      fpfx = str(phasedict['pId'])+'::Afrac:'
878        #      for i,at in enumerate(Atoms):
879        #          fval = self.parmDict.get(fpfx+str(i),at[cfrac])
880        #          if fval != 0.0:
881        #              break
882        #      else:
883        #          WriteCIFitem(self.fp, '\n# PHASE HAS NO ATOMS!')
884        #          return
885
886        #      WriteCIFitem(self.fp, '\n# ATOMIC COORDINATES AND DISPLACEMENT PARAMETERS')
887        #      WriteCIFitem(self.fp, 'loop_ '+
888        #                   '\n   _atom_site_label'+
889        #                   '\n   _atom_site_type_symbol'+
890        #                   '\n   _atom_site_fract_x'+
891        #                   '\n   _atom_site_fract_y'+
892        #                   '\n   _atom_site_fract_z'+
893        #                   '\n   _atom_site_occupancy'+
894        #                   '\n   _atom_site_adp_type'+
895        #                   '\n   _atom_site_U_iso_or_equiv'+
896        #                   '\n   _atom_site_symmetry_multiplicity')
897
898        #      varnames = {cx:'Ax',cx+1:'Ay',cx+2:'Az',cx+3:'Afrac',
899        #                  cia+1:'AUiso',cia+2:'AU11',cia+3:'AU22',cia+4:'AU33',
900        #                  cia+5:'AU12',cia+6:'AU13',cia+7:'AU23'}
901        #      self.labellist = []
902
903        #      pfx = str(phasedict['pId'])+'::'
904        #      # loop over all atoms
905        #      naniso = 0
906        #      for i,at in enumerate(Atoms):
907        #          if phasedict['General']['Type'] == 'macromolecular':
908        #              label = '%s_%s_%s_%s'%(at[ct-1],at[ct-3],at[ct-4],at[ct-2])
909        #              s = PutInCol(MakeUniqueLabel(label,self.labellist),15) # label
910        #          else:
911        #              s = PutInCol(MakeUniqueLabel(at[ct-1],self.labellist),6) # label
912        #          fval = self.parmDict.get(fpfx+str(i),at[cfrac])
913        #          if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
914        #          s += PutInCol(FmtAtomType(at[ct]),4) # type
915        #          if at[cia] == 'I':
916        #              adp = 'Uiso '
917        #          else:
918        #              adp = 'Uani '
919        #              naniso += 1
920        #              # compute Uequiv crudely
921        #              # correct: Defined as "1/3 trace of diagonalized U matrix".
922        #              # SEE cell2GS & Uij2Ueqv to GSASIIlattice. Former is needed to make the GS matrix used by the latter.
923        #              t = 0.0
924        #              for j in (2,3,4):
925        #                  var = pfx+varnames[cia+j]+":"+str(i)
926        #                  t += self.parmDict.get(var,at[cia+j])
927        #          for j in (cx,cx+1,cx+2,cx+3,cia,cia+1):
928        #              if j in (cx,cx+1,cx+2):
929        #                  dig = 11
930        #                  sigdig = -0.00009
931        #              else:
932        #                  dig = 10
933        #                  sigdig = -0.009
934        #              if j == cia:
935        #                  s += adp
936        #              else:
937        #                  var = pfx+varnames[j]+":"+str(i)
938        #                  dvar = pfx+"d"+varnames[j]+":"+str(i)
939        #                  if dvar not in self.sigDict:
940        #                      dvar = var
941        #                  if j == cia+1 and adp == 'Uani ':
942        #                      val = t/3.
943        #                      sig = sigdig
944        #                  else:
945        #                      #print var,(var in self.parmDict),(var in self.sigDict)
946        #                      val = self.parmDict.get(var,at[j])
947        #                      sig = self.sigDict.get(dvar,sigdig)
948        #                  s += PutInCol(G2mth.ValEsd(val,sig),dig)
949        #          s += PutInCol(at[cs+1],3)
950        #          WriteCIFitem(self.fp, s)
951        #      if naniso == 0: return
952        #      # now loop over aniso atoms
953        #      WriteCIFitem(self.fp, '\nloop_' + '\n   _atom_site_aniso_label' +
954        #                   '\n   _atom_site_aniso_U_11' + '\n   _atom_site_aniso_U_22' +
955        #                   '\n   _atom_site_aniso_U_33' + '\n   _atom_site_aniso_U_12' +
956        #                   '\n   _atom_site_aniso_U_13' + '\n   _atom_site_aniso_U_23')
957        #      for i,at in enumerate(Atoms):
958        #          fval = self.parmDict.get(fpfx+str(i),at[cfrac])
959        #          if fval == 0.0: continue # ignore any atoms that have a occupancy set to 0 (exact)
960        #          if at[cia] == 'I': continue
961        #          s = PutInCol(self.labellist[i],6) # label
962        #          for j in (2,3,4,5,6,7):
963        #              sigdig = -0.0009
964        #              var = pfx+varnames[cia+j]+":"+str(i)
965        #              val = self.parmDict.get(var,at[cia+j])
966        #              sig = self.sigDict.get(var,sigdig)
967        #              s += PutInCol(G2mth.ValEsd(val,sig),11)
968        #          WriteCIFitem(self.fp, s)
969
970        # def HillSortElements(elmlist):
971        #     '''Sort elements in "Hill" order: C, H, others, (where others
972        #     are alphabetical).
973
974        #     :params list elmlist: a list of element strings
975
976        #     :returns: a sorted list of element strings
977        #     '''
978        #     newlist = []
979        #     oldlist = elmlist[:]
980        #     for elm in ('C','H'):
981        #         if elm in elmlist:
982        #             newlist.append(elm)
983        #             oldlist.pop(oldlist.index(elm))
984        #     return newlist+sorted(oldlist)
985
986        # Factored out to above by Jackson O'Donnell
987        # so that it can be accessed by GSASIIscriptable
988
989        # def WriteComposition(phasenam):
990        #     '''determine the composition for the unit cell, crudely determine Z and
991        #     then compute the composition in formula units
992        #     '''
993        #     phasedict = self.Phases[phasenam] # pointer to current phase info
994        #     General = phasedict['General']
995        #     Z = General.get('cellZ',0.0)
996        #     cx,ct,cs,cia = General['AtomPtrs']
997        #     Atoms = phasedict['Atoms']
998        #     fpfx = str(phasedict['pId'])+'::Afrac:'
999        #     cfrac = cx+3
1000        #     cmult = cs+1
1001        #     compDict = {} # combines H,D & T
1002        #     sitemultlist = []
1003        #     massDict = dict(zip(General['AtomTypes'],General['AtomMass']))
1004        #     cellmass = 0
1005        #     for i,at in enumerate(Atoms):
1006        #         atype = at[ct].strip()
1007        #         if atype.find('-') != -1: atype = atype.split('-')[0]
1008        #         if atype.find('+') != -1: atype = atype.split('+')[0]
1009        #         atype = atype[0].upper()+atype[1:2].lower() # force case conversion
1010        #         if atype == "D" or atype == "D": atype = "H"
1011        #         fvar = fpfx+str(i)
1012        #         fval = self.parmDict.get(fvar,at[cfrac])
1013        #         mult = at[cmult]
1014        #         if not massDict.get(at[ct]):
1015        #             print('Error: No mass found for atom type '+at[ct])
1016        #             print('Will not compute cell contents for phase '+phasenam)
1017        #             return
1018        #         cellmass += massDict[at[ct]]*mult*fval
1019        #         compDict[atype] = compDict.get(atype,0.0) + mult*fval
1020        #         if fval == 1: sitemultlist.append(mult)
1021        #     if len(compDict.keys()) == 0: return # no elements!
1022        #     if Z < 1: # Z has not been computed or set by user
1023        #         Z = 1
1024        #         if not sitemultlist:
1025        #             General['cellZ'] = 1
1026        #             return
1027        #         for i in range(2,min(sitemultlist)+1):
1028        #             for m in sitemultlist:
1029        #                 if m % i != 0:
1030        #                     break
1031        #                 else:
1032        #                     Z = i
1033        #         General['cellZ'] = Z # save it
1034
1035        #     # when scattering factors are included in the CIF, this needs to be
1036        #     # added to the loop here but only in the one-block case.
1037        #     # For multiblock CIFs, scattering factors go in the histogram
1038        #     # blocks  (for all atoms in all appropriate phases) - an example?:
1039#loop_
1040#    _at# om_type_symbol
1041#    _at# om_type_description
1042#    _at# om_type_scat_dispersion_real
1043#    _at# om_type_scat_dispersion_imag
1044#    _at# om_type_scat_source
1045#    'C'#  'C' 0.0033 0.0016
1046#       #                   'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1047#    'H'#  'H' 0.0000 0.0000
1048#       #                   'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1049#    'P'#  'P' 0.1023 0.0942
1050#       #                   'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1051#    'Cl# ' 'Cl' 0.1484 0.1585
1052#       #                   'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1053#    'Cu# ' 'Cu' 0.3201 1.2651
1054#       #                   'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
1055
1056        #     #if oneblock: # add scattering factors for current phase here
1057        #     WriteCIFitem(self.fp, '\nloop_  _atom_type_symbol _atom_type_number_in_cell')
1058        #     formula = ''
1059        #     for elem in HillSortElements(compDict.keys()):
1060        #         WriteCIFitem(self.fp, '  ' + PutInCol(elem,4) +
1061        #                      G2mth.ValEsd(compDict[elem],-0.009,True))
1062        #         if formula: formula += " "
1063        #         formula += elem
1064        #         if compDict[elem] == Z: continue
1065        #         formula += G2mth.ValEsd(compDict[elem]/Z,-0.009,True)
1066        #     WriteCIFitem(self.fp,  '\n# Note that Z affects _cell_formula_sum and _weight')
1067        #     WriteCIFitem(self.fp,  '_cell_formula_units_Z',str(Z))
1068        #     WriteCIFitem(self.fp,  '_chemical_formula_sum',formula)
1069        #     WriteCIFitem(self.fp,  '_chemical_formula_weight',
1070        #                   G2mth.ValEsd(cellmass/Z,-0.09,True))
1071
1072        def WriteDistances(phasenam,SymOpList,offsetList,symOpList,G2oprList):
1073            '''Report bond distances and angles for the CIF
1074
1075            Note that _geom_*_symmetry_* fields are values of form
1076            n_klm where n is the symmetry operation in SymOpList (counted
1077            starting with 1) and (k-5, l-5, m-5) are translations to add
1078            to (x,y,z). See
1079            http://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Igeom_angle_site_symmetry_.html
1080
1081            TODO: need a method to select publication flags for distances/angles
1082            '''
1083            phasedict = self.Phases[phasenam] # pointer to current phase info
1084            Atoms = phasedict['Atoms']
1085            generalData = phasedict['General']
1086            # create a dict for storing Pub flag for bonds/angles, if needed
1087            if phasedict['General'].get("DisAglHideFlag") is None:
1088                phasedict['General']["DisAglHideFlag"] = {}
1089            DisAngSel = phasedict['General']["DisAglHideFlag"]
1090            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1091            cn = ct-1
1092            fpfx = str(phasedict['pId'])+'::Afrac:'
1093            cfrac = cx+3
1094            DisAglData = {}
1095            # create a list of atoms, but skip atoms with zero occupancy
1096            xyz = []
1097            fpfx = str(phasedict['pId'])+'::Afrac:'
1098            for i,atom in enumerate(Atoms):
1099                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
1100                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
1101            if 'DisAglCtls' not in generalData:
1102                # should not happen, since DisAglDialog should be called
1103                # for all phases before getting here
1104                dlg = G2G.DisAglDialog(
1105                    self.G2frame,
1106                    {},
1107                    generalData)
1108                if dlg.ShowModal() == wx.ID_OK:
1109                    generalData['DisAglCtls'] = dlg.GetData()
1110                else:
1111                    dlg.Destroy()
1112                    return
1113                dlg.Destroy()
1114            DisAglData['OrigAtoms'] = xyz
1115            DisAglData['TargAtoms'] = xyz
1116            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
1117                generalData['SGData'])
1118
1119#            xpandSGdata = generalData['SGData'].copy()
1120#            xpandSGdata.update({'SGOps':symOpList,
1121#                                'SGInv':False,
1122#                                'SGLatt':'P',
1123#                                'SGCen':np.array([[0, 0, 0]]),})
1124#            DisAglData['SGData'] = xpandSGdata
1125            DisAglData['SGData'] = generalData['SGData'].copy()
1126
1127            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
1128            if 'pId' in phasedict:
1129                DisAglData['pId'] = phasedict['pId']
1130                DisAglData['covData'] = self.OverallParms['Covariance']
1131            try:
1132                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
1133                    generalData['DisAglCtls'],
1134                    DisAglData)
1135            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
1136                print(u'**** ERROR computing distances & angles for phase {} ****\nresetting to default values'.format(phasenam))
1137                data = generalData['DisAglCtls'] = {}
1138                data['Name'] = generalData['Name']
1139                data['Factors'] = [0.85,0.85]
1140                data['AtomTypes'] = generalData['AtomTypes']
1141                data['BondRadii'] = generalData['BondRadii'][:]
1142                data['AngleRadii'] = generalData['AngleRadii'][:]
1143                try:
1144                    AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
1145                        generalData['DisAglCtls'],
1146                        DisAglData)
1147                except:
1148                    print('Reset failed. To fix this, use the Reset button in the "edit distance/angle menu" for this phase')
1149                    return
1150
1151            # loop over interatomic distances for this phase
1152            WriteCIFitem(self.fp, '\n# MOLECULAR GEOMETRY')
1153            WriteCIFitem(self.fp, 'loop_' +
1154                         '\n   _geom_bond_atom_site_label_1' +
1155                         '\n   _geom_bond_atom_site_label_2' +
1156                         '\n   _geom_bond_distance' +
1157                         '\n   _geom_bond_site_symmetry_1' +
1158                         '\n   _geom_bond_site_symmetry_2' +
1159                         '\n   _geom_bond_publ_flag')
1160
1161            for i in sorted(AtomLabels.keys()):
1162                Dist = DistArray[i]
1163                for D in Dist:
1164                    line = '  '+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[D[0]],6)
1165                    sig = D[4]
1166                    if sig == 0: sig = -0.00009
1167                    line += PutInCol(G2mth.ValEsd(D[3],sig,True),10)
1168                    line += "  1_555 "
1169                    line += " {:3d}_".format(G2opcodes.index(D[2])+1)
1170                    for d in D[1]:
1171                        line += "{:1d}".format(d+5)
1172                    if DisAngSel.get((i,tuple(D[0:3]))):
1173                        line += " no"
1174                    else:
1175                        line += " yes"
1176                    WriteCIFitem(self.fp, line)
1177
1178            # loop over interatomic angles for this phase
1179            WriteCIFitem(self.fp, '\nloop_' +
1180                         '\n   _geom_angle_atom_site_label_1' +
1181                         '\n   _geom_angle_atom_site_label_2' +
1182                         '\n   _geom_angle_atom_site_label_3' +
1183                         '\n   _geom_angle' +
1184                         '\n   _geom_angle_site_symmetry_1' +
1185                         '\n   _geom_angle_site_symmetry_2' +
1186                         '\n   _geom_angle_site_symmetry_3' +
1187                         '\n   _geom_angle_publ_flag')
1188
1189            for i in sorted(AtomLabels.keys()):
1190                Dist = DistArray[i]
1191                for k,j,tup in AngArray[i]:
1192                    Dj = Dist[j]
1193                    Dk = Dist[k]
1194                    line = '  '+PutInCol(AtomLabels[Dj[0]],6)+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[Dk[0]],6)
1195                    sig = tup[1]
1196                    if sig == 0: sig = -0.009
1197                    line += PutInCol(G2mth.ValEsd(tup[0],sig,True),10)
1198                    line += " {:3d}_".format(G2opcodes.index(Dj[2])+1)
1199                    for d in Dj[1]:
1200                        line += "{:1d}".format(d+5)
1201                    line += "  1_555 "
1202                    line += " {:3d}_".format(G2opcodes.index(Dk[2])+1)
1203                    for d in Dk[1]:
1204                        line += "{:1d}".format(d+5)
1205                    key = (tuple(Dk[0:3]),i,tuple(Dj[0:3]))
1206                    if DisAngSel.get(key):
1207                        line += " no"
1208                    else:
1209                        line += " yes"
1210                    WriteCIFitem(self.fp, line)
1211
1212        def WritePhaseInfo(phasenam,hist=None):
1213            'Write out the phase information for the selected phase'
1214            WriteCIFitem(self.fp, '\n# phase info for '+str(phasenam) + ' follows')
1215            phasedict = self.Phases[phasenam] # pointer to current phase info
1216            WriteCIFitem(self.fp, '_pd_phase_name', phasenam)
1217            cellList,cellSig = self.GetCell(phasenam)
1218            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
1219            names = ['length_a','length_b','length_c',
1220                     'angle_alpha','angle_beta ','angle_gamma',
1221                     'volume']
1222            prevsig = 0
1223            for lbl,defsig,val,sig in zip(names,defsigL,cellList,cellSig):
1224                if sig:
1225                    txt = G2mth.ValEsd(val,sig)
1226                    prevsig = -sig # use this as the significance for next value
1227                else:
1228                    txt = G2mth.ValEsd(val,min(defsig,prevsig),True)
1229                WriteCIFitem(self.fp, '_cell_'+lbl,txt)
1230
1231            WriteCIFitem(self.fp, '_symmetry_cell_setting',
1232                         phasedict['General']['SGData']['SGSys'])
1233
1234            spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
1235            # regularize capitalization and remove trailing H/R
1236            spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
1237            WriteCIFitem(self.fp, '_symmetry_space_group_name_H-M',spacegroup)
1238
1239            # generate symmetry operations including centering and center of symmetry
1240            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
1241                phasedict['General']['SGData'])
1242            WriteCIFitem(self.fp, 'loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
1243            for i,op in enumerate(SymOpList,start=1):
1244                WriteCIFitem(self.fp, '   {:3d}  {:}'.format(i,op.lower()))
1245
1246            # loop over histogram(s) used in this phase
1247            if not oneblock and not self.quickmode and not hist:
1248                # report pointers to the histograms used in this phase
1249                histlist = []
1250                for hist in self.Phases[phasenam]['Histograms']:
1251                    if self.Phases[phasenam]['Histograms'][hist]['Use']:
1252                        if phasebyhistDict.get(hist):
1253                            phasebyhistDict[hist].append(phasenam)
1254                        else:
1255                            phasebyhistDict[hist] = [phasenam,]
1256                        blockid = datablockidDict.get(hist)
1257                        if not blockid:
1258                            print("Internal error: no block for data. Phase "+str(
1259                                phasenam)+" histogram "+str(hist))
1260                            histlist = []
1261                            break
1262                        histlist.append(blockid)
1263
1264                if len(histlist) == 0:
1265                    WriteCIFitem(self.fp, '# Note: phase has no associated data')
1266
1267            # report atom params
1268            if phasedict['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
1269                try:
1270                    self.labellist
1271                except AttributeError:
1272                    self.labellist = []
1273                WriteAtomsNuclear(self.fp, self.Phases[phasenam], phasenam,
1274                                  self.parmDict, self.sigDict, self.labellist)
1275            else:
1276                try:
1277                    self.labellist
1278                except AttributeError:
1279                    self.labellist = []
1280                WriteAtomsMagnetic(self.fp, self.Phases[phasenam], phasenam,
1281                                  self.parmDict, self.sigDict, self.labellist)
1282                raise Exception("no export for "+str(phasedict['General']['Type'])+" coordinates implemented")
1283            # report cell contents
1284            WriteComposition(self.fp, self.Phases[phasenam], phasenam, self.parmDict)
1285            if not self.quickmode and phasedict['General']['Type'] == 'nuclear':      # report distances and angles
1286                WriteDistances(phasenam,SymOpList,offsetList,symOpList,G2oprList)
1287            if 'Map' in phasedict['General'] and 'minmax' in phasedict['General']['Map']:
1288                WriteCIFitem(self.fp, '\n# Difference density results')
1289                MinMax = phasedict['General']['Map']['minmax']
1290                WriteCIFitem(self.fp, '_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
1291                WriteCIFitem(self.fp, '_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
1292
1293        def Yfmt(ndec,val):
1294            'Format intensity values'
1295            out = ("{:."+str(ndec)+"f}").format(val)
1296            out = out.rstrip('0')  # strip zeros to right of decimal
1297            return out.rstrip('.')  # and decimal place when not needed
1298
1299        def WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,nRefSets=1):
1300            'Write reflection statistics'
1301            WriteCIFitem(self.fp, '_reflns_number_total', str(refcount))
1302            if hklmin is not None and nRefSets == 1: # hkl range has no meaning with multiple phases
1303                WriteCIFitem(self.fp, '_reflns_limit_h_min', str(int(hklmin[0])))
1304                WriteCIFitem(self.fp, '_reflns_limit_h_max', str(int(hklmax[0])))
1305                WriteCIFitem(self.fp, '_reflns_limit_k_min', str(int(hklmin[1])))
1306                WriteCIFitem(self.fp, '_reflns_limit_k_max', str(int(hklmax[1])))
1307                WriteCIFitem(self.fp, '_reflns_limit_l_min', str(int(hklmin[2])))
1308                WriteCIFitem(self.fp, '_reflns_limit_l_max', str(int(hklmax[2])))
1309            if hklmin is not None:
1310                WriteCIFitem(self.fp, '_reflns_d_resolution_low  ', G2mth.ValEsd(dmax,-0.009))
1311                WriteCIFitem(self.fp, '_reflns_d_resolution_high ', G2mth.ValEsd(dmin,-0.009))
1312
1313        def WritePowderData(histlbl):
1314            'Write out the selected powder diffraction histogram info'
1315            histblk = self.Histograms[histlbl]
1316            inst = histblk['Instrument Parameters'][0]
1317            hId = histblk['hId']
1318            pfx = ':' + str(hId) + ':'
1319
1320            if 'Lam1' in inst:
1321                ratio = self.parmDict.get('I(L2)/I(L1)',inst['I(L2)/I(L1)'][1])
1322                sratio = self.sigDict.get('I(L2)/I(L1)',-0.0009)
1323                lam1 = self.parmDict.get('Lam1',inst['Lam1'][1])
1324                slam1 = self.sigDict.get('Lam1',-0.00009)
1325                lam2 = self.parmDict.get('Lam2',inst['Lam2'][1])
1326                slam2 = self.sigDict.get('Lam2',-0.00009)
1327                # always assume Ka1 & Ka2 if two wavelengths are present
1328                WriteCIFitem(self.fp, '_diffrn_radiation_type','K\\a~1,2~')
1329                WriteCIFitem(self.fp, 'loop_' +
1330                             '\n   _diffrn_radiation_wavelength' +
1331                             '\n   _diffrn_radiation_wavelength_wt' +
1332                             '\n   _diffrn_radiation_wavelength_id')
1333                WriteCIFitem(self.fp, '  ' + PutInCol(G2mth.ValEsd(lam1,slam1),15)+
1334                             PutInCol('1.0',15) +
1335                             PutInCol('1',5))
1336                WriteCIFitem(self.fp, '  ' + PutInCol(G2mth.ValEsd(lam2,slam2),15)+
1337                             PutInCol(G2mth.ValEsd(ratio,sratio),15)+
1338                             PutInCol('2',5))
1339            elif 'Lam' in inst:
1340                lam1 = self.parmDict.get('Lam',inst['Lam'][1])
1341                slam1 = self.sigDict.get('Lam',-0.00009)
1342                WriteCIFitem(self.fp, '_diffrn_radiation_wavelength',G2mth.ValEsd(lam1,slam1))
1343
1344            if not oneblock:
1345                if not phasebyhistDict.get(histlbl):
1346                    WriteCIFitem(self.fp, '\n# No phases associated with this data set')
1347                else:
1348                    WriteCIFitem(self.fp, '\n# PHASE TABLE')
1349                    WriteCIFitem(self.fp, 'loop_' +
1350                                 '\n   _pd_phase_id' +
1351                                 '\n   _pd_phase_block_id' +
1352                                 '\n   _pd_phase_mass_%')
1353                    wtFrSum = 0.
1354                    for phasenam in phasebyhistDict.get(histlbl):
1355                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
1356                        General = self.Phases[phasenam]['General']
1357                        wtFrSum += hapData['Scale'][0]*General['Mass']
1358
1359                    for phasenam in phasebyhistDict.get(histlbl):
1360                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
1361                        General = self.Phases[phasenam]['General']
1362                        wtFr = hapData['Scale'][0]*General['Mass']/wtFrSum
1363                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
1364                        if pfx+'Scale' in self.sigDict:
1365                            sig = self.sigDict[pfx+'Scale']*wtFr/hapData['Scale'][0]
1366                        else:
1367                            sig = -0.0001
1368                        WriteCIFitem(self.fp,
1369                            '  '+
1370                            str(self.Phases[phasenam]['pId']) +
1371                            '  '+datablockidDict[phasenam]+
1372                            '  '+G2mth.ValEsd(wtFr,sig)
1373                            )
1374                    WriteCIFitem(self.fp, 'loop_' +
1375                                 '\n   _gsas_proc_phase_R_F_factor' +
1376                                 '\n   _gsas_proc_phase_R_Fsqd_factor' +
1377                                 '\n   _gsas_proc_phase_id' +
1378                                 '\n   _gsas_proc_phase_block_id')
1379                    for phasenam in phasebyhistDict.get(histlbl):
1380                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
1381                        WriteCIFitem(self.fp,
1382                            '  '+
1383                            '  '+G2mth.ValEsd(histblk[pfx+'Rf']/100.,-.00009) +
1384                            '  '+G2mth.ValEsd(histblk[pfx+'Rf^2']/100.,-.00009)+
1385                            '  '+str(self.Phases[phasenam]['pId'])+
1386                            '  '+datablockidDict[phasenam]
1387                            )
1388            else:
1389                # single phase in this histogram
1390                pfx = '0:'+str(hId)+':'
1391                WriteCIFitem(self.fp, '_refine_ls_R_F_factor      ','%.5f'%(histblk[pfx+'Rf']/100.))
1392                WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor   ','%.5f'%(histblk[pfx+'Rf^2']/100.))
1393
1394            WriteCIFitem(self.fp, '_pd_proc_ls_prof_R_factor   ','%.5f'%(histblk['R']/100.))
1395            WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_factor  ','%.5f'%(histblk['wR']/100.))
1396            WriteCIFitem(self.fp, '_gsas_proc_ls_prof_R_B_factor ','%.5f'%(histblk['Rb']/100.))
1397            WriteCIFitem(self.fp, '_gsas_proc_ls_prof_wR_B_factor','%.5f'%(histblk['wRb']/100.))
1398            WriteCIFitem(self.fp, '_pd_proc_ls_prof_wR_expected','%.5f'%(histblk['wRmin']/100.))
1399
1400            if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
1401                WriteCIFitem(self.fp, '_diffrn_radiation_probe','x-ray')
1402                pola = histblk['Instrument Parameters'][0].get('Polariz.')
1403                if pola:
1404                    pfx = ':' + str(hId) + ':'
1405                    sig = self.sigDict.get(pfx+'Polariz.',-0.0009)
1406                    txt = G2mth.ValEsd(pola[1],sig)
1407                    WriteCIFitem(self.fp, '_diffrn_radiation_polarisn_ratio',txt)
1408            elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
1409                WriteCIFitem(self.fp, '_diffrn_radiation_probe','neutron')
1410            if 'T' in inst['Type'][0]:
1411                txt = G2mth.ValEsd(inst['2-theta'][0],-0.009)
1412                WriteCIFitem(self.fp, '_pd_meas_2theta_fixed',txt)
1413
1414            # TODO: this will need help from Bob
1415            #if not oneblock:
1416            #WriteCIFitem(self.fp, '\n# SCATTERING FACTOR INFO')
1417            #WriteCIFitem(self.fp, 'loop_  _atom_type_symbol')
1418            #if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
1419            #    WriteCIFitem(self.fp, '      _atom_type_scat_dispersion_real')
1420            #    WriteCIFitem(self.fp, '      _atom_type_scat_dispersion_imag')
1421            #    for lbl in ('a1','a2','a3', 'a4', 'b1', 'b2', 'b3', 'b4', 'c'):
1422            #        WriteCIFitem(self.fp, '      _atom_type_scat_Cromer_Mann_'+lbl)
1423            #elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
1424            #    WriteCIFitem(self.fp, '      _atom_type_scat_length_neutron')
1425            #WriteCIFitem(self.fp, '      _atom_type_scat_source')
1426
1427            WriteCIFitem(self.fp, '_pd_proc_ls_background_function',FormatBackground(histblk['Background'],histblk['hId']))
1428
1429            # TODO: this will need help from Bob
1430            #WriteCIFitem(self.fp, '_exptl_absorpt_process_details','?')
1431            #WriteCIFitem(self.fp, '_exptl_absorpt_correction_T_min','?')
1432            #WriteCIFitem(self.fp, '_exptl_absorpt_correction_T_max','?')
1433            #C extinction
1434            #WRITE(IUCIF,'(A)') '# Extinction correction'
1435            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_min',TEXT(1:10))
1436            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_max',TEXT(11:20))
1437
1438            if not oneblock:                 # instrumental profile terms go here
1439                WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
1440                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1441
1442            #refprx = '_refln.' # mm
1443            refprx = '_refln_' # normal
1444            # data collection parameters for the powder dataset
1445
1446            temperature = histblk['Sample Parameters'].get('Temperature') # G2 uses K
1447            if not temperature:
1448                T = '?'
1449            else:
1450                T = G2mth.ValEsd(temperature,-0.009,True) # CIF uses K
1451            WriteCIFitem(self.fp, '_diffrn_ambient_temperature',T)
1452
1453            pressure = histblk['Sample Parameters'].get('Pressure') #G2 uses mega-Pascal
1454            if not pressure:
1455                P = '?'
1456            else:
1457                P = G2mth.ValEsd(pressure*1000,-0.09,True) # CIF uses kilopascal
1458            WriteCIFitem(self.fp, '_diffrn_ambient_pressure',P)
1459
1460            WriteCIFitem(self.fp, '\n# STRUCTURE FACTOR TABLE')
1461            # compute maximum intensity reflection
1462            Imax = 0
1463            for phasenam in histblk['Reflection Lists']:
1464                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1465                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
1466                I100 = scale*refList.T[8]*refList.T[11]
1467                #Icorr = np.array([refl[13] for refl in histblk['Reflection Lists'][phasenam]])[0]
1468                #FO2 = np.array([refl[8] for refl in histblk['Reflection Lists'][phasenam]])
1469                #I100 = scale*FO2*Icorr
1470                Imax = max(Imax,max(I100))
1471
1472            WriteCIFitem(self.fp, 'loop_')
1473            if len(histblk['Reflection Lists'].keys()) > 1:
1474                WriteCIFitem(self.fp, '   _pd_refln_phase_id')
1475            WriteCIFitem(self.fp, '   ' + refprx + 'index_h' +
1476                         '\n   ' + refprx + 'index_k' +
1477                         '\n   ' + refprx + 'index_l' +
1478                         '\n   ' + refprx + 'F_squared_meas' +
1479                         '\n   ' + refprx + 'F_squared_calc' +
1480                         '\n   ' + refprx + 'phase_calc' +
1481                         '\n   _pd_refln_d_spacing')
1482            if Imax > 0:
1483                WriteCIFitem(self.fp, '   _gsas_i100_meas')
1484
1485            refcount = 0
1486            hklmin = None
1487            hklmax = None
1488            dmax = None
1489            dmin = None
1490            for phasenam in histblk['Reflection Lists']:
1491                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1492                phaseid = self.Phases[phasenam]['pId']
1493                refcount += len(histblk['Reflection Lists'][phasenam]['RefList'])
1494                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
1495                I100 = scale*refList.T[8]*refList.T[11]
1496                for j,ref in enumerate(histblk['Reflection Lists'][phasenam]['RefList']):
1497                    if DEBUG:
1498                        print('DEBUG: skipping reflection list')
1499                        break
1500                    if hklmin is None:
1501                        hklmin = ref[0:3]
1502                        hklmax = ref[0:3]
1503                        dmax = dmin = ref[4]
1504                    if len(histblk['Reflection Lists'].keys()) > 1:
1505                        s = PutInCol(phaseid,2)
1506                    else:
1507                        s = ""
1508                    for i,hkl in enumerate(ref[0:3]):
1509                        hklmax[i] = max(hkl,hklmax[i])
1510                        hklmin[i] = min(hkl,hklmin[i])
1511                        s += PutInCol(int(hkl),4)
1512                    for I in ref[8:10]:
1513                        s += PutInCol(G2mth.ValEsd(I,-0.0009),10)
1514                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1515                    dmax = max(dmax,ref[4])
1516                    dmin = min(dmin,ref[4])
1517                    s += PutInCol(G2mth.ValEsd(ref[4],-0.009),8)
1518                    if Imax > 0:
1519                        s += PutInCol(G2mth.ValEsd(100.*I100[j]/Imax,-0.09),6)
1520                    WriteCIFitem(self.fp, "  "+s)
1521
1522            WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,len(histblk['Reflection Lists']))
1523            WriteCIFitem(self.fp, '\n# POWDER DATA TABLE')
1524            # is data fixed step? If the step varies by <0.01% treat as fixed step
1525            steps = histblk['Data'][0][1:] - histblk['Data'][0][:-1]
1526            if abs(max(steps)-min(steps)) > abs(max(steps))/10000.:
1527                fixedstep = False
1528            else:
1529                fixedstep = True
1530
1531            zero = None
1532            if fixedstep and 'T' not in inst['Type'][0]: # and not TOF
1533                WriteCIFitem(self.fp, '_pd_meas_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0],-0.00009))
1534                WriteCIFitem(self.fp, '_pd_meas_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1],-0.00009))
1535                WriteCIFitem(self.fp, '_pd_meas_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1536                # zero correct, if defined
1537                zerolst = histblk['Instrument Parameters'][0].get('Zero')
1538                if zerolst: zero = zerolst[1]
1539                zero = self.parmDict.get('Zero',zero)
1540                if zero:
1541                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0]-zero,-0.00009))
1542                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1]-zero,-0.00009))
1543                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1544
1545            if zero:
1546                WriteCIFitem(self.fp, '_pd_proc_number_of_points', str(len(histblk['Data'][0])))
1547            else:
1548                WriteCIFitem(self.fp, '_pd_meas_number_of_points', str(len(histblk['Data'][0])))
1549            WriteCIFitem(self.fp, '\nloop_')
1550            #            WriteCIFitem(self.fp, '   _pd_proc_d_spacing') # need easy way to get this
1551            if not fixedstep:
1552                if zero:
1553                    WriteCIFitem(self.fp, '   _pd_proc_2theta_corrected')
1554                elif 'T' in inst['Type'][0]: # and not TOF
1555                    WriteCIFitem(self.fp, '   _pd_meas_time_of_flight')
1556                else:
1557                    WriteCIFitem(self.fp, '   _pd_meas_2theta_scan')
1558            # at least for now, always report weights.
1559            #if countsdata:
1560            #    WriteCIFitem(self.fp, '   _pd_meas_counts_total')
1561            #else:
1562            WriteCIFitem(self.fp, '   _pd_meas_intensity_total')
1563            WriteCIFitem(self.fp, '   _pd_calc_intensity_total')
1564            WriteCIFitem(self.fp, '   _pd_proc_intensity_bkg_calc')
1565            WriteCIFitem(self.fp, '   _pd_proc_ls_weight')
1566            maxY = max(histblk['Data'][1].max(),histblk['Data'][3].max())
1567            if maxY < 0: maxY *= -10 # this should never happen, but...
1568            ndec = max(0,10-int(np.log10(maxY))-1) # 10 sig figs should be enough
1569            maxSU = histblk['Data'][2].max()
1570            if maxSU < 0: maxSU *= -1 # this should never happen, but...
1571            ndecSU = max(0,8-int(np.log10(maxSU))-1) # 8 sig figs should be enough
1572            lowlim,highlim = histblk['Limits'][1]
1573
1574            if DEBUG:
1575                print('DEBUG: skipping profile list')
1576            else:
1577                for x,yobs,yw,ycalc,ybkg in zip(histblk['Data'][0],
1578                                                histblk['Data'][1],
1579                                                histblk['Data'][2],
1580                                                histblk['Data'][3],
1581                                                histblk['Data'][4]):
1582                    if lowlim <= x <= highlim:
1583                        pass
1584                    else:
1585                        yw = 0.0 # show the point is not in use
1586
1587                    if fixedstep:
1588                        s = ""
1589                    elif zero:
1590                        s = PutInCol(G2mth.ValEsd(x-zero,-0.00009),10)
1591                    else:
1592                        s = PutInCol(G2mth.ValEsd(x,-0.00009),10)
1593                    s += PutInCol(Yfmt(ndec,yobs),12)
1594                    s += PutInCol(Yfmt(ndec,ycalc),12)
1595                    s += PutInCol(Yfmt(ndec,ybkg),11)
1596                    s += PutInCol(Yfmt(ndecSU,yw),9)
1597                    WriteCIFitem(self.fp, "  "+s)
1598
1599        def WriteSingleXtalData(histlbl):
1600            'Write out the selected single crystal histogram info'
1601            histblk = self.Histograms[histlbl]
1602
1603            #refprx = '_refln.' # mm
1604            refprx = '_refln_' # normal
1605
1606            WriteCIFitem(self.fp, '\n# STRUCTURE FACTOR TABLE')
1607            WriteCIFitem(self.fp, 'loop_' +
1608                         '\n   ' + refprx + 'index_h' +
1609                         '\n   ' + refprx + 'index_k' +
1610                         '\n   ' + refprx + 'index_l' +
1611                         '\n   ' + refprx + 'F_squared_meas' +
1612                         '\n   ' + refprx + 'F_squared_sigma' +
1613                         '\n   ' + refprx + 'F_squared_calc' +
1614                         '\n   ' + refprx + 'phase_calc'
1615                         )
1616
1617            hklmin = None
1618            hklmax = None
1619            dmax = None
1620            dmin = None
1621            refcount = len(histblk['Data']['RefList'])
1622            for ref in histblk['Data']['RefList']:
1623                if ref[3] <= 0:      #skip user rejected reflections (mul <= 0)
1624                    continue
1625                s = "  "
1626                if hklmin is None:
1627                    hklmin = ref[0:3]
1628                    hklmax = ref[0:3]
1629                    dmax = dmin = ref[4]
1630                for i,hkl in enumerate(ref[0:3]):
1631                    hklmax[i] = max(hkl,hklmax[i])
1632                    hklmin[i] = min(hkl,hklmin[i])
1633                    s += PutInCol(int(hkl),4)
1634                if ref[5] == 0.0:
1635                    s += PutInCol(G2mth.ValEsd(ref[8],0),12)
1636                    s += PutInCol('.',10)
1637                    s += PutInCol(G2mth.ValEsd(ref[9],0),12)
1638                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1639                else:
1640                    sig = ref[6] * ref[8] / ref[5]
1641                    s += PutInCol(G2mth.ValEsd(ref[8],-abs(sig/10)),12)
1642                    s += PutInCol(G2mth.ValEsd(sig,-abs(sig)/10.),10)
1643                    s += PutInCol(G2mth.ValEsd(ref[9],-abs(sig/10)),12)
1644                s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1645                dmax = max(dmax,ref[4])
1646                dmin = min(dmin,ref[4])
1647                WriteCIFitem(self.fp, s)
1648            if not self.quickmode: # statistics only in a full CIF
1649                WriteReflStat(refcount,hklmin,hklmax,dmin,dmax)
1650                hId = histblk['hId']
1651                hfx = '0:'+str(hId)+':'
1652                phfx = '%d:%d:'%(0,hId)
1653                extType,extModel,extParms = self.Phases[phasenam]['Histograms'][histlbl]['Extinction']
1654                if extModel != 'None':
1655                    WriteCIFitem(self.fp, '# Extinction scaled by 1.e5')
1656                    WriteCIFitem(self.fp, '_refine_ls_extinction_method','Becker-Coppens %s %s'%(extModel,extType))
1657                    sig = -1.e-3
1658                    if extModel == 'Primary':
1659                        parm = extParms['Ep'][0]*1.e5
1660                        if extParms['Ep'][1]:
1661                            sig = self.sigDict[phfx+'Ep']*1.e5
1662                        text = G2mth.ValEsd(parm,sig)
1663                    elif extModel == 'Secondary Type I':
1664                        parm = extParms['Eg'][0]*1.e5
1665                        if extParms['Eg'][1]:
1666                            sig = self.sigDict[phfx+'Eg']*1.e5
1667                        text = G2mth.ValEsd(parm,sig)
1668                    elif extModel == 'Secondary Type II':
1669                        parm = extParms['Es'][0]*1.e5
1670                        if extParms['Es'][1]:
1671                            sig = self.sigDict[phfx+'Es']*1.e5
1672                        text = G2mth.ValEsd(parm,sig)
1673                    elif extModel == 'Secondary Type I & II':
1674                        parm = extParms['Eg'][0]*1.e5
1675                        if extParms['Es'][1]:
1676                            sig = self.sigDict[phfx+'Es']*1.e5
1677                        text = G2mth.ValEsd(parm,sig)
1678                        sig = -1.0e-3
1679                        parm = extParms['Es'][0]*1.e5
1680                        if extParms['Es'][1]:
1681                            sig = self.sigDict[phfx+'Es']*1.e5
1682                        text += G2mth.ValEsd(parm,sig)
1683                    WriteCIFitem(self.fp, '_refine_ls_extinction_coef',text)
1684                    WriteCIFitem(self.fp, '_refine_ls_extinction_expression','Becker & Coppens (1974). Acta Cryst. A30, 129-147')
1685
1686                WriteCIFitem(self.fp, '_refine_ls_wR_factor_gt    ','%.4f'%(histblk['wR']/100.))
1687                WriteCIFitem(self.fp, '_refine_ls_R_factor_gt     ','%.4f'%(histblk[hfx+'Rf']/100.))
1688                WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor   ','%.4f'%(histblk[hfx+'Rf^2']/100.))
1689        def EditAuthor(event=None):
1690            'dialog to edit the CIF author info'
1691            'Edit the CIF author name'
1692            dlg = G2G.SingleStringDialog(self.G2frame,
1693                                          'Get CIF Author',
1694                                          'Provide CIF Author name (Last, First)',
1695                                          value=self.author)
1696            if not dlg.Show():
1697                dlg.Destroy()
1698                return False  # cancel was pressed
1699            self.author = dlg.GetValue()
1700            self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
1701            dlg.Destroy()
1702            try:
1703                self.OverallParms['Controls']["Author"] = self.author # save for future
1704            except KeyError:
1705                pass
1706            return True
1707
1708        def EditInstNames(event=None):
1709            'Provide a dialog for editing instrument names'
1710            dictlist = []
1711            keylist = []
1712            lbllist = []
1713            for hist in self.Histograms:
1714                if hist.startswith("PWDR"):
1715                    key2 = "Sample Parameters"
1716                    d = self.Histograms[hist][key2]
1717                elif hist.startswith("HKLF"):
1718                    key2 = "Instrument Parameters"
1719                    d = self.Histograms[hist][key2][0]
1720
1721                lbllist.append(hist)
1722                dictlist.append(d)
1723                keylist.append('InstrName')
1724                instrname = d.get('InstrName')
1725                if instrname is None:
1726                    d['InstrName'] = ''
1727            return G2G.CallScrolledMultiEditor(
1728                self.G2frame,dictlist,keylist,
1729                prelbl=range(1,len(dictlist)+1),
1730                postlbl=lbllist,
1731                title='Instrument names',
1732                header="Edit instrument names. Note that a non-blank\nname is required for all histograms",
1733                CopyButton=True,ASCIIonly=True)
1734
1735        def EditRanges(event):
1736            '''Edit the bond distance/angle search range; phase is determined from
1737            a pointer placed in the button object (.phasedict) that references the
1738            phase dictionary
1739            '''
1740            but = event.GetEventObject()
1741            phasedict = but.phasedict
1742            dlg = G2G.DisAglDialog(
1743                self.G2frame,
1744                phasedict['General']['DisAglCtls'], # edited
1745                phasedict['General'], # defaults
1746                )
1747            if dlg.ShowModal() == wx.ID_OK:
1748                phasedict['General']['DisAglCtls'] = dlg.GetData()
1749            dlg.Destroy()
1750
1751        def EditCIFDefaults():
1752            '''Fills the CIF Defaults window with controls for editing various CIF export
1753            parameters (mostly related to templates).
1754            '''
1755            self.cifdefs.DestroyChildren()
1756            self.cifdefs.SetTitle('Edit CIF settings')
1757            vbox = wx.BoxSizer(wx.VERTICAL)
1758            vbox.Add(wx.StaticText(self.cifdefs, wx.ID_ANY,'Creating file '+self.filename))
1759            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit CIF Author')
1760            but.Bind(wx.EVT_BUTTON,EditAuthor)
1761            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1762            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit Instrument Name(s)')
1763            but.Bind(wx.EVT_BUTTON,EditInstNames)
1764            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1765            cpnl = wxscroll.ScrolledPanel(self.cifdefs,size=(300,300))
1766            cbox = wx.BoxSizer(wx.VERTICAL)
1767            G2G.HorizontalLine(cbox,cpnl)
1768            cbox.Add(
1769                CIFtemplateSelect(self.cifdefs,
1770                                  cpnl,'publ',self.OverallParms['Controls'],
1771                                  EditCIFDefaults,
1772                                  "Publication (overall) template",
1773                                  ),
1774                0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1775            for phasenam in sorted(self.Phases.keys()):
1776                G2G.HorizontalLine(cbox,cpnl)
1777                title = 'Phase '+phasenam
1778                phasedict = self.Phases[phasenam] # pointer to current phase info
1779                cbox.Add(
1780                    CIFtemplateSelect(self.cifdefs,
1781                                      cpnl,'phase',phasedict['General'],
1782                                      EditCIFDefaults,
1783                                      title,
1784                                      phasenam),
1785                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1786                cpnl.SetSizer(cbox)
1787                if phasedict['General']['Type'] == 'nuclear':
1788                    but = wx.Button(cpnl, wx.ID_ANY,'Edit distance/angle ranges')
1789                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1790                    cbox.Add((-1,2))
1791                    but.phasedict = self.Phases[phasenam]  # set a pointer to current phase info
1792                    but.Bind(wx.EVT_BUTTON,EditRanges)     # phase bond/angle ranges
1793                    but = wx.Button(cpnl, wx.ID_ANY,'Set distance/angle publication flags')
1794                    but.phase = phasenam  # set a pointer to current phase info
1795                    but.Bind(wx.EVT_BUTTON,SelectDisAglFlags)     # phase bond/angle ranges
1796                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1797                cbox.Add((-1,2))
1798            for i in sorted(self.powderDict.keys()):
1799                G2G.HorizontalLine(cbox,cpnl)
1800                hist = self.powderDict[i]
1801                histblk = self.Histograms[hist]
1802                title = 'Powder dataset '+hist[5:]
1803                cbox.Add(
1804                    CIFtemplateSelect(self.cifdefs,
1805                                      cpnl,'powder',histblk["Sample Parameters"],
1806                                      EditCIFDefaults,
1807                                      title,
1808                                      histblk["Sample Parameters"]['InstrName']),
1809                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1810            for i in sorted(self.xtalDict.keys()):
1811                G2G.HorizontalLine(cbox,cpnl)
1812                hist = self.xtalDict[i]
1813                histblk = self.Histograms[hist]
1814                title = 'Single Xtal dataset '+hist[5:]
1815                cbox.Add(
1816                    CIFtemplateSelect(self.cifdefs,
1817                                      cpnl,'single',histblk["Instrument Parameters"][0],
1818                                      EditCIFDefaults,
1819                                      title,
1820                                      histblk["Instrument Parameters"][0]['InstrName']),
1821                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1822            cpnl.SetSizer(cbox)
1823            cpnl.SetAutoLayout(1)
1824            cpnl.SetupScrolling()
1825            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1826            cpnl.Layout()
1827
1828            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1829            btnsizer = wx.StdDialogButtonSizer()
1830            btn = wx.Button(self.cifdefs, wx.ID_OK, "Create CIF")
1831            btn.SetDefault()
1832            btnsizer.AddButton(btn)
1833            btn = wx.Button(self.cifdefs, wx.ID_CANCEL)
1834            btnsizer.AddButton(btn)
1835            btnsizer.Realize()
1836            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1837            self.cifdefs.SetSizer(vbox)
1838            vbox.Fit(self.cifdefs)
1839            self.cifdefs.Layout()
1840
1841        def OnToggleButton(event):
1842            'Respond to press of ToggleButton in SelectDisAglFlags'
1843            but = event.GetEventObject()
1844            if but.GetValue():
1845                but.DisAglSel[but.key] = True
1846            else:
1847                try:
1848                    del but.DisAglSel[but.key]
1849                except KeyError:
1850                    pass
1851        def keepTrue(event):
1852            event.GetEventObject().SetValue(True)
1853        def keepFalse(event):
1854            event.GetEventObject().SetValue(False)
1855
1856        def SelectDisAglFlags(event):
1857            'Select Distance/Angle use flags for the selected phase'
1858            phasenam = event.GetEventObject().phase
1859            phasedict = self.Phases[phasenam]
1860            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(phasedict['General']['SGData'])
1861            generalData = phasedict['General']
1862            # create a dict for storing Pub flag for bonds/angles, if needed
1863            if phasedict['General'].get("DisAglHideFlag") is None:
1864                phasedict['General']["DisAglHideFlag"] = {}
1865            DisAngSel = phasedict['General']["DisAglHideFlag"]
1866
1867            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1868            cn = ct-1
1869            cfrac = cx+3
1870            DisAglData = {}
1871            # create a list of atoms, but skip atoms with zero occupancy
1872            xyz = []
1873            fpfx = str(phasedict['pId'])+'::Afrac:'
1874            for i,atom in enumerate(phasedict['Atoms']):
1875                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
1876                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
1877            if 'DisAglCtls' not in generalData:
1878                # should not be used, since DisAglDialog should be called
1879                # for all phases before getting here
1880                dlg = G2G.DisAglDialog(
1881                    self.cifdefs,
1882                    {},
1883                    generalData)
1884                if dlg.ShowModal() == wx.ID_OK:
1885                    generalData['DisAglCtls'] = dlg.GetData()
1886                else:
1887                    dlg.Destroy()
1888                    return
1889                dlg.Destroy()
1890            dlg = wx.Dialog(
1891                self.G2frame,
1892                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
1893            vbox = wx.BoxSizer(wx.VERTICAL)
1894            txt = wx.StaticText(dlg,wx.ID_ANY,'Searching distances for phase '+phasenam
1895                                +'\nPlease wait...')
1896            vbox.Add(txt,0,wx.ALL|wx.EXPAND)
1897            dlg.SetSizer(vbox)
1898            dlg.CenterOnParent()
1899            dlg.Show() # post "please wait"
1900            wx.BeginBusyCursor() # and change cursor
1901
1902            DisAglData['OrigAtoms'] = xyz
1903            DisAglData['TargAtoms'] = xyz
1904            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
1905                generalData['SGData'])
1906
1907#            xpandSGdata = generalData['SGData'].copy()
1908#            xpandSGdata.update({'SGOps':symOpList,
1909#                                'SGInv':False,
1910#                                'SGLatt':'P',
1911#                                'SGCen':np.array([[0, 0, 0]]),})
1912#            DisAglData['SGData'] = xpandSGdata
1913            DisAglData['SGData'] = generalData['SGData'].copy()
1914
1915            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
1916            if 'pId' in phasedict:
1917                DisAglData['pId'] = phasedict['pId']
1918                DisAglData['covData'] = self.OverallParms['Covariance']
1919            try:
1920                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
1921                    generalData['DisAglCtls'],
1922                    DisAglData)
1923            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
1924                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
1925            wx.EndBusyCursor()
1926            txt.SetLabel('Set publication flags for distances and angles in\nphase '+phasenam)
1927            vbox.Add((5,5))
1928            vbox.Add(wx.StaticText(dlg,wx.ID_ANY,
1929                                   'The default is to flag all distances and angles as to be'+
1930                                   '\npublished. Change this by pressing appropriate buttons.'),
1931                     0,wx.ALL|wx.EXPAND)
1932            hbox = wx.BoxSizer(wx.HORIZONTAL)
1933            vbox.Add(hbox)
1934            hbox.Add(wx.StaticText(dlg,wx.ID_ANY,'Button appearance: '))
1935            but = wx.ToggleButton(dlg,wx.ID_ANY,'Publish')
1936            but.Bind(wx.EVT_TOGGLEBUTTON,keepFalse)
1937            hbox.Add(but)
1938            but = wx.ToggleButton(dlg,wx.ID_ANY,"Don't publish")
1939            but.Bind(wx.EVT_TOGGLEBUTTON,keepTrue)
1940            hbox.Add(but)
1941            but.SetValue(True)
1942            G2G.HorizontalLine(vbox,dlg)
1943
1944            cpnl = wxscroll.ScrolledPanel(dlg,size=(400,300))
1945            cbox = wx.BoxSizer(wx.VERTICAL)
1946            for c in sorted(DistArray):
1947                karr = []
1948                UsedCols = {}
1949                cbox.Add(wx.StaticText(cpnl,wx.ID_ANY,
1950                                   'distances to/angles around atom '+AtomLabels[c]))
1951                #dbox = wx.GridBagSizer(hgap=5)
1952                dbox = wx.GridBagSizer()
1953                for i,D in enumerate(DistArray[c]):
1954                    karr.append(tuple(D[0:3]))
1955                    val = "{:.2f}".format(D[3])
1956                    sym = " [{:d} {:d} {:d}]".format(*D[1]) + " #{:d}".format(D[2])
1957                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
1958                             (i+1,0)
1959                             )
1960                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,sym),
1961                             (i+1,1)
1962                             )
1963                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1964                    but.key = (c,karr[-1])
1965                    but.DisAglSel = DisAngSel
1966                    if DisAngSel.get(but.key): but.SetValue(True)
1967                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1968                    dbox.Add(but,(i+1,2),border=1)
1969                for i,D in enumerate(AngArray[c]):
1970                    val = "{:.1f}".format(D[2][0])
1971                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1972                    but.key = (karr[D[0]],c,karr[D[1]])
1973                    but.DisAglSel = DisAngSel
1974                    if DisAngSel.get(but.key): but.SetValue(True)
1975                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1976                    dbox.Add(but,(D[0]+1,D[1]+3),border=1)
1977                    UsedCols[D[1]+3] = True
1978                for i,D in enumerate(DistArray[c][:-1]): # label columns that are used
1979                    if UsedCols.get(i+3):
1980                        dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
1981                                 (0,i+3),
1982                                 flag=wx.ALIGN_CENTER
1983                                 )
1984                dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,'distance'),
1985                                 (0,2),
1986                                 flag=wx.ALIGN_CENTER
1987                                 )
1988                cbox.Add(dbox)
1989                G2G.HorizontalLine(cbox,cpnl)
1990            cpnl.SetSizer(cbox)
1991            cpnl.SetAutoLayout(1)
1992            cpnl.SetupScrolling()
1993            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1994            cpnl.Layout()
1995
1996            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1997
1998            btnsizer = wx.StdDialogButtonSizer()
1999            btn = wx.Button(dlg, wx.ID_OK, "Done")
2000            btn.SetDefault()
2001            btnsizer.AddButton(btn)
2002            btnsizer.Realize()
2003            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2004            dlg.SetSizer(vbox)
2005            vbox.Fit(dlg)
2006            dlg.Layout()
2007
2008            dlg.CenterOnParent()
2009            dlg.ShowModal()
2010
2011#=================================================================================
2012#===== end of function definitions for _Exporter =================================
2013#=================================================================================
2014        # make sure required information is present
2015        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
2016        if not self.CIFname: # Get a name for the CIF. If not defined, use the GPX name (save, if that is needed).
2017            if not self.G2frame.GSASprojectfile:
2018                self.G2frame.OnFileSaveas(None)
2019            if not self.G2frame.GSASprojectfile: return
2020            self.CIFname = os.path.splitext(
2021                os.path.split(self.G2frame.GSASprojectfile)[1]
2022                )[0]
2023            self.CIFname = self.CIFname.replace(' ','')
2024        # replace non-ASCII characters in CIFname with dots
2025        s = ''
2026        for c in self.CIFname:
2027            if ord(c) < 128:
2028                s += c
2029            else:
2030                s += '.'
2031        self.CIFname = s
2032        # load saved CIF author name
2033        try:
2034            self.author = self.OverallParms['Controls'].get("Author",'').strip()
2035        except KeyError:
2036            pass
2037        #=================================================================
2038        # write quick CIFs
2039        #=================================================================
2040        if phaseOnly: #====Phase only CIF ================================
2041            print('Writing CIF output to file '+self.filename)
2042            #self.OpenFile()
2043            oneblock = True
2044            self.quickmode = True
2045            self.Write(' ')
2046            self.Write(70*'#')
2047            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2048            #phaseblk = self.Phases[phaseOnly] # pointer to current phase info
2049            # report the phase info
2050            WritePhaseInfo(phaseOnly)
2051            #self.CloseFile()
2052            return
2053        elif histOnly: #====Histogram only CIF ================================
2054            print('Writing CIF output to file '+self.filename)
2055            #self.OpenFile()
2056            hist = histOnly
2057            #histname = histOnly.replace(' ','')
2058            oneblock = True
2059            self.quickmode = True
2060            self.ifHKLF = False
2061            self.ifPWDR = True
2062            self.Write(' ')
2063            self.Write(70*'#')
2064            #phasenam = self.Phases.keys()[0]
2065            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2066            #print 'phasenam',phasenam
2067            #phaseblk = self.Phases[phasenam] # pointer to current phase info
2068            #instnam = instnam.replace(' ','')
2069            #WriteCIFitem(self.fp, '_pd_block_id',
2070            #             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2071            #             str(self.shortauthorname) + "|" + instnam + '|' + histname)
2072            #WriteAudit()
2073            #writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
2074            #WriteOverall()
2075            #writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
2076            # report the phase info
2077            #WritePhaseInfo(phasenam,hist)
2078            # preferred orientation
2079            #SH = FormatSH(phasenam)
2080            #MD = FormatHAPpo(phasenam)
2081            #if SH and MD:
2082            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2083            #elif SH or MD:
2084            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
2085            #else:
2086            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
2087            # report profile, since one-block: include both histogram and phase info
2088            #WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2089            #    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
2090            #        +'\n'+FormatPhaseProfile(phasenam))
2091            if hist.startswith("PWDR"):
2092                WritePowderData(hist)
2093            elif hist.startswith("HKLF"):
2094                WriteSingleXtalData(hist)
2095            #writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
2096            #self.CloseFile()
2097            return
2098        #elif IncludeOnlyHist is not None: # truncate histogram list to only selected (for sequential export)
2099        #    self.Histograms = {IncludeOnlyHist:self.Histograms[IncludeOnlyHist]}
2100
2101        #===============================================================================
2102        # the export process for a full CIF starts here
2103        #===============================================================================
2104        self.InitExport(event)
2105        # load all of the tree into a set of dicts
2106        self.loadTree()
2107        # create a dict with refined values and their uncertainties
2108        self.loadParmDict()
2109        if self.ExportSelect('ask'): return
2110        if not self.filename:
2111            print('No name supplied')
2112            return
2113        self.OpenFile()
2114        #if self.ExportSelect('default'): return
2115        # Someday: get restraint & constraint info
2116        #restraintDict = self.OverallParms.get('Restraints',{})
2117        #for i in  self.OverallParms['Constraints']:
2118        #    print i
2119        #    for j in self.OverallParms['Constraints'][i]:
2120        #        print j
2121
2122        # is there anything to export?
2123        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
2124           self.G2frame.ErrorDialog(
2125               'Empty project',
2126               'Project does not contain any data or phases. Are they interconnected?')
2127           return
2128        self.quickmode = False # full CIF
2129        phasenam = None # include all phases
2130        # Will this require a multiblock CIF?
2131        if len(self.Phases) > 1:
2132            oneblock = False
2133        elif len(self.powderDict) + len(self.xtalDict) > 1:
2134            oneblock = False
2135        else: # one phase, one dataset, Full CIF
2136            oneblock = True
2137
2138        # check there is an instrument name for every histogram
2139        self.ifPWDR = False
2140        self.ifHKLF = False
2141        invalid = 0
2142        key3 = 'InstrName'
2143        for hist in self.Histograms:
2144            if hist.startswith("PWDR"):
2145                self.ifPWDR = True
2146                key2 = "Sample Parameters"
2147                d = self.Histograms[hist][key2]
2148            elif hist.startswith("HKLF"):
2149                self.ifHKLF = True
2150                key2 = "Instrument Parameters"
2151                d = self.Histograms[hist][key2][0]
2152            instrname = d.get(key3)
2153            if instrname is None:
2154                d[key3] = ''
2155                invalid += 1
2156            elif instrname.strip() == '':
2157                invalid += 1
2158        if invalid:
2159            #msg = ""
2160            #if invalid > 3: msg = (
2161            #    "\n\nNote: it may be faster to set the name for\n"
2162            #    "one histogram for each instrument and use the\n"
2163            #    "File/Copy option to duplicate the name"
2164            #    )
2165            if not EditInstNames(): return
2166
2167        # check for a distance-angle range search range for each phase
2168        for phasenam in sorted(self.Phases.keys()):
2169            #i = self.Phases[phasenam]['pId']
2170            phasedict = self.Phases[phasenam] # pointer to current phase info
2171            if 'DisAglCtls' not in phasedict['General']:
2172                dlg = G2G.DisAglDialog(
2173                    self.G2frame,
2174                    {},
2175                    phasedict['General'])
2176                if dlg.ShowModal() == wx.ID_OK:
2177                    phasedict['General']['DisAglCtls'] = dlg.GetData()
2178                else:
2179                    dlg.Destroy()
2180                    return
2181                dlg.Destroy()
2182
2183        # check if temperature values & pressure are defaulted
2184        default = 0
2185        for hist in self.Histograms:
2186            if hist.startswith("PWDR"):
2187                key2 = "Sample Parameters"
2188                T = self.Histograms[hist][key2].get('Temperature')
2189                if not T:
2190                    default += 1
2191                elif T == 300:
2192                    default += 1
2193                P = self.Histograms[hist][key2].get('Pressure')
2194                if not P:
2195                    default += 1
2196                elif P == 1:
2197                    default += 1
2198        if default > 0:
2199            dlg = wx.MessageDialog(
2200                self.G2frame,
2201                'Temperature/Pressure values appear to be defaulted for some powder histograms (See Sample Parameters for each PWDR tree entry). Do you want to use those values?',
2202                'Check T and P values',
2203                wx.OK|wx.CANCEL)
2204            ret = dlg.ShowModal()
2205            dlg.Destroy()
2206            if ret != wx.ID_OK: return
2207        if oneblock:
2208            # select a dataset to use (there should only be one set in one block,
2209            # but take whatever comes 1st)
2210            for hist in self.Histograms:
2211                histblk = self.Histograms[hist]
2212                if hist.startswith("PWDR"):
2213                    instnam = histblk["Sample Parameters"]['InstrName']
2214                    break # ignore all but 1st data histogram
2215                elif hist.startswith("HKLF"):
2216                    instnam = histblk["Instrument Parameters"][0]['InstrName']
2217                    break # ignore all but 1st data histogram
2218        # give the user a window to edit CIF contents
2219        if not self.author:
2220            if not EditAuthor(): return
2221        self.ValidateAscii([('Author name',self.author),]) # check for ASCII strings where needed, warn on problems
2222        self.cifdefs = wx.Dialog(
2223            self.G2frame,
2224            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2225        EditCIFDefaults()
2226        self.cifdefs.CenterOnParent()
2227        if self.cifdefs.ShowModal() != wx.ID_OK:
2228            self.cifdefs.Destroy()
2229            return
2230        while self.ValidateAscii([('Author name',self.author),
2231                                  ]): # validate a few things as ASCII
2232            if self.cifdefs.ShowModal() != wx.ID_OK:
2233                self.cifdefs.Destroy()
2234                return
2235        self.cifdefs.Destroy()
2236        #======================================================================
2237        # Start writing the CIF - single block
2238        #======================================================================
2239        print('Writing CIF output to file '+self.filename+"...")
2240        #self.OpenFile()
2241        if self.currentExportType == 'single' or self.currentExportType == 'powder':
2242            #======Data only CIF (powder/xtal) ====================================
2243            hist = self.histnam[0]
2244            self.CIFname = hist[5:40].replace(' ','')
2245            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2246            if hist.startswith("PWDR"):
2247                WritePowderData(hist)
2248            elif hist.startswith("HKLF"):
2249                WriteSingleXtalData(hist)
2250            else:
2251                print ("should not happen")
2252        elif oneblock:
2253            #====Single block, data & phase CIF ===================================
2254            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2255            if phasenam is None: # if not already selected, select the first phase (should be one)
2256                phasenam = self.Phases.keys()[0]
2257            #print 'phasenam',phasenam
2258            #phaseblk = self.Phases[phasenam] # pointer to current phase info
2259            instnam = instnam.replace(' ','')
2260            WriteCIFitem(self.fp, '_pd_block_id',
2261                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2262                         str(self.shortauthorname) + "|" + instnam)
2263            WriteAudit()
2264            writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
2265            WriteOverall()
2266            writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
2267            # report the phase info
2268            WritePhaseInfo(phasenam)
2269            if hist.startswith("PWDR"):
2270                # preferred orientation
2271                SH = FormatSH(phasenam)
2272                MD = FormatHAPpo(phasenam)
2273                if SH and MD:
2274                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2275                elif SH or MD:
2276                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
2277                else:
2278                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
2279                    # report profile, since one-block: include both histogram and phase info
2280                WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2281                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
2282                    +'\n'+FormatPhaseProfile(phasenam))
2283                histblk = self.Histograms[hist]["Sample Parameters"]
2284                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
2285                WritePowderData(hist)
2286            elif hist.startswith("HKLF"):
2287                histprm = self.Histograms[hist]["Instrument Parameters"][0]
2288                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
2289                WriteSingleXtalData(hist)
2290        else:
2291            #=== multiblock: multiple phases and/or histograms ====================
2292            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
2293            dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
2294#                Size = dlg.GetSize()
2295#                Size = (int(Size[0]*3),Size[1]) # increase size along x
2296#                dlg.SetSize(Size)
2297            dlg.CenterOnParent()
2298
2299            # publication info
2300            step = 1
2301            dlg.Update(step,"Exporting overall section")
2302            WriteCIFitem(self.fp, '\ndata_'+self.CIFname+'_publ')
2303            WriteAudit()
2304            WriteCIFitem(self.fp, '_pd_block_id',
2305                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2306                         str(self.shortauthorname) + "|Overall")
2307            writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
2308            # ``template_publ.cif`` or a modified version
2309            # overall info
2310            WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall')
2311            WriteOverall()
2312            #============================================================
2313            WriteCIFitem(self.fp, '# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
2314            datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
2315            # loop over phase blocks
2316            if len(self.Phases) > 1:
2317                loopprefix = ''
2318                WriteCIFitem(self.fp, 'loop_   _pd_phase_block_id')
2319            else:
2320                loopprefix = '_pd_phase_block_id'
2321
2322            for phasenam in sorted(self.Phases.keys()):
2323                i = self.Phases[phasenam]['pId']
2324                datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2325                             'phase_'+ str(i) + '|' + str(self.shortauthorname))
2326                WriteCIFitem(self.fp, loopprefix,datablockidDict[phasenam])
2327            # loop over data blocks
2328            if len(self.powderDict) + len(self.xtalDict) > 1:
2329                loopprefix = ''
2330                WriteCIFitem(self.fp, 'loop_   _pd_block_diffractogram_id')
2331            else:
2332                loopprefix = '_pd_block_diffractogram_id'
2333            for i in sorted(self.powderDict.keys()):
2334                hist = self.powderDict[i]
2335                histblk = self.Histograms[hist]
2336                instnam = histblk["Sample Parameters"]['InstrName']
2337                instnam = instnam.replace(' ','')
2338                j = histblk['hId']
2339                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2340                                         str(self.shortauthorname) + "|" +
2341                                         instnam + "_hist_"+str(j))
2342                WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
2343            for i in sorted(self.xtalDict.keys()):
2344                hist = self.xtalDict[i]
2345                histblk = self.Histograms[hist]
2346                instnam = histblk["Instrument Parameters"][0]['InstrName']
2347                instnam = instnam.replace(' ','')
2348                i = histblk['hId']
2349                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2350                                         str(self.shortauthorname) + "|" +
2351                                         instnam + "_hist_"+str(i))
2352                WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
2353            #============================================================
2354            # loop over phases, exporting them
2355            phasebyhistDict = {} # create a cross-reference to phases by histogram
2356            for j,phasenam in enumerate(sorted(self.Phases.keys())):
2357                step += 1
2358                dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
2359                i = self.Phases[phasenam]['pId']
2360                WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_phase_"+str(i))
2361                WriteCIFitem(self.fp, '# Information for phase '+str(i))
2362                WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[phasenam])
2363                # report the phase
2364                writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
2365                WritePhaseInfo(phasenam)
2366                # preferred orientation
2367                if self.ifPWDR:
2368                    SH = FormatSH(phasenam)
2369                    MD = FormatHAPpo(phasenam)
2370                    if SH and MD:
2371                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2372                    elif SH or MD:
2373                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
2374                    else:
2375                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
2376                # report sample profile terms
2377                PP = FormatPhaseProfile(phasenam)
2378                if PP:
2379                    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP)
2380
2381            #============================================================
2382            # loop over histograms, exporting them
2383            for i in sorted(self.powderDict.keys()):
2384                hist = self.powderDict[i]
2385                histblk = self.Histograms[hist]
2386                if hist.startswith("PWDR"):
2387                    step += 1
2388                    dlg.Update(step,"Exporting "+hist.strip())
2389                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_pwd_"+str(i))
2390                    #instnam = histblk["Sample Parameters"]['InstrName']
2391                    # report instrumental profile terms
2392                    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2393                        FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
2394                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
2395                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
2396                    histprm = self.Histograms[hist]["Sample Parameters"]
2397                    writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
2398                    WritePowderData(hist)
2399            for i in sorted(self.xtalDict.keys()):
2400                hist = self.xtalDict[i]
2401                histblk = self.Histograms[hist]
2402                if hist.startswith("HKLF"):
2403                    step += 1
2404                    dlg.Update(step,"Exporting "+hist.strip())
2405                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_sx_"+str(i))
2406                    #instnam = histblk["Instrument Parameters"][0]['InstrName']
2407                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
2408                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
2409                    histprm = self.Histograms[hist]["Instrument Parameters"][0]
2410                    writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
2411                    WriteSingleXtalData(hist)
2412
2413            dlg.Destroy()
2414
2415        WriteCIFitem(self.fp, '#--' + 15*'eof--' + '#')
2416        #self.CloseFile()
2417        print("...export completed")
2418        print('file '+self.fullpath)
2419        # end of CIF export
2420
2421class ExportProjectCIF(ExportCIF):
2422    '''Used to create a CIF of an entire project
2423
2424    :param wx.Frame G2frame: reference to main GSAS-II frame
2425    '''
2426    def __init__(self,G2frame):
2427        ExportCIF.__init__(self,
2428            G2frame=G2frame,
2429            formatName = 'Full CIF',
2430            extension='.cif',
2431            longFormatName = 'Export project as CIF'
2432            )
2433        self.exporttype = ['project']
2434
2435    def Exporter(self,event=None):
2436        self._Exporter(event=event)
2437        self.CloseFile()
2438
2439    # def Writer(self,hist,mode='w'):
2440    #     '''Used for full project CIF export of a sequential fit.
2441    #     TODO: Needs extensive work
2442    #     '''
2443    #     # set the project file name
2444    #     self.CIFname = os.path.splitext(
2445    #         os.path.split(self.G2frame.GSASprojectfile)[1]
2446    #         )[0]+'_'+hist
2447    #     self.CIFname = self.CIFname.replace(' ','')
2448    #     self.OpenFile(mode=mode)
2449    #     self._Exporter(IncludeOnlyHist=hist)
2450    #     if mode == 'w':
2451    #         print('CIF written to file '+self.fullpath)
2452    #     self.CloseFile()
2453
2454class ExportPhaseCIF(ExportCIF):
2455    '''Used to create a simple CIF with one phase. Uses exact same code as
2456    :class:`ExportCIF` except that `phaseOnly` is set for the Exporter
2457    Shows up in menu as Quick CIF.
2458
2459    :param wx.Frame G2frame: reference to main GSAS-II frame
2460    '''
2461    def __init__(self,G2frame):
2462        ExportCIF.__init__(self,
2463            G2frame=G2frame,
2464            formatName = 'Quick CIF',
2465            extension='.cif',
2466            longFormatName = 'Export one phase in CIF'
2467            )
2468        self.exporttype = ['phase']
2469        # CIF-specific items
2470        self.author = ''
2471
2472    def Exporter(self,event=None):
2473        # get a phase and file name
2474        # the export process starts here
2475        self.InitExport(event)
2476        # load all of the tree into a set of dicts
2477        self.loadTree()
2478        # create a dict with refined values and their uncertainties
2479        self.loadParmDict()
2480        self.multiple = False
2481        self.currentExportType = 'phase'
2482        if self.ExportSelect('ask'): return
2483        self.OpenFile()
2484        self._Exporter(event=event,phaseOnly=self.phasenam[0])
2485        self.CloseFile()
2486
2487    def Writer(self,hist,phasenam,mode='w'):
2488        # set the project file name
2489        self.CIFname = os.path.splitext(
2490            os.path.split(self.G2frame.GSASprojectfile)[1]
2491            )[0]+'_'+phasenam+'_'+hist
2492        self.CIFname = self.CIFname.replace(' ','')
2493        self.OpenFile(mode=mode)
2494        self._Exporter(phaseOnly=phasenam)
2495        self.CloseFile()
2496
2497class ExportPwdrCIF(ExportCIF):
2498    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2499    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2500    Shows up in menu as Quick CIF.
2501
2502    :param wx.Frame G2frame: reference to main GSAS-II frame
2503    '''
2504    def __init__(self,G2frame):
2505        ExportCIF.__init__(self,
2506            G2frame=G2frame,
2507            formatName = 'Data-only CIF',
2508            extension='.cif',
2509            longFormatName = 'Export data as CIF'
2510            )
2511        if G2frame is None: raise AttributeError('CIF export requires data tree') # prevent use from Scriptable
2512        self.exporttype = ['powder']
2513        # CIF-specific items
2514        self.author = ''
2515
2516    def Exporter(self,event=None):
2517        self.InitExport(event)
2518        # load all of the tree into a set of dicts
2519        self.currentExportType = None
2520        self.loadTree()
2521        self.currentExportType = 'powder'
2522        # create a dict with refined values and their uncertainties
2523        self.loadParmDict()
2524        self.multiple = False
2525        if self.ExportSelect( # set export parameters
2526            AskFile='ask' # get a file name/directory to save in
2527            ): return
2528        self.OpenFile()
2529        self._Exporter(event=event,histOnly=self.histnam[0])
2530
2531    def Writer(self,hist,mode='w'):
2532        '''Used for histogram CIF export of a sequential fit.
2533        '''
2534        # set the project file name
2535        self.CIFname = os.path.splitext(
2536            os.path.split(self.G2frame.GSASprojectfile)[1]
2537            )[0]+'_'+hist
2538        self.CIFname = self.CIFname.replace(' ','')
2539        self.OpenFile(mode=mode)
2540        self._Exporter(histOnly=hist)
2541        if mode == 'w':
2542            print('CIF written to file '+self.fullpath)
2543        self.CloseFile()
2544
2545class ExportHKLCIF(ExportCIF):
2546    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2547    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2548    Shows up in menu as Quick CIF.
2549
2550    :param wx.Frame G2frame: reference to main GSAS-II frame
2551    '''
2552    def __init__(self,G2frame):
2553        ExportCIF.__init__(self,
2554            G2frame=G2frame,
2555            formatName = 'Data-only CIF',
2556            extension='.cif',
2557            longFormatName = 'Export data as CIF'
2558            )
2559        self.exporttype = ['single']
2560        # CIF-specific items
2561        self.author = ''
2562
2563    def Exporter(self,event=None):
2564        self.InitExport(event)
2565        # load all of the tree into a set of dicts
2566        self.currentExportType = None
2567        self.loadTree()
2568        self.currentExportType = 'single'
2569        # create a dict with refined values and their uncertainties
2570        self.loadParmDict()
2571        self.multiple = False
2572        if self.ExportSelect( # set export parameters
2573            AskFile='ask' # get a file name/directory to save in
2574            ): return
2575        self.OpenFile()
2576        self._Exporter(event=event,histOnly=self.histnam[0])
2577
2578#===============================================================================
2579# misc CIF utilities
2580#===============================================================================
2581def PickleCIFdict(fil):
2582    '''Loads a CIF dictionary, cherry picks out the items needed
2583    by local code and sticks them into a python dict and writes
2584    that dict out as a pickle file for later reuse.
2585    If the write fails a warning message is printed,
2586    but no exception occurs.
2587
2588    :param str fil: file name of CIF dictionary, will usually end
2589      in .dic
2590    :returns: the dict with the definitions
2591    '''
2592    import CifFile as cif # PyCifRW from James Hester
2593    cifdic = {}
2594    try:
2595        fp = open(fil,'r')             # patch: open file to avoid windows bug
2596        dictobj = cif.CifDic(fp)
2597        fp.close()
2598    except IOError:
2599        dictobj = cif.CifDic(fil)
2600    if DEBUG: print('loaded '+fil)
2601    for item in dictobj.keys():
2602        cifdic[item] = {}
2603        for j in (
2604            '_definition','_type',
2605            '_enumeration',
2606            '_enumeration_detail',
2607            '_enumeration_range'):
2608            if dictobj[item].get(j):
2609                cifdic[item][j] = dictobj[item][j]
2610    try:
2611        fil = os.path.splitext(fil)[0]+'.cpickle'
2612        fp = open(fil,'w')
2613        pickle.dump(cifdic,fp)
2614        fp.close()
2615        if DEBUG: print('wrote '+fil)
2616    except:
2617        print ('Unable to write '+fil)
2618    return cifdic
2619
2620def LoadCIFdic():
2621    '''Create a composite core+powder CIF lookup dict containing
2622    information about all items in the CIF dictionaries, loading
2623    pickled files if possible. The routine looks for files
2624    named cif_core.cpickle and cif_pd.cpickle in every
2625    directory in the path and if they are not found, files
2626    cif_core.dic and/or cif_pd.dic are read.
2627
2628    :returns: the dict with the definitions
2629    '''
2630    cifdic = {}
2631    for ftyp in "cif_core","cif_pd":
2632        for loc in sys.path:
2633            fil = os.path.join(loc,ftyp+".cpickle")
2634            if not os.path.exists(fil): continue
2635            fp = open(fil,'r')
2636            try:
2637                cifdic.update(pickle.load(fp))
2638                if DEBUG: print('reloaded '+fil)
2639                break
2640            finally:
2641                fp.close()
2642        else:
2643            for loc in sys.path:
2644                fil = os.path.join(loc,ftyp+".dic")
2645                if not os.path.exists(fil): continue
2646                #try:
2647                if True:
2648                    cifdic.update(PickleCIFdict(fil))
2649                    break
2650                #except:
2651                #    pass
2652            else:
2653                print('Could not load '+ftyp+' dictionary')
2654    return cifdic
2655
2656class CIFdefHelp(wx.Button):
2657    '''Create a help button that displays help information on
2658    the current data item
2659
2660    :param parent: the panel which will be the parent of the button
2661    :param str msg: the help text to be displayed
2662    :param wx.Dialog helpwin: Frame for CIF editing dialog
2663    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
2664    '''
2665    def __init__(self,parent,msg,helpwin,helptxt):
2666        wx.Button.__init__(self,parent,wx.ID_HELP)
2667        self.Bind(wx.EVT_BUTTON,self._onPress)
2668        self.msg=msg
2669        self.parent = parent
2670        #self.helpwin = self.parent.helpwin
2671        self.helpwin = helpwin
2672        self.helptxt = helptxt
2673    def _onPress(self,event):
2674        'Respond to a button press by displaying the requested text'
2675        try:
2676            #helptxt = self.helptxt
2677            ow,oh = self.helptxt.GetSize()
2678            self.helptxt.SetLabel(self.msg)
2679            w,h = self.helptxt.GetSize()
2680            if h > oh:
2681                self.helpwin.GetSizer().Fit(self.helpwin)
2682        except: # error posting help, ignore
2683            return
2684
2685def CIF2dict(cf):
2686    '''copy the contents of a CIF out from a PyCifRW block object
2687    into a dict
2688
2689    :returns: cifblk, loopstructure where cifblk is a dict with
2690      CIF items and loopstructure is a list of lists that defines
2691      which items are in which loops.
2692    '''
2693    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
2694    loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
2695    dblk = {}
2696    for item in cf[blk].keys(): # make a copy of all the items in the block
2697        dblk[item] = cf[blk][item]
2698    return dblk,loopstructure
2699
2700def dict2CIF(dblk,loopstructure,blockname='Template'):
2701    '''Create a PyCifRW CIF object containing a single CIF
2702    block object from a dict and loop structure list.
2703
2704    :param dblk: a dict containing values for each CIF item
2705    :param list loopstructure: a list of lists containing the contents of
2706      each loop, as an example::
2707
2708         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2709
2710      this describes a CIF with this type of structure::
2711
2712        loop_ _a _b <a1> <b1> <a2> ...
2713        loop_ _c <c1> <c2>...
2714        loop _d_1 _d_2 _d_3 ...
2715
2716      Note that the values for each looped CIF item, such as _a,
2717      are contained in a list, for example as cifblk["_a"]
2718
2719    :param str blockname: an optional name for the CIF block.
2720      Defaults to 'Template'
2721
2722    :returns: the newly created PyCifRW CIF object
2723    '''
2724
2725    import CifFile as cif # PyCifRW from James Hester
2726    # compile a 'list' of items in loops
2727    loopnames = set()
2728    for i in loopstructure:
2729        loopnames |= set(i)
2730    # create a new block
2731    newblk = cif.CifBlock()
2732    # add the looped items
2733    for keys in loopstructure:
2734        vals = []
2735        for key in keys:
2736            vals.append(dblk[key])
2737        newblk.AddCifItem(([keys],[vals]))
2738    # add the non-looped items
2739    for item in dblk:
2740        if item in loopnames: continue
2741        newblk[item] = dblk[item]
2742    # create a CIF and add the block
2743    newcf = cif.CifFile()
2744    newcf[blockname] = newblk
2745    return newcf
2746
2747
2748class EditCIFtemplate(wx.Dialog):
2749    '''Create a dialog for editing a CIF template. The edited information is
2750    placed in cifblk. If the CIF is saved as a file, the name of that file
2751    is saved as ``self.newfile``.
2752
2753    :param wx.Frame parent: parent frame or None
2754    :param cifblk: dict or PyCifRW block containing values for each CIF item
2755    :param list loopstructure: a list of lists containing the contents of
2756      each loop, as an example::
2757
2758         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2759
2760      this describes a CIF with this type of structure::
2761
2762        loop_ _a _b <a1> <b1> <a2> ...
2763        loop_ _c <c1> <c2>...
2764        loop _d_1 _d_2 _d_3 ...
2765
2766      Note that the values for each looped CIF item, such as _a,
2767      are contained in a list, for example as cifblk["_a"]
2768
2769    :param str defaultname: specifies the default file name to be used for
2770      saving the CIF.
2771    '''
2772    def __init__(self,parent,cifblk,loopstructure,defaultname):
2773        OKbuttons = []
2774        self.cifblk = cifblk
2775        self.loopstructure = loopstructure
2776        self.newfile = None
2777        self.defaultname = defaultname
2778        global CIFdic  # once this is loaded, keep it around
2779        if CIFdic is None:
2780            CIFdic = LoadCIFdic()
2781        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2782
2783        # define widgets that will be needed during panel creation
2784        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
2785        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
2786        OKbuttons.append(savebtn)
2787        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
2788        OKbtn = wx.Button(self, wx.ID_OK, "Use")
2789        OKbtn.SetDefault()
2790        OKbuttons.append(OKbtn)
2791
2792        self.SetTitle('Edit items in CIF template')
2793        vbox = wx.BoxSizer(wx.VERTICAL)
2794        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
2795        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2796        G2G.HorizontalLine(vbox,self)
2797        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
2798        G2G.HorizontalLine(vbox,self)
2799        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2800        btn = wx.Button(self, wx.ID_CANCEL)
2801        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
2802        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
2803        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
2804        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2805        self.SetSizer(vbox)
2806        vbox.Fit(self)
2807    def Post(self):
2808        '''Display the dialog
2809
2810        :returns: True unless Cancel has been pressed.
2811        '''
2812        return (self.ShowModal() == wx.ID_OK)
2813    def _onSave(self,event):
2814        'Save CIF entries in a template file'
2815        pth = G2G.GetExportPath(self.G2frame)
2816        dlg = wx.FileDialog(
2817            self, message="Save as CIF template",
2818            defaultDir=pth,
2819            defaultFile=self.defaultname,
2820            wildcard="CIF (*.cif)|*.cif",
2821            style=wx.SAVE)
2822        val = (dlg.ShowModal() == wx.ID_OK)
2823        fil = dlg.GetPath()
2824        dlg.Destroy()
2825        if val: # ignore a Cancel button
2826            fil = os.path.splitext(fil)[0]+'.cif' # force extension
2827            fp = open(fil,'w')
2828            newcf = dict2CIF(self.cifblk,self.loopstructure)
2829            fp.write(newcf.WriteOut())
2830            fp.close()
2831            self.newfile = fil
2832            self.EndModal(wx.ID_OK)
2833
2834class EditCIFpanel(wxscroll.ScrolledPanel):
2835    '''Creates a scrolled panel for editing CIF template items
2836
2837    :param wx.Frame parent: parent frame where panel will be placed
2838    :param cifblk: dict or PyCifRW block containing values for each CIF item
2839    :param list loopstructure: a list of lists containing the contents of
2840      each loop, as an example::
2841
2842         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2843
2844      this describes a CIF with this type of structure::
2845
2846        loop_ _a _b <a1> <b1> <a2> ...
2847        loop_ _c <c1> <c2>...
2848        loop _d_1 _d_2 _d_3 ...
2849
2850      Note that the values for each looped CIF item, such as _a,
2851      are contained in a list, for example as cifblk["_a"]
2852
2853    :param dict cifdic: optional CIF dictionary definitions
2854    :param list OKbuttons: A list of wx.Button objects that should
2855      be disabled when information in the CIF is invalid
2856    :param (other): optional keyword parameters for wx.ScrolledPanel
2857    '''
2858    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
2859        self.parent = parent
2860        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
2861        self.vbox = None
2862        self.AddDict = None
2863        self.cifdic = cifdic
2864        self.cifblk = cifblk
2865        self.loops = loopstructure
2866        self.parent = parent
2867        self.LayoutCalled = False
2868        self.parentOKbuttons = OKbuttons
2869        self.ValidatedControlsList = []
2870        self._fill()
2871    def _fill(self):
2872        'Fill the scrolled panel with widgets for each CIF item'
2873        wx.BeginBusyCursor()
2874        self.AddDict = {}
2875        self.ValidatedControlsList = []
2876        # delete any only contents
2877        if self.vbox:
2878            if 'phoenix' in wx.version():
2879                self.vbox.Clear(True)
2880            else:
2881                self.vbox.DeleteWindows()
2882            self.vbox = None
2883            self.Update()
2884        vbox = wx.BoxSizer(wx.VERTICAL)
2885        self.vbox = vbox
2886        # compile a 'list' of items in loops
2887        loopnames = set()
2888        for i in self.loops:
2889            loopnames |= set(i)
2890        # post the looped CIF items
2891        for lnum,lp in enumerate(self.loops):
2892            hbox = wx.BoxSizer(wx.HORIZONTAL)
2893            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
2894            vbox.Add(hbox)
2895            but = wx.Button(self,wx.ID_ANY,"Add row")
2896            self.AddDict[but]=lnum
2897
2898            hbox.Add(but)
2899            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
2900            fbox = wx.GridBagSizer(0, 0)
2901            vbox.Add(fbox)
2902            rows = 0
2903            for i,item in enumerate(lp):
2904                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
2905                fbox.Add(txt,(0,i+1))
2906                # if self.cifdic.get(item):
2907                #     df = self.cifdic[item].get('_definition')
2908                #     if df:
2909                #         txt.SetToolTipString(G2IO.trim(df))
2910                #         but = CIFdefHelp(self,
2911                #                          "Definition for "+item+":\n\n"+G2IO.trim(df),
2912                #                          self.parent,
2913                #                          self.parent.helptxt)
2914                #         fbox.Add(but,(1,i+1),flag=wx.ALIGN_CENTER)
2915                for j,val in enumerate(self.cifblk[item]):
2916                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
2917                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
2918                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
2919                if self.cifdic.get(item):
2920                    df = self.cifdic[item].get('_definition')
2921                    if df:
2922                        txt.SetToolTipString(G2IO.trim(df))
2923                        but = CIFdefHelp(self,
2924                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2925                                         self.parent,
2926                                         self.parent.helptxt)
2927                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
2928                rows = max(rows,len(self.cifblk[item]))
2929            for i in range(rows):
2930                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
2931                fbox.Add(txt,(i+2,0))
2932            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
2933            vbox.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2934
2935        # post the non-looped CIF items
2936        for item in sorted(self.cifblk.keys()):
2937            if item not in loopnames:
2938                hbox = wx.BoxSizer(wx.HORIZONTAL)
2939                vbox.Add(hbox)
2940                txt = wx.StaticText(self,wx.ID_ANY,item)
2941                hbox.Add(txt)
2942                ent = self.CIFEntryWidget(self.cifblk,item,item)
2943                hbox.Add(ent)
2944                if self.cifdic.get(item):
2945                    df = self.cifdic[item].get('_definition')
2946                    if df:
2947                        txt.SetToolTipString(G2IO.trim(df))
2948                        but = CIFdefHelp(self,
2949                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2950                                         self.parent,
2951                                         self.parent.helptxt)
2952                        hbox.Add(but,0,wx.ALL,2)
2953        self.SetSizer(vbox)
2954        #vbox.Fit(self.parent)
2955        self.SetAutoLayout(1)
2956        self.SetupScrolling()
2957        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
2958        self.Layout()
2959        wx.EndBusyCursor()
2960    def OnLayoutNeeded(self,event):
2961        '''Called when an update of the panel layout is needed. Calls
2962        self.DoLayout after the current operations are complete using
2963        CallAfter. This is called only once, according to flag
2964        self.LayoutCalled, which is cleared in self.DoLayout.
2965        '''
2966        if self.LayoutCalled: return # call already queued
2967        wx.CallAfter(self.DoLayout) # queue a call
2968        self.LayoutCalled = True
2969    def DoLayout(self):
2970        '''Update the Layout and scroll bars for the Panel. Clears
2971        self.LayoutCalled so that next change to panel can
2972        request a new update
2973        '''
2974        wx.BeginBusyCursor()
2975        self.Layout()
2976        self.SetupScrolling()
2977        wx.EndBusyCursor()
2978        self.LayoutCalled = False
2979    def OnAddRow(self,event):
2980        'add a row to a loop'
2981        lnum = self.AddDict.get(event.GetEventObject())
2982        if lnum is None: return
2983        for item in self.loops[lnum]:
2984            self.cifblk[item].append('?')
2985        self._fill()
2986
2987    def ControlOKButton(self,setvalue):
2988        '''Enable or Disable the OK button(s) for the dialog. Note that this is
2989        passed into the ValidatedTxtCtrl for use by validators.
2990
2991        :param bool setvalue: if True, all entries in the dialog are
2992          checked for validity. The first invalid control triggers
2993          disabling of buttons.
2994          If False then the OK button(s) are disabled with no checking
2995          of the invalid flag for each control.
2996        '''
2997        if setvalue: # turn button on, do only if all controls show as valid
2998            for ctrl in self.ValidatedControlsList:
2999                if ctrl.invalid:
3000                    for btn in self.parentOKbuttons:
3001                        btn.Disable()
3002                    return
3003            else:
3004                for btn in self.parentOKbuttons:
3005                    btn.Enable()
3006        else:
3007            for btn in self.parentOKbuttons:
3008                btn.Disable()
3009
3010    def CIFEntryWidget(self,dct,item,dataname):
3011        '''Create an entry widget for a CIF item. Use a validated entry for numb values
3012        where int is required when limits are integers and floats otherwise.
3013        At present this does not allow entry of the special CIF values of "." and "?" for
3014        numerical values and highlights them as invalid.
3015        Use a selection widget when there are specific enumerated values for a string.
3016        '''
3017        if self.cifdic.get(dataname):
3018            if self.cifdic[dataname].get('_enumeration'):
3019                values = ['?']+self.cifdic[dataname]['_enumeration']
3020                choices = ['undefined']
3021                for i in self.cifdic[dataname].get('_enumeration_detail',values):
3022                    choices.append(G2IO.trim(i))
3023                ent = G2G.EnumSelector(self, dct, item, choices, values, size=(200, -1))
3024                return ent
3025            if self.cifdic[dataname].get('_type') == 'numb':
3026                mn = None
3027                mx = None
3028                hint = int
3029                if self.cifdic[dataname].get('_enumeration_range'):
3030                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
3031                    if '.' in rng[0] or '.' in rng[1]: hint = float
3032                    if rng[0]: mn = hint(rng[0])
3033                    if rng[1]: mx = hint(rng[1])
3034                    ent = G2G.ValidatedTxtCtrl(
3035                        self,dct,item,typeHint=hint,min=mn,max=mx,
3036                        CIFinput=True,ASCIIonly=True,
3037                        OKcontrol=self.ControlOKButton)
3038                    self.ValidatedControlsList.append(ent)
3039                    return ent
3040        rw1 = rw.ResizeWidget(self)
3041        ent = G2G.ValidatedTxtCtrl(
3042            rw1,dct,item,size=(100, 20),
3043            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
3044            CIFinput=True,ASCIIonly=True,
3045            OKcontrol=self.ControlOKButton)
3046        self.ValidatedControlsList.append(ent)
3047        return rw1
3048
3049class CIFtemplateSelect(wx.BoxSizer):
3050    '''Create a set of buttons to show, select and edit a CIF template
3051
3052    :param frame: wx.Frame object of parent
3053    :param panel: wx.Panel object where widgets should be placed
3054    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
3055      the type of template
3056    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
3057      "CIF_template" will be used to store either a list or a string.
3058      If a list, it will contain a dict and a list defining loops. If
3059      an str, it will contain a file name.
3060    :param function repaint: reference to a routine to be called to repaint
3061      the frame after a change has been made
3062    :param str title: A line of text to show at the top of the window
3063    :param str defaultname: specifies the default file name to be used for
3064      saving the CIF.
3065    '''
3066    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
3067        wx.BoxSizer.__init__(self,wx.VERTICAL)
3068        self.cifdefs = frame
3069        self.dict = G2dict
3070        self.repaint = repaint
3071        templateDefName = 'template_'+tmplate+'.cif'
3072        self.CIF = G2dict.get("CIF_template")
3073        if defaultname:
3074            self.defaultname = G2obj.StripUnicode(defaultname)
3075            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
3076            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
3077        else:
3078            self.defaultname = ''
3079
3080        txt = wx.StaticText(panel,wx.ID_ANY,title)
3081        self.Add(txt,0,wx.ALIGN_CENTER)
3082        # change font on title
3083        txtfnt = txt.GetFont()
3084        txtfnt.SetWeight(wx.BOLD)
3085        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
3086        txt.SetFont(txtfnt)
3087        self.Add((-1,3))
3088
3089        if not self.CIF: # empty or None
3090            for pth in [os.getcwd()]+sys.path:
3091                fil = os.path.join(pth,self.defaultname)
3092                if os.path.exists(fil) and self.defaultname:
3093                    self.CIF = fil
3094                    CIFtxt = "Template: "+self.defaultname
3095                    break
3096            else:
3097                for pth in sys.path:
3098                    fil = os.path.join(pth,templateDefName)
3099                    if os.path.exists(fil):
3100                        self.CIF = fil
3101                        CIFtxt = "Template: "+templateDefName
3102                        break
3103                else:
3104                    print("Default CIF template "+self.defaultname+' not found in path!')
3105                    self.CIF = None
3106                    CIFtxt = "none! (No template found)"
3107        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
3108            if not os.path.exists(self.CIF):
3109                print("Error: template file has disappeared: "+self.CIF)
3110                self.CIF = None
3111                CIFtxt = "none! (file not found)"
3112            else:
3113                if len(self.CIF) < 50:
3114                    CIFtxt = "File: "+self.CIF
3115                else:
3116                    CIFtxt = "File: ..."+self.CIF[-50:]
3117        else:
3118            CIFtxt = "Template is customized"
3119        # show template source
3120        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
3121        # show str, button to select file; button to edit (if CIF defined)
3122        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
3123        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
3124        hbox =  wx.BoxSizer(wx.HORIZONTAL)
3125        hbox.Add(but,0,0,2)
3126        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
3127        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
3128        if self.CIF is None: but.Disable() # nothing to edit!
3129        hbox.Add(but,0,0,2)
3130        self.Add(hbox)
3131    def _onGetTemplateFile(self,event):
3132        'select a template file'
3133        pth = G2G.GetImportPath(self.G2frame)
3134        if not pth: pth = '.'
3135        dlg = wx.FileDialog(
3136            self.cifdefs, message="Read CIF template file",
3137            defaultDir=pth,
3138            defaultFile=self.defaultname,
3139            wildcard="CIF (*.cif)|*.cif",
3140            style=wx.OPEN)
3141        ret = dlg.ShowModal()
3142        fil = dlg.GetPath()
3143        dlg.Destroy()
3144        if ret == wx.ID_OK:
3145            cf = G2IO.ReadCIF(fil)
3146            if len(cf.keys()) == 0:
3147                raise Exception("No CIF data_ blocks found")
3148            if len(cf.keys()) != 1:
3149                raise Exception('Error, CIF Template has more than one block: '+fil)
3150            self.dict["CIF_template"] = fil
3151            self.repaint() #EditCIFDefaults()
3152
3153    def _onEditTemplateContents(self,event):
3154        'Called to edit the contents of a CIF template'
3155        if type(self.CIF) is list or  type(self.CIF) is tuple:
3156            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
3157        else:
3158            cf = G2IO.ReadCIF(self.CIF)
3159            dblk,loopstructure = CIF2dict(cf)
3160        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
3161        val = dlg.Post()
3162        if val:
3163            if dlg.newfile: # results saved in file
3164                self.dict["CIF_template"] = dlg.newfile
3165            else:
3166                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
3167            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
3168        else:
3169            dlg.Destroy()
3170
3171#===============================================================================
3172# end of misc CIF utilities
3173#===============================================================================
Note: See TracBrowser for help on using the repository browser.