source: trunk/exports/G2export_CIF.py @ 1659

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

get fourier density min/max & keep them
enter fourier min/max into cif file

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