source: branch/logging/exports/G2export_CIF.py @ 2317

Last change on this file since 2317 was 1261, checked in by toby, 11 years ago

reorg exports to implement directory selection

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