source: trunk/exports/G2export_CIF.py @ 2529

Last change on this file since 2529 was 2529, checked in by toby, 5 years ago

fix I100 error in multiphase CIFs

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