source: trunk/exports/G2export_CIF.py @ 3011

Last change on this file since 3011 was 3011, checked in by odonnell, 4 years ago

CIF export. changelist:

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