source: trunk/exports/G2export_CIF.py @ 2567

Last change on this file since 2567 was 2567, checked in by vondreele, 5 years ago

fix to export CIF sym. op. problem
use ValidatedTxtctrl? for all values in PDF Controls, Background & RDFDialog

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