source: trunk/exports/G2export_CIF.py @ 3019

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

fixed error in refactoring

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