source: trunk/exports/G2export_CIF.py @ 1782

Last change on this file since 1782 was 1782, checked in by vondreele, 8 years ago

fix display of fixed wavelength selection
show no. user rejected refl. in lst file after least squares
improve color display for Refl. Lists
skip user rejected reflections in single crystal cif output

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