source: trunk/exports/G2export_CIF.py @ 1102

Last change on this file since 1102 was 1102, checked in by toby, 8 years ago

change exports to always have lists of phases & histograms; new export examples; CIF export fixes

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