source: trunk/exports/G2export_CIF.py @ 2354

Last change on this file since 2354 was 2354, checked in by toby, 6 years ago

fix export of TOF powder data

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