source: trunk/exports/G2export_CIF.py @ 2449

Last change on this file since 2449 was 2449, checked in by toby, 5 years ago

Fix CIF exports; make sure exporters are imported only once (on Mac)

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