source: trunk/exports/G2export_CIF.py @ 1081

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

revise export menu; add multiple selection to G2gd.ItemSelector?

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 114.0 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2013-10-04 03:33:07 +0000 (Fri, 04 Oct 2013) $
5# $Author: toby $
6# $Revision: 1081 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 1081 2013-10-04 03:33:07Z 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: 1081 $")
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
1526        # Someday: get restraint & constraint info
1527        #restraintDict = self.OverallParms.get('Restraints',{})
1528        #for i in  self.OverallParms['Constraints']:
1529        #    print i
1530        #    for j in self.OverallParms['Constraints'][i]:
1531        #        print j
1532
1533        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
1534        # is there anything to export?
1535        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
1536           self.G2frame.ErrorDialog(
1537               'Empty project',
1538               'Project does not contain any data or phases. Are they interconnected?')
1539           return
1540        # get the project file name
1541        self.CIFname = os.path.splitext(
1542            os.path.split(self.G2frame.GSASprojectfile)[1]
1543            )[0]
1544        self.CIFname = self.CIFname.replace(' ','')
1545        if not self.CIFname: # none defined & needed, save as GPX to get one
1546            self.G2frame.OnFileSaveas(None)
1547            if not self.G2frame.GSASprojectfile: return
1548            self.CIFname = os.path.splitext(
1549                os.path.split(self.G2frame.GSASprojectfile)[1]
1550                )[0]
1551            self.CIFname = self.CIFname.replace(' ','')
1552        # test for quick CIF mode or no data
1553        self.quickmode = False
1554        phasenam = phasenum = None # include all phases
1555        if self.mode != "full" or len(self.powderDict) + len(self.xtalDict) == 0:
1556            self.quickmode = True
1557            oneblock = True
1558            if len(self.Phases) == 0:
1559                self.G2frame.ErrorDialog(
1560                    'No phase present',
1561                    'Cannot create a coordinates CIF with no phases')
1562                return
1563            elif len(self.Phases) > 1: # quick mode: choose one phase
1564                choices = sorted(self.Phases.keys())
1565                phasenum = G2gd.ItemSelector(choices,self.G2frame)
1566                if phasenum is None: return
1567                phasenam = choices[phasenum]
1568        # will this require a multiblock CIF?
1569        elif len(self.Phases) > 1:
1570            oneblock = False
1571        elif len(self.powderDict) + len(self.xtalDict) > 1:
1572            oneblock = False
1573        else: # one phase, one dataset, Full CIF
1574            oneblock = True
1575
1576        # make sure needed infomation is present
1577        # get CIF author name -- required for full CIFs
1578        try:
1579            self.author = self.OverallParms['Controls'].get("Author",'').strip()
1580        except KeyError:
1581            pass
1582        while not (self.author or self.quickmode):
1583            if not EditAuthor(): return
1584        self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
1585
1586        # check there is an instrument name for every histogram
1587        if not self.quickmode:
1588            invalid = 0
1589            key3 = 'InstrName'
1590            for hist in self.Histograms:
1591                if hist.startswith("PWDR"): 
1592                    key2 = "Sample Parameters"
1593                    d = self.Histograms[hist][key2]
1594                elif hist.startswith("HKLF"): 
1595                    key2 = "Instrument Parameters"
1596                    d = self.Histograms[hist][key2][0]                   
1597                instrname = d.get(key3)
1598                if instrname is None:
1599                    d[key3] = ''
1600                    invalid += 1
1601                elif instrname.strip() == '':
1602                    invalid += 1
1603            if invalid:
1604                msg = ""
1605                if invalid > 3: msg = (
1606                    "\n\nNote: it may be faster to set the name for\n"
1607                    "one histogram for each instrument and use the\n"
1608                    "File/Copy option to duplicate the name"
1609                    )
1610                if not EditInstNames(): return
1611        # check for a distance-angle range search range for each phase
1612        if not self.quickmode:
1613            for phasenam in sorted(self.Phases.keys()):
1614                #i = self.Phases[phasenam]['pId']
1615                phasedict = self.Phases[phasenam] # pointer to current phase info           
1616                if 'DisAglCtls' not in phasedict['General']:
1617                    dlg = G2gd.DisAglDialog(self.G2frame,{},phasedict['General'])
1618                    if dlg.ShowModal() == wx.ID_OK:
1619                        phasedict['General']['DisAglCtls'] = dlg.GetData()
1620                    else:
1621                        dlg.Destroy()
1622                        return
1623                    dlg.Destroy()
1624
1625        if oneblock and not self.quickmode:
1626            # select a dataset to use (there should only be one set in one block,
1627            # but take whatever comes 1st)
1628            for hist in self.Histograms:
1629                histblk = self.Histograms[hist]
1630                if hist.startswith("PWDR"): 
1631                    instnam = histblk["Sample Parameters"]['InstrName']
1632                    break # ignore all but 1st data histogram
1633                elif hist.startswith("HKLF"): 
1634                    instnam = histblk["Instrument Parameters"][0]['InstrName']
1635                    break # ignore all but 1st data histogram
1636        if not self.quickmode: # give the user a chance to edit all defaults
1637            self.cifdefs = wx.Dialog(
1638                self.G2frame,
1639                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
1640            EditCIFDefaults()
1641            self.cifdefs.CenterOnParent()
1642            val = self.cifdefs.ShowModal()
1643            self.cifdefs.Destroy()
1644            if val != wx.ID_OK:
1645                return
1646        #======================================================================
1647        # Start writing the CIF - single block
1648        #======================================================================
1649        print('Writing CIF output to file '+str(self.filename)+"...")
1650        openCIF(self.filename)
1651        if self.currentExportType == 'single' or self.currentExportType == 'powder':
1652            hist = self.histnam
1653            self.CIFname = self.histnam[5:40].replace(' ','')
1654            WriteCIFitem('data_'+self.CIFname)
1655            if hist.startswith("PWDR"):
1656                WritePowderData(hist)
1657            elif hist.startswith("HKLF"):
1658                WriteSingleXtalData(hist)
1659            else:
1660                print "should not happen"
1661        elif oneblock:
1662            WriteCIFitem('data_'+self.CIFname)
1663            if phasenam is None: # if not already selected, select the first phase (should be one)
1664                phasenam = self.Phases.keys()[0]
1665            #print 'phasenam',phasenam
1666            phaseblk = self.Phases[phasenam] # pointer to current phase info
1667            if not self.quickmode:
1668                instnam = instnam.replace(' ','')
1669                WriteCIFitem('_pd_block_id',
1670                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1671                             str(self.shortauthorname) + "|" + instnam)
1672                WriteAudit()
1673                writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
1674                WriteOverall()
1675                writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1676            # report the phase info
1677            WritePhaseInfo(phasenam)
1678            if hist.startswith("PWDR") and not self.quickmode:
1679                # preferred orientation
1680                SH = FormatSH(phasenam)
1681                MD = FormatHAPpo(phasenam)
1682                if SH and MD:
1683                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1684                elif SH or MD:
1685                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1686                else:
1687                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1688                    # report profile, since one-block: include both histogram and phase info
1689                WriteCIFitem('_pd_proc_ls_profile_function',
1690                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
1691                    +'\n'+FormatPhaseProfile(phasenam))
1692                histblk = self.Histograms[hist]["Sample Parameters"]
1693                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
1694                WritePowderData(hist)
1695            elif hist.startswith("HKLF") and not self.quickmode:
1696                histprm = self.Histograms[hist]["Instrument Parameters"][0]
1697                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
1698                WriteSingleXtalData(hist)
1699        else:
1700        #======================================================================
1701        # Start writing the CIF - multiblock
1702        #======================================================================
1703            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
1704            try:
1705                dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
1706                Size = dlg.GetSize()
1707                Size = (int(Size[0]*3),Size[1]) # increase size along x
1708                dlg.SetSize(Size)
1709                dlg.CenterOnParent()
1710
1711                # publication info
1712                step = 1
1713                dlg.Update(step,"Exporting overall section")
1714                WriteCIFitem('\ndata_'+self.CIFname+'_publ')
1715                WriteAudit()
1716                WriteCIFitem('_pd_block_id',
1717                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1718                             str(self.shortauthorname) + "|Overall")
1719                writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
1720                # ``template_publ.cif`` or a modified version
1721                # overall info
1722                WriteCIFitem('data_'+str(self.CIFname)+'_overall')
1723                WriteOverall()
1724                #============================================================
1725                WriteCIFitem('# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
1726                datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
1727                # loop over phase blocks
1728                if len(self.Phases) > 1:
1729                    loopprefix = ''
1730                    WriteCIFitem('loop_   _pd_phase_block_id')
1731                else:
1732                    loopprefix = '_pd_phase_block_id'
1733
1734                for phasenam in sorted(self.Phases.keys()):
1735                    i = self.Phases[phasenam]['pId']
1736                    datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1737                                 'phase_'+ str(i) + '|' + str(self.shortauthorname))
1738                    WriteCIFitem(loopprefix,datablockidDict[phasenam])
1739                # loop over data blocks
1740                if len(self.powderDict) + len(self.xtalDict) > 1:
1741                    loopprefix = ''
1742                    WriteCIFitem('loop_   _pd_block_diffractogram_id')
1743                else:
1744                    loopprefix = '_pd_block_diffractogram_id'
1745                for i in sorted(self.powderDict.keys()):
1746                    hist = self.powderDict[i]
1747                    histblk = self.Histograms[hist]
1748                    instnam = histblk["Sample Parameters"]['InstrName']
1749                    instnam = instnam.replace(' ','')
1750                    j = histblk['hId']
1751                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1752                                             str(self.shortauthorname) + "|" +
1753                                             instnam + "_hist_"+str(j))
1754                    WriteCIFitem(loopprefix,datablockidDict[hist])
1755                for i in sorted(self.xtalDict.keys()):
1756                    hist = self.xtalDict[i]
1757                    histblk = self.Histograms[hist]
1758                    instnam = histblk["Instrument Parameters"][0]['InstrName']
1759                    instnam = instnam.replace(' ','')
1760                    i = histblk['hId']
1761                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1762                                             str(self.shortauthorname) + "|" +
1763                                             instnam + "_hist_"+str(i))
1764                    WriteCIFitem(loopprefix,datablockidDict[hist])
1765                #============================================================
1766                # loop over phases, exporting them
1767                phasebyhistDict = {} # create a cross-reference to phases by histogram
1768                for j,phasenam in enumerate(sorted(self.Phases.keys())):
1769                    step += 1
1770                    dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
1771                    i = self.Phases[phasenam]['pId']
1772                    WriteCIFitem('\ndata_'+self.CIFname+"_phase_"+str(i))
1773                    WriteCIFitem('# Information for phase '+str(i))
1774                    WriteCIFitem('_pd_block_id',datablockidDict[phasenam])
1775                    # report the phase
1776                    writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1777                    WritePhaseInfo(phasenam)
1778                    # preferred orientation
1779                    SH = FormatSH(phasenam)
1780                    MD = FormatHAPpo(phasenam)
1781                    if SH and MD:
1782                        WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1783                    elif SH or MD:
1784                        WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1785                    else:
1786                        WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1787                    # report sample profile terms
1788                    PP = FormatPhaseProfile(phasenam)
1789                    if PP:
1790                        WriteCIFitem('_pd_proc_ls_profile_function',PP)
1791
1792                #============================================================
1793                # loop over histograms, exporting them
1794                for i in sorted(self.powderDict.keys()):
1795                    hist = self.powderDict[i]
1796                    histblk = self.Histograms[hist]
1797                    if hist.startswith("PWDR"): 
1798                        step += 1
1799                        dlg.Update(step,"Exporting "+hist.strip())
1800                        WriteCIFitem('\ndata_'+self.CIFname+"_pwd_"+str(i))
1801                        #instnam = histblk["Sample Parameters"]['InstrName']
1802                        # report instrumental profile terms
1803                        WriteCIFitem('_pd_proc_ls_profile_function',
1804                            FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1805                        WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
1806                        WriteCIFitem('_pd_block_id',datablockidDict[hist])
1807                        histprm = self.Histograms[hist]["Sample Parameters"]
1808                        writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
1809                        WritePowderData(hist)
1810                for i in sorted(self.xtalDict.keys()):
1811                    hist = self.xtalDict[i]
1812                    histblk = self.Histograms[hist]
1813                    if hist.startswith("HKLF"): 
1814                        step += 1
1815                        dlg.Update(step,"Exporting "+hist.strip())
1816                        WriteCIFitem('\ndata_'+self.CIFname+"_sx_"+str(i))
1817                        #instnam = histblk["Instrument Parameters"][0]['InstrName']
1818                        WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
1819                        WriteCIFitem('_pd_block_id',datablockidDict[hist])
1820                        histprm = self.Histograms[hist]["Instrument Parameters"][0]
1821                        writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
1822                        WriteSingleXtalData(hist)
1823
1824            except Exception:
1825                import traceback
1826                print(traceback.format_exc())
1827                self.G2frame.ErrorDialog('Exception',
1828                                         'Error occurred in CIF creation. '+
1829                                         'See full error message in console output ')
1830            finally:
1831                dlg.Destroy()
1832
1833        WriteCIFitem('#--' + 15*'eof--' + '#')
1834        closeCIF()
1835        print("...export completed")
1836        # end of CIF export
1837
1838class ExportPhaseCIF(ExportCIF):
1839    '''Used to create a simple CIF of at most one phase. Uses exact same code as
1840    :class:`ExportCIF` except that `self.mode` is set to "simple". Shows up in menu as
1841    Quick CIF
1842
1843    :param wx.Frame G2frame: reference to main GSAS-II frame
1844    '''
1845    def __init__(self,G2frame):
1846        G2IO.ExportBaseclass.__init__(self,
1847            G2frame=G2frame,
1848            formatName = 'Quick CIF',
1849            extension='.cif',
1850            longFormatName = 'Export one phase in CIF'
1851            )
1852        self.exporttype = ['phase']
1853        # CIF-specific items
1854        self.author = ''
1855        self.mode = 'simple'
1856
1857class ExportDataCIF(ExportCIF):
1858    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
1859    :class:`ExportCIF` except that `self.mode` is set to "simple" and `self.currentExportType`
1860    is set to "single" or "powder" in `self.SetupExport`. Shows up in menus as Data-only CIF.
1861
1862    :param wx.Frame G2frame: reference to main GSAS-II frame
1863    '''
1864    def __init__(self,G2frame):
1865        G2IO.ExportBaseclass.__init__(self,
1866            G2frame=G2frame,
1867            formatName = 'Data-only CIF',
1868            extension='.cif',
1869            longFormatName = 'Export data as CIF'
1870            )
1871        self.exporttype = ['single','powder']
1872        # CIF-specific items
1873        self.author = ''
1874        self.mode = 'simple'
1875
1876#===============================================================================
1877# misc CIF utilities
1878#===============================================================================
1879def PickleCIFdict(fil):
1880    '''Loads a CIF dictionary, cherry picks out the items needed
1881    by local code and sticks them into a python dict and writes
1882    that dict out as a cPickle file for later reuse.
1883    If the write fails a warning message is printed,
1884    but no exception occurs.
1885
1886    :param str fil: file name of CIF dictionary, will usually end
1887      in .dic
1888    :returns: the dict with the definitions 
1889    '''
1890    import CifFile as cif # PyCifRW from James Hester
1891    cifdic = {}
1892    try:
1893        fp = open(fil,'r')             # patch: open file to avoid windows bug
1894        dictobj = cif.CifDic(fp)
1895        fp.close()
1896    except IOError:
1897        dictobj = cif.CifDic(fil)
1898    if DEBUG: print('loaded '+str(fil))
1899    for item in dictobj.keys():
1900        cifdic[item] = {}
1901        for j in (
1902            '_definition','_type',
1903            '_enumeration',
1904            '_enumeration_detail',
1905            '_enumeration_range'):
1906            if dictobj[item].get(j):
1907                cifdic[item][j] = dictobj[item][j]
1908    try:
1909        fil = os.path.splitext(fil)[0]+'.cpickle'
1910        fp = open(fil,'w')
1911        cPickle.dump(cifdic,fp)
1912        fp.close()
1913        if DEBUG: print('wrote '+str(fil))
1914    except:
1915        print ('Unable to write '+str(fil))
1916    return cifdic
1917
1918def LoadCIFdic():
1919    '''Create a composite core+powder CIF lookup dict containing
1920    information about all items in the CIF dictionaries, loading
1921    pickled files if possible. The routine looks for files
1922    named cif_core.cpickle and cif_pd.cpickle in every
1923    directory in the path and if they are not found, files
1924    cif_core.dic and/or cif_pd.dic are read.
1925
1926    :returns: the dict with the definitions 
1927    '''
1928    cifdic = {}
1929    for ftyp in "cif_core","cif_pd":
1930        for loc in sys.path:
1931            fil = os.path.join(loc,ftyp+".cpickle")
1932            if not os.path.exists(fil): continue
1933            fp = open(fil,'r')
1934            try:
1935                cifdic.update(cPickle.load(fp))
1936                if DEBUG: print('reloaded '+str(fil))
1937                break
1938            finally:
1939                fp.close()
1940        else:
1941            for loc in sys.path:
1942                fil = os.path.join(loc,ftyp+".dic")
1943                if not os.path.exists(fil): continue
1944                #try:
1945                if True:
1946                    cifdic.update(PickleCIFdict(fil))
1947                    break
1948                #except:
1949                #    pass
1950            else:
1951                print('Could not load '+ftyp+' dictionary')
1952    return cifdic
1953
1954class CIFdefHelp(wx.Button):
1955    '''Create a help button that displays help information on
1956    the current data item
1957
1958    :param parent: the panel which will be the parent of the button
1959    :param str msg: the help text to be displayed
1960    :param wx.Dialog helpwin: Frame for CIF editing dialog
1961    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
1962    '''
1963    def __init__(self,parent,msg,helpwin,helptxt):
1964        wx.Button.__init__(self,parent,wx.ID_HELP)
1965        self.Bind(wx.EVT_BUTTON,self._onPress)
1966        self.msg=msg
1967        self.parent = parent
1968        #self.helpwin = self.parent.helpwin
1969        self.helpwin = helpwin
1970        self.helptxt = helptxt
1971    def _onPress(self,event):
1972        'Respond to a button press by displaying the requested text'
1973        try:
1974            #helptxt = self.helptxt
1975            ow,oh = self.helptxt.GetSize()
1976            self.helptxt.SetLabel(self.msg)
1977            w,h = self.helptxt.GetSize()
1978            if h > oh:
1979                self.helpwin.GetSizer().Fit(self.helpwin)
1980        except: # error posting help, ignore
1981            return
1982
1983def CIF2dict(cf):
1984    '''copy the contents of a CIF out from a PyCifRW block object
1985    into a dict
1986
1987    :returns: cifblk, loopstructure where cifblk is a dict with
1988      CIF items and loopstructure is a list of lists that defines
1989      which items are in which loops.
1990    '''
1991    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
1992    loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
1993    dblk = {}
1994    for item in cf[blk].keys(): # make a copy of all the items in the block
1995        dblk[item] = cf[blk][item]
1996    return dblk,loopstructure
1997
1998def dict2CIF(dblk,loopstructure,blockname='Template'):
1999    '''Create a PyCifRW CIF object containing a single CIF
2000    block object from a dict and loop structure list.
2001
2002    :param dblk: a dict containing values for each CIF item
2003    :param list loopstructure: a list of lists containing the contents of
2004      each loop, as an example::
2005
2006         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2007
2008      this describes a CIF with this type of structure::
2009
2010        loop_ _a _b <a1> <b1> <a2> ...
2011        loop_ _c <c1> <c2>...
2012        loop _d_1 _d_2 _d_3 ...
2013
2014      Note that the values for each looped CIF item, such as _a,
2015      are contained in a list, for example as cifblk["_a"]
2016
2017    :param str blockname: an optional name for the CIF block.
2018      Defaults to 'Template'
2019
2020    :returns: the newly created PyCifRW CIF object
2021    '''
2022
2023    import CifFile as cif # PyCifRW from James Hester
2024    # compile a 'list' of items in loops
2025    loopnames = set()
2026    for i in loopstructure:
2027        loopnames |= set(i)
2028    # create a new block
2029    newblk = cif.CifBlock()
2030    # add the looped items
2031    for keys in loopstructure:
2032        vals = []
2033        for key in keys:
2034            vals.append(dblk[key])
2035        newblk.AddCifItem(([keys],[vals]))
2036    # add the non-looped items
2037    for item in dblk:
2038        if item in loopnames: continue
2039        newblk[item] = dblk[item]
2040    # create a CIF and add the block
2041    newcf = cif.CifFile()
2042    newcf[blockname] = newblk   
2043    return newcf
2044
2045
2046class EditCIFtemplate(wx.Dialog):
2047    '''Create a dialog for editing a CIF template. The edited information is
2048    placed in cifblk. If the CIF is saved as a file, the name of that file
2049    is saved as ``self.newfile``.
2050   
2051    :param wx.Frame parent: parent frame or None
2052    :param cifblk: dict or PyCifRW block containing values for each CIF item
2053    :param list loopstructure: a list of lists containing the contents of
2054      each loop, as an example::
2055
2056         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2057
2058      this describes a CIF with this type of structure::
2059
2060        loop_ _a _b <a1> <b1> <a2> ...
2061        loop_ _c <c1> <c2>...
2062        loop _d_1 _d_2 _d_3 ...
2063
2064      Note that the values for each looped CIF item, such as _a,
2065      are contained in a list, for example as cifblk["_a"]
2066     
2067    :param str defaultname: specifies the default file name to be used for
2068      saving the CIF.
2069    '''
2070    def __init__(self,parent,cifblk,loopstructure,defaultname):
2071        OKbuttons = []
2072        self.cifblk = cifblk
2073        self.loopstructure = loopstructure
2074        self.newfile = None
2075        self.defaultname = defaultname       
2076        global CIFdic  # once this is loaded, keep it around
2077        if CIFdic is None:
2078            CIFdic = LoadCIFdic()
2079        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2080
2081        # define widgets that will be needed during panel creation
2082        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
2083        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
2084        OKbuttons.append(savebtn)
2085        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
2086        OKbtn = wx.Button(self, wx.ID_OK, "Use")
2087        OKbtn.SetDefault()
2088        OKbuttons.append(OKbtn)
2089
2090        self.SetTitle('Edit items in CIF template')
2091        vbox = wx.BoxSizer(wx.VERTICAL)
2092        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
2093        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2094        G2gd.HorizontalLine(vbox,self)
2095        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
2096        G2gd.HorizontalLine(vbox,self)
2097        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2098        btn = wx.Button(self, wx.ID_CANCEL)
2099        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
2100        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
2101        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
2102        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2103        self.SetSizer(vbox)
2104        vbox.Fit(self)
2105    def Post(self):
2106        '''Display the dialog
2107       
2108        :returns: True unless Cancel has been pressed.
2109        '''
2110        return (self.ShowModal() == wx.ID_OK)
2111    def _onSave(self,event):
2112        'Save CIF entries in a template file'
2113        dlg = wx.FileDialog(
2114            self, message="Save as CIF template",
2115            defaultDir=os.getcwd(),
2116            defaultFile=self.defaultname,
2117            wildcard="CIF (*.cif)|*.cif",
2118            style=wx.SAVE | wx.CHANGE_DIR
2119            )
2120        val = (dlg.ShowModal() == wx.ID_OK)
2121        fil = dlg.GetPath()
2122        dlg.Destroy()
2123        if val: # ignore a Cancel button
2124            fil = os.path.splitext(fil)[0]+'.cif' # force extension
2125            fp = open(fil,'w')
2126            newcf = dict2CIF(self.cifblk,self.loopstructure)
2127            fp.write(newcf.WriteOut())
2128            fp.close()
2129            self.newfile = fil
2130            self.EndModal(wx.ID_OK)
2131
2132class EditCIFpanel(wxscroll.ScrolledPanel):
2133    '''Creates a scrolled panel for editing CIF template items
2134
2135    :param wx.Frame parent: parent frame where panel will be placed
2136    :param cifblk: dict or PyCifRW block containing values for each CIF item
2137    :param list loopstructure: a list of lists containing the contents of
2138      each loop, as an example::
2139
2140         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2141
2142      this describes a CIF with this type of structure::
2143
2144        loop_ _a _b <a1> <b1> <a2> ...
2145        loop_ _c <c1> <c2>...
2146        loop _d_1 _d_2 _d_3 ...
2147
2148      Note that the values for each looped CIF item, such as _a,
2149      are contained in a list, for example as cifblk["_a"]
2150
2151    :param dict cifdic: optional CIF dictionary definitions
2152    :param list OKbuttons: A list of wx.Button objects that should
2153      be disabled when information in the CIF is invalid
2154    :param (other): optional keyword parameters for wx.ScrolledPanel
2155    '''
2156    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
2157        self.parent = parent
2158        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
2159        self.vbox = None
2160        self.AddDict = None
2161        self.cifdic = cifdic
2162        self.cifblk = cifblk
2163        self.loops = loopstructure
2164        self.parent = parent
2165        self.LayoutCalled = False
2166        self.parentOKbuttons = OKbuttons
2167        self.ValidatedControlsList = []
2168        self._fill()
2169    def _fill(self):
2170        'Fill the scrolled panel with widgets for each CIF item'
2171        wx.BeginBusyCursor()
2172        self.AddDict = {}
2173        self.ValidatedControlsList = []
2174        # delete any only contents
2175        if self.vbox:
2176            self.vbox.DeleteWindows()
2177            self.vbox = None
2178            self.Update()
2179        vbox = wx.BoxSizer(wx.VERTICAL)
2180        self.vbox = vbox
2181        # compile a 'list' of items in loops
2182        loopnames = set()
2183        for i in self.loops:
2184            loopnames |= set(i)
2185        # post the looped CIF items
2186        for lnum,lp in enumerate(self.loops):
2187            hbox = wx.BoxSizer(wx.HORIZONTAL)
2188            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
2189            vbox.Add(hbox)
2190            but = wx.Button(self,wx.ID_ANY,"Add row")
2191            self.AddDict[but]=lnum
2192           
2193            hbox.Add(but)           
2194            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
2195            fbox = wx.GridBagSizer(0, 0)
2196            vbox.Add(fbox)
2197            rows = 0
2198            for i,item in enumerate(lp):
2199                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
2200                fbox.Add(txt,(0,i+1))
2201                # if self.cifdic.get(item):
2202                #     df = self.cifdic[item].get('_definition')
2203                #     if df:
2204                #         txt.SetToolTipString(G2IO.trim(df))
2205                #         but = CIFdefHelp(self,
2206                #                          "Definition for "+item+":\n\n"+G2IO.trim(df),
2207                #                          self.parent,
2208                #                          self.parent.helptxt)
2209                #         fbox.Add(but,(1,i+1),flag=wx.ALIGN_CENTER)
2210                for j,val in enumerate(self.cifblk[item]):
2211                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
2212                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
2213                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
2214                if self.cifdic.get(item):
2215                    df = self.cifdic[item].get('_definition')
2216                    if df:
2217                        txt.SetToolTipString(G2IO.trim(df))
2218                        but = CIFdefHelp(self,
2219                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2220                                         self.parent,
2221                                         self.parent.helptxt)
2222                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
2223                rows = max(rows,len(self.cifblk[item]))
2224            for i in range(rows):
2225                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
2226                fbox.Add(txt,(i+2,0))
2227            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
2228            vbox.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2229               
2230        # post the non-looped CIF items
2231        for item in sorted(self.cifblk.keys()):
2232            if item not in loopnames:
2233                hbox = wx.BoxSizer(wx.HORIZONTAL)
2234                vbox.Add(hbox)
2235                txt = wx.StaticText(self,wx.ID_ANY,item)
2236                hbox.Add(txt)
2237                ent = self.CIFEntryWidget(self.cifblk,item,item)
2238                hbox.Add(ent)
2239                if self.cifdic.get(item):
2240                    df = self.cifdic[item].get('_definition')
2241                    if df:
2242                        txt.SetToolTipString(G2IO.trim(df))
2243                        but = CIFdefHelp(self,
2244                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2245                                         self.parent,
2246                                         self.parent.helptxt)
2247                        hbox.Add(but,0,wx.ALL,2)
2248        self.SetSizer(vbox)
2249        #vbox.Fit(self.parent)
2250        self.SetAutoLayout(1)
2251        self.SetupScrolling()
2252        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
2253        self.Layout()
2254        wx.EndBusyCursor()
2255    def OnLayoutNeeded(self,event):
2256        '''Called when an update of the panel layout is needed. Calls
2257        self.DoLayout after the current operations are complete using
2258        CallAfter. This is called only once, according to flag
2259        self.LayoutCalled, which is cleared in self.DoLayout.
2260        '''
2261        if self.LayoutCalled: return # call already queued
2262        wx.CallAfter(self.DoLayout) # queue a call
2263        self.LayoutCalled = True
2264    def DoLayout(self):
2265        '''Update the Layout and scroll bars for the Panel. Clears
2266        self.LayoutCalled so that next change to panel can
2267        request a new update
2268        '''
2269        wx.BeginBusyCursor()
2270        self.Layout()
2271        self.SetupScrolling()
2272        wx.EndBusyCursor()
2273        self.LayoutCalled = False
2274    def OnAddRow(self,event):
2275        'add a row to a loop'
2276        lnum = self.AddDict.get(event.GetEventObject())
2277        if lnum is None: return
2278        for item in self.loops[lnum]:
2279            self.cifblk[item].append('?')
2280        self._fill()
2281
2282    def ControlOKButton(self,setvalue):
2283        '''Enable or Disable the OK button(s) for the dialog. Note that this is
2284        passed into the ValidatedTxtCtrl for use by validators.
2285
2286        :param bool setvalue: if True, all entries in the dialog are
2287          checked for validity. The first invalid control triggers
2288          disabling of buttons.
2289          If False then the OK button(s) are disabled with no checking
2290          of the invalid flag for each control.
2291        '''
2292        if setvalue: # turn button on, do only if all controls show as valid
2293            for ctrl in self.ValidatedControlsList:
2294                if ctrl.invalid:
2295                    for btn in self.parentOKbuttons:
2296                        btn.Disable()
2297                    return
2298            else:
2299                for btn in self.parentOKbuttons:
2300                    btn.Enable()
2301        else:
2302            for btn in self.parentOKbuttons:
2303                btn.Disable()
2304       
2305    def CIFEntryWidget(self,dct,item,dataname):
2306        '''Create an entry widget for a CIF item. Use a validated entry for numb values
2307        where int is required when limits are integers and floats otherwise.
2308        At present this does not allow entry of the special CIF values of "." and "?" for
2309        numerical values and highlights them as invalid.
2310        Use a selection widget when there are specific enumerated values for a string.       
2311        '''
2312        if self.cifdic.get(dataname):
2313            if self.cifdic[dataname].get('_enumeration'):
2314                values = ['?']+self.cifdic[dataname]['_enumeration']
2315                choices = ['undefined']
2316                for i in self.cifdic[dataname].get('_enumeration_detail',values):
2317                    choices.append(G2IO.trim(i))
2318                ent = G2gd.EnumSelector(self, dct, item, choices, values, size=(200, -1))
2319                return ent
2320            if self.cifdic[dataname].get('_type') == 'numb':
2321                mn = None
2322                mx = None
2323                hint = int
2324                if self.cifdic[dataname].get('_enumeration_range'):
2325                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
2326                    if '.' in rng[0] or '.' in rng[1]: hint = float
2327                    if rng[0]: mn = hint(rng[0])
2328                    if rng[1]: mx = hint(rng[1])
2329                    ent = G2gd.ValidatedTxtCtrl(
2330                        self,dct,item,typeHint=hint,min=mn,max=mx,
2331                        CIFinput=True,
2332                        OKcontrol=self.ControlOKButton)
2333                    self.ValidatedControlsList.append(ent)
2334                    return ent
2335        rw1 = rw.ResizeWidget(self)
2336        ent = G2gd.ValidatedTxtCtrl(
2337            rw1,dct,item,size=(100, 20),
2338            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
2339            CIFinput=True,
2340            OKcontrol=self.ControlOKButton)
2341        self.ValidatedControlsList.append(ent)
2342        return rw1
2343
2344class CIFtemplateSelect(wx.BoxSizer):
2345    '''Create a set of buttons to show, select and edit a CIF template
2346   
2347    :param frame: wx.Frame object of parent
2348    :param panel: wx.Panel object where widgets should be placed
2349    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
2350      the type of template
2351    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
2352      "CIF_template" will be used to store either a list or a string.
2353      If a list, it will contain a dict and a list defining loops. If
2354      an str, it will contain a file name.   
2355    :param function repaint: reference to a routine to be called to repaint
2356      the frame after a change has been made
2357    :param str title: A line of text to show at the top of the window
2358    :param str defaultname: specifies the default file name to be used for
2359      saving the CIF.
2360    '''
2361    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
2362        wx.BoxSizer.__init__(self,wx.VERTICAL)
2363        self.cifdefs = frame
2364        self.dict = G2dict
2365        self.repaint = repaint
2366        templateDefName = 'template_'+tmplate+'.cif'
2367        self.CIF = G2dict.get("CIF_template")
2368        if defaultname:
2369            self.defaultname = defaultname.encode('ascii','replace').strip().replace(' ','_')
2370            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
2371            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
2372        else:
2373            self.defaultname = ''
2374           
2375        txt = wx.StaticText(panel,wx.ID_ANY,title)
2376        self.Add(txt,0,wx.ALIGN_CENTER)
2377        # change font on title
2378        txtfnt = txt.GetFont()
2379        txtfnt.SetWeight(wx.BOLD)
2380        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
2381        txt.SetFont(txtfnt)
2382        self.Add((-1,3))
2383
2384        if not self.CIF: # empty or None
2385            for pth in [os.getcwd()]+sys.path:
2386                fil = os.path.join(pth,self.defaultname)
2387                if os.path.exists(fil) and self.defaultname:
2388                    self.CIF = fil
2389                    CIFtxt = "Template: "+self.defaultname
2390                    break
2391            else:
2392                for pth in sys.path:
2393                    fil = os.path.join(pth,templateDefName)
2394                    if os.path.exists(fil):
2395                        self.CIF = fil
2396                        CIFtxt = "Template: "+templateDefName
2397                        break
2398                else:
2399                    print("Default CIF template "+self.defaultname+' not found in path!')
2400                    self.CIF = None
2401                    CIFtxt = "none! (No template found)"
2402        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
2403            if not os.path.exists(self.CIF):
2404                print("Error: template file has disappeared: "+self.CIF)
2405                self.CIF = None
2406                CIFtxt = "none! (file not found)"
2407            else:
2408                if len(self.CIF) < 50:
2409                    CIFtxt = "File: "+self.CIF
2410                else:
2411                    CIFtxt = "File: ..."+self.CIF[-50:]
2412        else:
2413            CIFtxt = "Template is customized"
2414        # show template source
2415        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
2416        # show str, button to select file; button to edit (if CIF defined)
2417        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
2418        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
2419        hbox =  wx.BoxSizer(wx.HORIZONTAL)
2420        hbox.Add(but,0,0,2)
2421        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
2422        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
2423        if self.CIF is None: but.Disable() # nothing to edit!
2424        hbox.Add(but,0,0,2)
2425        self.Add(hbox)
2426    def _onGetTemplateFile(self,event):
2427        'select a template file'
2428        dlg = wx.FileDialog(
2429            self.cifdefs, message="Read CIF template file",
2430            defaultDir=os.getcwd(),
2431            defaultFile=self.defaultname,
2432            wildcard="CIF (*.cif)|*.cif",
2433            style=wx.OPEN | wx.CHANGE_DIR
2434            )
2435        ret = dlg.ShowModal()
2436        fil = dlg.GetPath()
2437        dlg.Destroy()
2438        if ret == wx.ID_OK:
2439            try:
2440                cf = G2IO.ReadCIF(fil)
2441                if len(cf.keys()) == 0:
2442                    raise Exception,"No CIF data_ blocks found"
2443                if len(cf.keys()) != 1:
2444                    raise Exception, 'Error, CIF Template has more than one block: '+fil
2445                self.dict["CIF_template"] = fil
2446            except Exception as err:
2447                print('\nError reading CIF: '+fil)
2448                dlg = wx.MessageDialog(self.cifdefs,
2449                                   'Error reading CIF '+fil,
2450                                   'Error in CIF file',
2451                                   wx.OK)
2452                dlg.ShowModal()
2453                dlg.Destroy()
2454                print(err.message)
2455                return
2456            self.repaint() #EditCIFDefaults()
2457
2458    def _onEditTemplateContents(self,event):
2459        'Called to edit the contents of a CIF template'
2460        if type(self.CIF) is list or  type(self.CIF) is tuple:
2461            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
2462        else:
2463            cf = G2IO.ReadCIF(self.CIF)
2464            dblk,loopstructure = CIF2dict(cf)
2465        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
2466        val = dlg.Post()
2467        if val:
2468            if dlg.newfile: # results saved in file
2469                self.dict["CIF_template"] = dlg.newfile
2470            else:
2471                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
2472            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
2473        else:
2474            dlg.Destroy()       
2475
2476#===============================================================================
2477# end of misc CIF utilities
2478#===============================================================================
Note: See TracBrowser for help on using the repository browser.