source: trunk/exports/G2export_CIF.py @ 1836

Last change on this file since 1836 was 1831, checked in by toby, 7 years ago

move remaining generic controls to G2ctrls

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