source: trunk/exports/G2export_CIF.py @ 2819

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

fix printing of nonascii characters (e.g. "umlaut-o") in file names, etc. in exporters

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 125.2 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2017-05-04 14:14:19 +0000 (Thu, 04 May 2017) $
5# $Author: vondreele $
6# $Revision: 2819 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 2819 2017-05-04 14:14:19Z 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: 2819 $")
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                if not sitemultlist: 
660                    General['cellZ'] = 1
661                    return
662                for i in range(2,min(sitemultlist)+1):
663                    for m in sitemultlist:
664                        if m % i != 0:
665                            break
666                        else:
667                            Z = i
668                General['cellZ'] = Z # save it
669
670            # when scattering factors are included in the CIF, this needs to be
671            # added to the loop here but only in the one-block case.
672            # For multiblock CIFs, scattering factors go in the histogram
673            # blocks  (for all atoms in all appropriate phases) - an example?:
674#loop_
675#    _atom_type_symbol
676#    _atom_type_description
677#    _atom_type_scat_dispersion_real
678#    _atom_type_scat_dispersion_imag
679#    _atom_type_scat_source
680#    'C' 'C' 0.0033 0.0016
681#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
682#    'H' 'H' 0.0000 0.0000
683#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
684#    'P' 'P' 0.1023 0.0942
685#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
686#    'Cl' 'Cl' 0.1484 0.1585
687#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
688#    'Cu' 'Cu' 0.3201 1.2651
689#                         'International Tables Vol C Tables 4.2.6.8 and 6.1.1.4'
690
691            #if oneblock: # add scattering factors for current phase here
692            WriteCIFitem('\nloop_  _atom_type_symbol _atom_type_number_in_cell')
693            formula = ''
694            reload(G2mth)
695            for elem in HillSortElements(compDict.keys()):
696                WriteCIFitem('  ' + PutInCol(elem,4) +
697                             G2mth.ValEsd(compDict[elem],-0.009,True))
698                if formula: formula += " "
699                formula += elem
700                if compDict[elem] == Z: continue
701                formula += G2mth.ValEsd(compDict[elem]/Z,-0.009,True)
702            WriteCIFitem( '\n# Note that Z affects _cell_formula_sum and _weight')
703            WriteCIFitem( '_cell_formula_units_Z',str(Z))
704            WriteCIFitem( '_chemical_formula_sum',formula)
705            WriteCIFitem( '_chemical_formula_weight',
706                          G2mth.ValEsd(cellmass/Z,-0.09,True))
707
708        def WriteDistances(phasenam,SymOpList,offsetList,symOpList,G2oprList):
709            '''Report bond distances and angles for the CIF
710
711            Note that _geom_*_symmetry_* fields are values of form
712            n_klm where n is the symmetry operation in SymOpList (counted
713            starting with 1) and (k-5, l-5, m-5) are translations to add
714            to (x,y,z). See
715            http://www.iucr.org/__data/iucr/cifdic_html/1/cif_core.dic/Igeom_angle_site_symmetry_.html
716
717            TODO: need a method to select publication flags for distances/angles
718            '''
719            phasedict = self.Phases[phasenam] # pointer to current phase info           
720            Atoms = phasedict['Atoms']
721            generalData = phasedict['General']
722            # create a dict for storing Pub flag for bonds/angles, if needed
723            if phasedict['General'].get("DisAglHideFlag") is None:
724                phasedict['General']["DisAglHideFlag"] = {}
725            DisAngSel = phasedict['General']["DisAglHideFlag"]
726            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
727            cn = ct-1
728            fpfx = str(phasedict['pId'])+'::Afrac:'       
729            cfrac = cx+3
730            DisAglData = {}
731            # create a list of atoms, but skip atoms with zero occupancy
732            xyz = []
733            fpfx = str(phasedict['pId'])+'::Afrac:'       
734            for i,atom in enumerate(Atoms):
735                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
736                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
737            if 'DisAglCtls' not in generalData:
738                # should not happen, since DisAglDialog should be called
739                # for all phases before getting here
740                dlg = G2gd.DisAglDialog(
741                    self.G2frame,
742                    {},
743                    generalData)
744                if dlg.ShowModal() == wx.ID_OK:
745                    generalData['DisAglCtls'] = dlg.GetData()
746                else:
747                    dlg.Destroy()
748                    return
749                dlg.Destroy()
750            DisAglData['OrigAtoms'] = xyz
751            DisAglData['TargAtoms'] = xyz
752            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
753                generalData['SGData'])
754
755#            xpandSGdata = generalData['SGData'].copy()
756#            xpandSGdata.update({'SGOps':symOpList,
757#                                'SGInv':False,
758#                                'SGLatt':'P',
759#                                'SGCen':np.array([[0, 0, 0]]),})
760#            DisAglData['SGData'] = xpandSGdata
761            DisAglData['SGData'] = generalData['SGData'].copy()
762
763            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
764            if 'pId' in phasedict:
765                DisAglData['pId'] = phasedict['pId']
766                DisAglData['covData'] = self.OverallParms['Covariance']
767            try:
768                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
769                    generalData['DisAglCtls'],
770                    DisAglData)
771            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
772                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
773                   
774            # loop over interatomic distances for this phase
775            WriteCIFitem('\n# MOLECULAR GEOMETRY')
776            WriteCIFitem('loop_' + 
777                         '\n   _geom_bond_atom_site_label_1' +
778                         '\n   _geom_bond_atom_site_label_2' + 
779                         '\n   _geom_bond_distance' + 
780                         '\n   _geom_bond_site_symmetry_1' + 
781                         '\n   _geom_bond_site_symmetry_2' + 
782                         '\n   _geom_bond_publ_flag')
783
784            for i in sorted(AtomLabels.keys()):
785                Dist = DistArray[i]
786                for D in Dist:
787                    line = '  '+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[D[0]],6)
788                    sig = D[4]
789                    if sig == 0: sig = -0.00009
790                    line += PutInCol(G2mth.ValEsd(D[3],sig,True),10)
791                    line += "  1_555 "
792                    line += " {:3d}_".format(G2opcodes.index(D[2])+1)
793                    for d in D[1]:
794                        line += "{:1d}".format(d+5)
795                    if DisAngSel.get((i,tuple(D[0:3]))):
796                        line += " no"
797                    else:
798                        line += " yes"
799                    WriteCIFitem(line)
800
801            # loop over interatomic angles for this phase
802            WriteCIFitem('\nloop_' + 
803                         '\n   _geom_angle_atom_site_label_1' + 
804                         '\n   _geom_angle_atom_site_label_2' + 
805                         '\n   _geom_angle_atom_site_label_3' + 
806                         '\n   _geom_angle' + 
807                         '\n   _geom_angle_site_symmetry_1' +
808                         '\n   _geom_angle_site_symmetry_2' + 
809                         '\n   _geom_angle_site_symmetry_3' + 
810                         '\n   _geom_angle_publ_flag')
811
812            for i in sorted(AtomLabels.keys()):
813                Dist = DistArray[i]
814                for k,j,tup in AngArray[i]:
815                    Dj = Dist[j]
816                    Dk = Dist[k]
817                    line = '  '+PutInCol(AtomLabels[Dj[0]],6)+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[Dk[0]],6)
818                    sig = tup[1]
819                    if sig == 0: sig = -0.009
820                    line += PutInCol(G2mth.ValEsd(tup[0],sig,True),10)
821                    line += " {:3d}_".format(G2opcodes.index(Dj[2])+1)
822                    for d in Dj[1]:
823                        line += "{:1d}".format(d+5)
824                    line += "  1_555 "
825                    line += " {:3d}_".format(G2opcodes.index(Dk[2])+1)
826                    for d in Dk[1]:
827                        line += "{:1d}".format(d+5)
828                    key = (tuple(Dk[0:3]),i,tuple(Dj[0:3]))
829                    if DisAngSel.get(key):
830                        line += " no"
831                    else:
832                        line += " yes"
833                    WriteCIFitem(line)
834
835        def WritePhaseInfo(phasenam,hist=None):
836            'Write out the phase information for the selected phase'
837            WriteCIFitem('\n# phase info for '+str(phasenam) + ' follows')
838            phasedict = self.Phases[phasenam] # pointer to current phase info           
839            WriteCIFitem('_pd_phase_name', phasenam)
840            cellList,cellSig = self.GetCell(phasenam)
841            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
842            names = ['length_a','length_b','length_c',
843                     'angle_alpha','angle_beta ','angle_gamma',
844                     'volume']
845            prevsig = 0
846            for lbl,defsig,val,sig in zip(names,defsigL,cellList,cellSig):
847                if sig:
848                    txt = G2mth.ValEsd(val,sig)
849                    prevsig = -sig # use this as the significance for next value
850                else:
851                    txt = G2mth.ValEsd(val,min(defsig,prevsig),True)
852                WriteCIFitem('_cell_'+lbl,txt)
853                   
854            WriteCIFitem('_symmetry_cell_setting',
855                         phasedict['General']['SGData']['SGSys'])
856
857            spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
858            # regularize capitalization and remove trailing H/R
859            spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
860            WriteCIFitem('_symmetry_space_group_name_H-M',spacegroup)
861
862            # generate symmetry operations including centering and center of symmetry
863            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
864                phasedict['General']['SGData'])
865            WriteCIFitem('loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
866            for i,op in enumerate(SymOpList,start=1):
867                WriteCIFitem('   {:3d}  {:}'.format(i,op.lower()))
868
869            # loop over histogram(s) used in this phase
870            if not oneblock and not self.quickmode and not hist:
871                # report pointers to the histograms used in this phase
872                histlist = []
873                for hist in self.Phases[phasenam]['Histograms']:
874                    if self.Phases[phasenam]['Histograms'][hist]['Use']:
875                        if phasebyhistDict.get(hist):
876                            phasebyhistDict[hist].append(phasenam)
877                        else:
878                            phasebyhistDict[hist] = [phasenam,]
879                        blockid = datablockidDict.get(hist)
880                        if not blockid:
881                            print("Internal error: no block for data. Phase "+str(
882                                phasenam)+" histogram "+str(hist))
883                            histlist = []
884                            break
885                        histlist.append(blockid)
886
887                if len(histlist) == 0:
888                    WriteCIFitem('# Note: phase has no associated data')
889
890            # report atom params
891            if phasedict['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
892                WriteAtomsNuclear(phasenam)
893            else:
894                raise Exception,"no export for "+str(phasedict['General']['Type'])+" coordinates implemented"
895            # report cell contents
896            WriteComposition(phasenam)
897            if not self.quickmode and phasedict['General']['Type'] == 'nuclear':      # report distances and angles
898                WriteDistances(phasenam,SymOpList,offsetList,symOpList,G2oprList)
899            if 'Map' in phasedict['General'] and 'minmax' in phasedict['General']['Map']:
900                WriteCIFitem('\n# Difference density results')
901                MinMax = phasedict['General']['Map']['minmax']
902                WriteCIFitem('_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
903                WriteCIFitem('_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
904               
905        def Yfmt(ndec,val):
906            'Format intensity values'
907            out = ("{:."+str(ndec)+"f}").format(val)
908            out = out.rstrip('0')  # strip zeros to right of decimal
909            return out.rstrip('.')  # and decimal place when not needed
910           
911        def WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,nRefSets=1):
912            'Write reflection statistics'
913            WriteCIFitem('_reflns_number_total', str(refcount))
914            if hklmin is not None and nRefSets == 1: # hkl range has no meaning with multiple phases
915                WriteCIFitem('_reflns_limit_h_min', str(int(hklmin[0])))
916                WriteCIFitem('_reflns_limit_h_max', str(int(hklmax[0])))
917                WriteCIFitem('_reflns_limit_k_min', str(int(hklmin[1])))
918                WriteCIFitem('_reflns_limit_k_max', str(int(hklmax[1])))
919                WriteCIFitem('_reflns_limit_l_min', str(int(hklmin[2])))
920                WriteCIFitem('_reflns_limit_l_max', str(int(hklmax[2])))
921            if hklmin is not None:
922                WriteCIFitem('_reflns_d_resolution_low  ', G2mth.ValEsd(dmax,-0.009))
923                WriteCIFitem('_reflns_d_resolution_high ', G2mth.ValEsd(dmin,-0.009))
924
925        def WritePowderData(histlbl):
926            'Write out the selected powder diffraction histogram info'
927            histblk = self.Histograms[histlbl]
928            inst = histblk['Instrument Parameters'][0]
929            hId = histblk['hId']
930            pfx = ':' + str(hId) + ':'
931           
932            if 'Lam1' in inst:
933                ratio = self.parmDict.get('I(L2)/I(L1)',inst['I(L2)/I(L1)'][1])
934                sratio = self.sigDict.get('I(L2)/I(L1)',-0.0009)
935                lam1 = self.parmDict.get('Lam1',inst['Lam1'][1])
936                slam1 = self.sigDict.get('Lam1',-0.00009)
937                lam2 = self.parmDict.get('Lam2',inst['Lam2'][1])
938                slam2 = self.sigDict.get('Lam2',-0.00009)
939                # always assume Ka1 & Ka2 if two wavelengths are present
940                WriteCIFitem('_diffrn_radiation_type','K\\a~1,2~')
941                WriteCIFitem('loop_' + 
942                             '\n   _diffrn_radiation_wavelength' +
943                             '\n   _diffrn_radiation_wavelength_wt' + 
944                             '\n   _diffrn_radiation_wavelength_id')
945                WriteCIFitem('  ' + PutInCol(G2mth.ValEsd(lam1,slam1),15)+
946                             PutInCol('1.0',15) + 
947                             PutInCol('1',5))
948                WriteCIFitem('  ' + PutInCol(G2mth.ValEsd(lam2,slam2),15)+
949                             PutInCol(G2mth.ValEsd(ratio,sratio),15)+
950                             PutInCol('2',5))               
951            elif 'Lam' in inst:
952                lam1 = self.parmDict.get('Lam',inst['Lam'][1])
953                slam1 = self.sigDict.get('Lam',-0.00009)
954                WriteCIFitem('_diffrn_radiation_wavelength',G2mth.ValEsd(lam1,slam1))
955
956            if not oneblock:
957                if not phasebyhistDict.get(histlbl):
958                    WriteCIFitem('\n# No phases associated with this data set')
959                else:
960                    WriteCIFitem('\n# PHASE TABLE')
961                    WriteCIFitem('loop_' +
962                                 '\n   _pd_phase_id' + 
963                                 '\n   _pd_phase_block_id' + 
964                                 '\n   _pd_phase_mass_%')
965                    wtFrSum = 0.
966                    for phasenam in phasebyhistDict.get(histlbl):
967                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
968                        General = self.Phases[phasenam]['General']
969                        wtFrSum += hapData['Scale'][0]*General['Mass']
970
971                    for phasenam in phasebyhistDict.get(histlbl):
972                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
973                        General = self.Phases[phasenam]['General']
974                        wtFr = hapData['Scale'][0]*General['Mass']/wtFrSum
975                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
976                        if pfx+'Scale' in self.sigDict:
977                            sig = self.sigDict[pfx+'Scale']*wtFr/hapData['Scale'][0]
978                        else:
979                            sig = -0.0001
980                        WriteCIFitem(
981                            '  '+
982                            str(self.Phases[phasenam]['pId']) +
983                            '  '+datablockidDict[phasenam]+
984                            '  '+G2mth.ValEsd(wtFr,sig)
985                            )
986                    WriteCIFitem('loop_' +
987                                 '\n   _gsas_proc_phase_R_F_factor' +
988                                 '\n   _gsas_proc_phase_R_Fsqd_factor' +
989                                 '\n   _gsas_proc_phase_id' +
990                                 '\n   _gsas_proc_phase_block_id')
991                    for phasenam in phasebyhistDict.get(histlbl):
992                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
993                        WriteCIFitem(
994                            '  '+
995                            '  '+G2mth.ValEsd(histblk[pfx+'Rf']/100.,-.00009) +
996                            '  '+G2mth.ValEsd(histblk[pfx+'Rf^2']/100.,-.00009)+
997                            '  '+str(self.Phases[phasenam]['pId'])+
998                            '  '+datablockidDict[phasenam]
999                            )
1000            else:
1001                # single phase in this histogram
1002                pfx = '0:'+str(hId)+':'
1003                WriteCIFitem('_refine_ls_R_F_factor      ','%.5f'%(histblk[pfx+'Rf']/100.))
1004                WriteCIFitem('_refine_ls_R_Fsqd_factor   ','%.5f'%(histblk[pfx+'Rf^2']/100.))
1005               
1006            WriteCIFitem('_pd_proc_ls_prof_R_factor   ','%.5f'%(histblk['R']/100.))
1007            WriteCIFitem('_pd_proc_ls_prof_wR_factor  ','%.5f'%(histblk['wR']/100.))
1008            WriteCIFitem('_gsas_proc_ls_prof_R_B_factor ','%.5f'%(histblk['Rb']/100.))
1009            WriteCIFitem('_gsas_proc_ls_prof_wR_B_factor','%.5f'%(histblk['wRb']/100.))
1010            WriteCIFitem('_pd_proc_ls_prof_wR_expected','%.5f'%(histblk['wRmin']/100.))
1011
1012            if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
1013                WriteCIFitem('_diffrn_radiation_probe','x-ray')
1014                pola = histblk['Instrument Parameters'][0].get('Polariz.')
1015                if pola:
1016                    pfx = ':' + str(hId) + ':'
1017                    sig = self.sigDict.get(pfx+'Polariz.',-0.0009)
1018                    txt = G2mth.ValEsd(pola[1],sig)
1019                    WriteCIFitem('_diffrn_radiation_polarisn_ratio',txt)
1020            elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
1021                WriteCIFitem('_diffrn_radiation_probe','neutron')
1022            if 'T' in inst['Type'][0]:
1023                txt = G2mth.ValEsd(inst['2-theta'][0],-0.009)
1024                WriteCIFitem('_pd_meas_2theta_fixed',txt)
1025
1026            # TODO: this will need help from Bob
1027            #if not oneblock:
1028            #WriteCIFitem('\n# SCATTERING FACTOR INFO')
1029            #WriteCIFitem('loop_  _atom_type_symbol')
1030            #if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
1031            #    WriteCIFitem('      _atom_type_scat_dispersion_real')
1032            #    WriteCIFitem('      _atom_type_scat_dispersion_imag')
1033            #    for lbl in ('a1','a2','a3', 'a4', 'b1', 'b2', 'b3', 'b4', 'c'):
1034            #        WriteCIFitem('      _atom_type_scat_Cromer_Mann_'+lbl)
1035            #elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
1036            #    WriteCIFitem('      _atom_type_scat_length_neutron')
1037            #WriteCIFitem('      _atom_type_scat_source')
1038
1039            WriteCIFitem('_pd_proc_ls_background_function',FormatBackground(histblk['Background'],histblk['hId']))
1040
1041            # TODO: this will need help from Bob
1042            #WriteCIFitem('_exptl_absorpt_process_details','?')
1043            #WriteCIFitem('_exptl_absorpt_correction_T_min','?')
1044            #WriteCIFitem('_exptl_absorpt_correction_T_max','?')
1045            #C extinction
1046            #WRITE(IUCIF,'(A)') '# Extinction correction'
1047            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_min',TEXT(1:10))
1048            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_max',TEXT(11:20))
1049
1050            if not oneblock:                 # instrumental profile terms go here
1051                WriteCIFitem('_pd_proc_ls_profile_function', 
1052                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1053
1054            #refprx = '_refln.' # mm
1055            refprx = '_refln_' # normal
1056            # data collection parameters for the powder dataset
1057           
1058            temperature = histblk['Sample Parameters'].get('Temperature') # G2 uses K
1059            if not temperature:
1060                T = '?'
1061            else:
1062                T = G2mth.ValEsd(temperature,-0.009,True) # CIF uses K
1063            WriteCIFitem('_diffrn_ambient_temperature',T)
1064
1065            pressure = histblk['Sample Parameters'].get('Pressure') #G2 uses mega-Pascal
1066            if not pressure:
1067                P = '?'
1068            else:
1069                P = G2mth.ValEsd(pressure*1000,-0.09,True) # CIF uses kilopascal
1070            WriteCIFitem('_diffrn_ambient_pressure',P)
1071
1072            WriteCIFitem('\n# STRUCTURE FACTOR TABLE')           
1073            # compute maximum intensity reflection
1074            Imax = 0
1075            for phasenam in histblk['Reflection Lists']:
1076                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1077                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
1078                I100 = scale*refList.T[8]*refList.T[11]
1079                #Icorr = np.array([refl[13] for refl in histblk['Reflection Lists'][phasenam]])[0]
1080                #FO2 = np.array([refl[8] for refl in histblk['Reflection Lists'][phasenam]])
1081                #I100 = scale*FO2*Icorr
1082                Imax = max(Imax,max(I100))
1083
1084            WriteCIFitem('loop_')
1085            if len(histblk['Reflection Lists'].keys()) > 1:
1086                WriteCIFitem('   _pd_refln_phase_id')
1087            WriteCIFitem('   ' + refprx + 'index_h' + 
1088                         '\n   ' + refprx + 'index_k' + 
1089                         '\n   ' + refprx + 'index_l' + 
1090                         '\n   ' + refprx + 'F_squared_meas' + 
1091                         '\n   ' + refprx + 'F_squared_calc' + 
1092                         '\n   ' + refprx + 'phase_calc' + 
1093                         '\n   _pd_refln_d_spacing')
1094            if Imax > 0:
1095                WriteCIFitem('   _gsas_i100_meas')
1096
1097            refcount = 0
1098            hklmin = None
1099            hklmax = None
1100            dmax = None
1101            dmin = None
1102            for phasenam in histblk['Reflection Lists']:
1103                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1104                phaseid = self.Phases[phasenam]['pId']
1105                refcount += len(histblk['Reflection Lists'][phasenam]['RefList'])
1106                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
1107                I100 = scale*refList.T[8]*refList.T[11]
1108                for j,ref in enumerate(histblk['Reflection Lists'][phasenam]['RefList']):
1109                    if DEBUG:
1110                        print('DEBUG: skipping reflection list')
1111                        break
1112                    if hklmin is None:
1113                        hklmin = ref[0:3]
1114                        hklmax = ref[0:3]
1115                        dmax = dmin = ref[4]
1116                    if len(histblk['Reflection Lists'].keys()) > 1:
1117                        s = PutInCol(phaseid,2)
1118                    else:
1119                        s = ""
1120                    for i,hkl in enumerate(ref[0:3]):
1121                        hklmax[i] = max(hkl,hklmax[i])
1122                        hklmin[i] = min(hkl,hklmin[i])
1123                        s += PutInCol(int(hkl),4)
1124                    for I in ref[8:10]:
1125                        s += PutInCol(G2mth.ValEsd(I,-0.0009),10)
1126                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1127                    dmax = max(dmax,ref[4])
1128                    dmin = min(dmin,ref[4])
1129                    s += PutInCol(G2mth.ValEsd(ref[4],-0.009),8)
1130                    if Imax > 0:
1131                        s += PutInCol(G2mth.ValEsd(100.*I100[j]/Imax,-0.09),6)
1132                    WriteCIFitem("  "+s)
1133
1134            WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,len(histblk['Reflection Lists']))
1135            WriteCIFitem('\n# POWDER DATA TABLE')
1136            # is data fixed step? If the step varies by <0.01% treat as fixed step
1137            steps = histblk['Data'][0][1:] - histblk['Data'][0][:-1]
1138            if abs(max(steps)-min(steps)) > abs(max(steps))/10000.:
1139                fixedstep = False
1140            else:
1141                fixedstep = True
1142
1143            zero = None
1144            if fixedstep and 'T' not in inst['Type'][0]: # and not TOF
1145                WriteCIFitem('_pd_meas_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0],-0.00009))
1146                WriteCIFitem('_pd_meas_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1],-0.00009))
1147                WriteCIFitem('_pd_meas_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1148                # zero correct, if defined
1149                zerolst = histblk['Instrument Parameters'][0].get('Zero')
1150                if zerolst: zero = zerolst[1]
1151                zero = self.parmDict.get('Zero',zero)
1152                if zero:
1153                    WriteCIFitem('_pd_proc_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0]-zero,-0.00009))
1154                    WriteCIFitem('_pd_proc_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1]-zero,-0.00009))
1155                    WriteCIFitem('_pd_proc_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1156               
1157            if zero:
1158                WriteCIFitem('_pd_proc_number_of_points', str(len(histblk['Data'][0])))
1159            else:
1160                WriteCIFitem('_pd_meas_number_of_points', str(len(histblk['Data'][0])))
1161            WriteCIFitem('\nloop_')
1162            #            WriteCIFitem('   _pd_proc_d_spacing') # need easy way to get this
1163            if not fixedstep:
1164                if zero:
1165                    WriteCIFitem('   _pd_proc_2theta_corrected')
1166                elif 'T' in inst['Type'][0]: # and not TOF
1167                    WriteCIFitem('   _pd_meas_time_of_flight')
1168                else:
1169                    WriteCIFitem('   _pd_meas_2theta_scan')
1170            # at least for now, always report weights.
1171            #if countsdata:
1172            #    WriteCIFitem('   _pd_meas_counts_total')
1173            #else:
1174            WriteCIFitem('   _pd_meas_intensity_total')
1175            WriteCIFitem('   _pd_calc_intensity_total')
1176            WriteCIFitem('   _pd_proc_intensity_bkg_calc')
1177            WriteCIFitem('   _pd_proc_ls_weight')
1178            maxY = max(histblk['Data'][1].max(),histblk['Data'][3].max())
1179            if maxY < 0: maxY *= -10 # this should never happen, but...
1180            ndec = max(0,10-int(np.log10(maxY))-1) # 10 sig figs should be enough
1181            maxSU = histblk['Data'][2].max()
1182            if maxSU < 0: maxSU *= -1 # this should never happen, but...
1183            ndecSU = max(0,8-int(np.log10(maxSU))-1) # 8 sig figs should be enough
1184            lowlim,highlim = histblk['Limits'][1]
1185
1186            if DEBUG:
1187                print('DEBUG: skipping profile list')
1188            else:   
1189                for x,yobs,yw,ycalc,ybkg in zip(histblk['Data'][0],
1190                                                histblk['Data'][1],
1191                                                histblk['Data'][2],
1192                                                histblk['Data'][3],
1193                                                histblk['Data'][4]):
1194                    if lowlim <= x <= highlim:
1195                        pass
1196                    else:
1197                        yw = 0.0 # show the point is not in use
1198   
1199                    if fixedstep:
1200                        s = ""
1201                    elif zero:
1202                        s = PutInCol(G2mth.ValEsd(x-zero,-0.00009),10)
1203                    else:
1204                        s = PutInCol(G2mth.ValEsd(x,-0.00009),10)
1205                    s += PutInCol(Yfmt(ndec,yobs),12)
1206                    s += PutInCol(Yfmt(ndec,ycalc),12)
1207                    s += PutInCol(Yfmt(ndec,ybkg),11)
1208                    s += PutInCol(Yfmt(ndecSU,yw),9)
1209                    WriteCIFitem("  "+s)
1210
1211        def WriteSingleXtalData(histlbl):
1212            'Write out the selected single crystal histogram info'
1213            histblk = self.Histograms[histlbl]
1214
1215            #refprx = '_refln.' # mm
1216            refprx = '_refln_' # normal
1217
1218            WriteCIFitem('\n# STRUCTURE FACTOR TABLE')           
1219            WriteCIFitem('loop_' + 
1220                         '\n   ' + refprx + 'index_h' + 
1221                         '\n   ' + refprx + 'index_k' + 
1222                         '\n   ' + refprx + 'index_l' +
1223                         '\n   ' + refprx + 'F_squared_meas' + 
1224                         '\n   ' + refprx + 'F_squared_sigma' + 
1225                         '\n   ' + refprx + 'F_squared_calc' + 
1226                         '\n   ' + refprx + 'phase_calc'
1227                         )
1228
1229            hklmin = None
1230            hklmax = None
1231            dmax = None
1232            dmin = None
1233            refcount = len(histblk['Data']['RefList'])
1234            for ref in histblk['Data']['RefList']:
1235                if ref[3] <= 0:      #skip user rejected reflections (mul <= 0)
1236                    continue
1237                s = "  "
1238                if hklmin is None:
1239                    hklmin = ref[0:3]
1240                    hklmax = ref[0:3]
1241                    dmax = dmin = ref[4]
1242                for i,hkl in enumerate(ref[0:3]):
1243                    hklmax[i] = max(hkl,hklmax[i])
1244                    hklmin[i] = min(hkl,hklmin[i])
1245                    s += PutInCol(int(hkl),4)
1246                if ref[5] == 0.0:
1247                    s += PutInCol(G2mth.ValEsd(ref[8],0),12)
1248                    s += PutInCol('.',10)
1249                    s += PutInCol(G2mth.ValEsd(ref[9],0),12)
1250                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1251                else:
1252                    sig = ref[6] * ref[8] / ref[5]
1253                    s += PutInCol(G2mth.ValEsd(ref[8],-abs(sig/10)),12)
1254                    s += PutInCol(G2mth.ValEsd(sig,-abs(sig)/10.),10)
1255                    s += PutInCol(G2mth.ValEsd(ref[9],-abs(sig/10)),12)
1256                s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1257                dmax = max(dmax,ref[4])
1258                dmin = min(dmin,ref[4])
1259                WriteCIFitem(s)
1260            if not self.quickmode: # statistics only in a full CIF
1261                WriteReflStat(refcount,hklmin,hklmax,dmin,dmax)
1262                hId = histblk['hId']
1263                hfx = '0:'+str(hId)+':'
1264                phfx = '%d:%d:'%(0,hId)
1265                extType,extModel,extParms = self.Phases[phasenam]['Histograms'][histlbl]['Extinction']
1266                if extModel != 'None':
1267                    WriteCIFitem('# Extinction scaled by 1.e5')
1268                    WriteCIFitem('_refine_ls_extinction_method','Becker-Coppens %s %s'%(extModel,extType))
1269                    sig = -1.e-3
1270                    if extModel == 'Primary':
1271                        parm = extParms['Ep'][0]*1.e5
1272                        if extParms['Ep'][1]:
1273                            sig = self.sigDict[phfx+'Ep']*1.e5
1274                        text = G2mth.ValEsd(parm,sig)
1275                    elif extModel == 'Secondary Type I':
1276                        parm = extParms['Eg'][0]*1.e5
1277                        if extParms['Eg'][1]:
1278                            sig = self.sigDict[phfx+'Eg']*1.e5
1279                        text = G2mth.ValEsd(parm,sig)
1280                    elif extModel == 'Secondary Type II':
1281                        parm = extParms['Es'][0]*1.e5
1282                        if extParms['Es'][1]:
1283                            sig = self.sigDict[phfx+'Es']*1.e5
1284                        text = G2mth.ValEsd(parm,sig)
1285                    elif extModel == 'Secondary Type I & II':
1286                        parm = extParms['Eg'][0]*1.e5
1287                        if extParms['Es'][1]:
1288                            sig = self.sigDict[phfx+'Es']*1.e5
1289                        text = G2mth.ValEsd(parm,sig)
1290                        sig = -1.0e-3
1291                        parm = extParms['Es'][0]*1.e5
1292                        if extParms['Es'][1]:
1293                            sig = self.sigDict[phfx+'Es']*1.e5
1294                        text += G2mth.ValEsd(parm,sig)
1295                    WriteCIFitem('_refine_ls_extinction_coef',text)
1296                    WriteCIFitem('_refine_ls_extinction_expression','Becker & Coppens (1974). Acta Cryst. A30, 129-147')
1297
1298                WriteCIFitem('_refine_ls_wR_factor_gt    ','%.4f'%(histblk['wR']/100.))
1299                WriteCIFitem('_refine_ls_R_factor_gt     ','%.4f'%(histblk[hfx+'Rf']/100.))
1300                WriteCIFitem('_refine_ls_R_Fsqd_factor   ','%.4f'%(histblk[hfx+'Rf^2']/100.))
1301        def EditAuthor(event=None):
1302            'dialog to edit the CIF author info'
1303            'Edit the CIF author name'
1304            dlg = G2G.SingleStringDialog(self.G2frame,
1305                                          'Get CIF Author',
1306                                          'Provide CIF Author name (Last, First)',
1307                                          value=self.author)
1308            if not dlg.Show():
1309                dlg.Destroy()
1310                return False  # cancel was pressed
1311            self.author = dlg.GetValue()
1312            self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
1313            dlg.Destroy()
1314            try:
1315                self.OverallParms['Controls']["Author"] = self.author # save for future
1316            except KeyError:
1317                pass
1318            return True
1319
1320        def EditInstNames(event=None):
1321            'Provide a dialog for editing instrument names'
1322            dictlist = []
1323            keylist = []
1324            lbllist = []
1325            for hist in self.Histograms:
1326                if hist.startswith("PWDR"): 
1327                    key2 = "Sample Parameters"
1328                    d = self.Histograms[hist][key2]
1329                elif hist.startswith("HKLF"): 
1330                    key2 = "Instrument Parameters"
1331                    d = self.Histograms[hist][key2][0]
1332                   
1333                lbllist.append(hist)
1334                dictlist.append(d)
1335                keylist.append('InstrName')
1336                instrname = d.get('InstrName')
1337                if instrname is None:
1338                    d['InstrName'] = ''
1339            return G2G.CallScrolledMultiEditor(
1340                self.G2frame,dictlist,keylist,
1341                prelbl=range(1,len(dictlist)+1),
1342                postlbl=lbllist,
1343                title='Instrument names',
1344                header="Edit instrument names. Note that a non-blank\nname is required for all histograms",
1345                CopyButton=True,ASCIIonly=True)
1346           
1347        def EditRanges(event):
1348            '''Edit the bond distance/angle search range; phase is determined from
1349            a pointer placed in the button object (.phasedict) that references the
1350            phase dictionary
1351            '''
1352            but = event.GetEventObject()
1353            phasedict = but.phasedict
1354            dlg = G2gd.DisAglDialog(
1355                self.G2frame,
1356                phasedict['General']['DisAglCtls'], # edited
1357                phasedict['General'], # defaults
1358                )
1359            if dlg.ShowModal() == wx.ID_OK:
1360                phasedict['General']['DisAglCtls'] = dlg.GetData()
1361            dlg.Destroy()
1362           
1363        def EditCIFDefaults():
1364            '''Fills the CIF Defaults window with controls for editing various CIF export
1365            parameters (mostly related to templates).
1366            '''
1367            self.cifdefs.DestroyChildren()
1368            self.cifdefs.SetTitle('Edit CIF settings')
1369            vbox = wx.BoxSizer(wx.VERTICAL)
1370            vbox.Add(wx.StaticText(self.cifdefs, wx.ID_ANY,'Creating file '+self.filename))
1371            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit CIF Author')
1372            but.Bind(wx.EVT_BUTTON,EditAuthor)
1373            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1374            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit Instrument Name(s)')
1375            but.Bind(wx.EVT_BUTTON,EditInstNames)
1376            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1377            cpnl = wxscroll.ScrolledPanel(self.cifdefs,size=(300,300))
1378            cbox = wx.BoxSizer(wx.VERTICAL)
1379            G2G.HorizontalLine(cbox,cpnl)         
1380            cbox.Add(
1381                CIFtemplateSelect(self.cifdefs,
1382                                  cpnl,'publ',self.OverallParms['Controls'],
1383                                  EditCIFDefaults,
1384                                  "Publication (overall) template",
1385                                  ),
1386                0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1387            for phasenam in sorted(self.Phases.keys()):
1388                G2G.HorizontalLine(cbox,cpnl)         
1389                title = 'Phase '+phasenam
1390                phasedict = self.Phases[phasenam] # pointer to current phase info           
1391                cbox.Add(
1392                    CIFtemplateSelect(self.cifdefs,
1393                                      cpnl,'phase',phasedict['General'],
1394                                      EditCIFDefaults,
1395                                      title,
1396                                      phasenam),
1397                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1398                cpnl.SetSizer(cbox)
1399                if phasedict['General']['Type'] == 'nuclear': 
1400                    but = wx.Button(cpnl, wx.ID_ANY,'Edit distance/angle ranges')
1401                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1402                    cbox.Add((-1,2))
1403                    but.phasedict = self.Phases[phasenam]  # set a pointer to current phase info
1404                    but.Bind(wx.EVT_BUTTON,EditRanges)     # phase bond/angle ranges
1405                    but = wx.Button(cpnl, wx.ID_ANY,'Set distance/angle publication flags')
1406                    but.phase = phasenam  # set a pointer to current phase info     
1407                    but.Bind(wx.EVT_BUTTON,SelectDisAglFlags)     # phase bond/angle ranges
1408                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1409                cbox.Add((-1,2))
1410            for i in sorted(self.powderDict.keys()):
1411                G2G.HorizontalLine(cbox,cpnl)         
1412                hist = self.powderDict[i]
1413                histblk = self.Histograms[hist]
1414                title = 'Powder dataset '+hist[5:]
1415                cbox.Add(
1416                    CIFtemplateSelect(self.cifdefs,
1417                                      cpnl,'powder',histblk["Sample Parameters"],
1418                                      EditCIFDefaults,
1419                                      title,
1420                                      histblk["Sample Parameters"]['InstrName']),
1421                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1422            for i in sorted(self.xtalDict.keys()):
1423                G2G.HorizontalLine(cbox,cpnl)         
1424                hist = self.xtalDict[i]
1425                histblk = self.Histograms[hist]
1426                title = 'Single Xtal dataset '+hist[5:]
1427                cbox.Add(
1428                    CIFtemplateSelect(self.cifdefs,
1429                                      cpnl,'single',histblk["Instrument Parameters"][0],
1430                                      EditCIFDefaults,
1431                                      title,
1432                                      histblk["Instrument Parameters"][0]['InstrName']),
1433                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1434            cpnl.SetSizer(cbox)
1435            cpnl.SetAutoLayout(1)
1436            cpnl.SetupScrolling()
1437            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1438            cpnl.Layout()
1439
1440            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1441            btnsizer = wx.StdDialogButtonSizer()
1442            btn = wx.Button(self.cifdefs, wx.ID_OK, "Create CIF")
1443            btn.SetDefault()
1444            btnsizer.AddButton(btn)
1445            btn = wx.Button(self.cifdefs, wx.ID_CANCEL)
1446            btnsizer.AddButton(btn)
1447            btnsizer.Realize()
1448            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1449            self.cifdefs.SetSizer(vbox)
1450            vbox.Fit(self.cifdefs)
1451            self.cifdefs.Layout()
1452           
1453        def OnToggleButton(event):
1454            'Respond to press of ToggleButton in SelectDisAglFlags'
1455            but = event.GetEventObject()
1456            if but.GetValue():                   
1457                but.DisAglSel[but.key] = True
1458            else:
1459                try:
1460                    del but.DisAglSel[but.key]
1461                except KeyError:
1462                    pass
1463        def keepTrue(event):
1464            event.GetEventObject().SetValue(True)
1465        def keepFalse(event):
1466            event.GetEventObject().SetValue(False)
1467               
1468        def SelectDisAglFlags(event):
1469            'Select Distance/Angle use flags for the selected phase'
1470            phasenam = event.GetEventObject().phase
1471            phasedict = self.Phases[phasenam]
1472            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(phasedict['General']['SGData'])
1473            generalData = phasedict['General']
1474            # create a dict for storing Pub flag for bonds/angles, if needed
1475            if phasedict['General'].get("DisAglHideFlag") is None:
1476                phasedict['General']["DisAglHideFlag"] = {}
1477            DisAngSel = phasedict['General']["DisAglHideFlag"]
1478
1479            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1480            cn = ct-1
1481            cfrac = cx+3
1482            DisAglData = {}
1483            # create a list of atoms, but skip atoms with zero occupancy
1484            xyz = []
1485            fpfx = str(phasedict['pId'])+'::Afrac:'       
1486            for i,atom in enumerate(phasedict['Atoms']):
1487                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
1488                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
1489            if 'DisAglCtls' not in generalData:
1490                # should not be used, since DisAglDialog should be called
1491                # for all phases before getting here
1492                dlg = G2gd.DisAglDialog(
1493                    self.cifdefs,
1494                    {},
1495                    generalData)
1496                if dlg.ShowModal() == wx.ID_OK:
1497                    generalData['DisAglCtls'] = dlg.GetData()
1498                else:
1499                    dlg.Destroy()
1500                    return
1501                dlg.Destroy()
1502            dlg = wx.Dialog(
1503                self.G2frame,
1504                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
1505            vbox = wx.BoxSizer(wx.VERTICAL)
1506            txt = wx.StaticText(dlg,wx.ID_ANY,'Searching distances for phase '+phasenam
1507                                +'\nPlease wait...')
1508            vbox.Add(txt,0,wx.ALL|wx.EXPAND)
1509            dlg.SetSizer(vbox)
1510            dlg.CenterOnParent()
1511            dlg.Show() # post "please wait"
1512            wx.BeginBusyCursor() # and change cursor
1513
1514            DisAglData['OrigAtoms'] = xyz
1515            DisAglData['TargAtoms'] = xyz
1516            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
1517                generalData['SGData'])
1518
1519#            xpandSGdata = generalData['SGData'].copy()
1520#            xpandSGdata.update({'SGOps':symOpList,
1521#                                'SGInv':False,
1522#                                'SGLatt':'P',
1523#                                'SGCen':np.array([[0, 0, 0]]),})
1524#            DisAglData['SGData'] = xpandSGdata
1525            DisAglData['SGData'] = generalData['SGData'].copy()
1526
1527            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
1528            if 'pId' in phasedict:
1529                DisAglData['pId'] = phasedict['pId']
1530                DisAglData['covData'] = self.OverallParms['Covariance']
1531            try:
1532                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
1533                    generalData['DisAglCtls'],
1534                    DisAglData)
1535            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
1536                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
1537            wx.EndBusyCursor()
1538            txt.SetLabel('Set publication flags for distances and angles in\nphase '+phasenam)
1539            vbox.Add((5,5)) 
1540            vbox.Add(wx.StaticText(dlg,wx.ID_ANY,
1541                                   'The default is to flag all distances and angles as to be'+
1542                                   '\npublished. Change this by pressing appropriate buttons.'),
1543                     0,wx.ALL|wx.EXPAND)
1544            hbox = wx.BoxSizer(wx.HORIZONTAL)
1545            vbox.Add(hbox)
1546            hbox.Add(wx.StaticText(dlg,wx.ID_ANY,'Button appearance: '))
1547            but = wx.ToggleButton(dlg,wx.ID_ANY,'Publish')
1548            but.Bind(wx.EVT_TOGGLEBUTTON,keepFalse)
1549            hbox.Add(but)
1550            but = wx.ToggleButton(dlg,wx.ID_ANY,"Don't publish")
1551            but.Bind(wx.EVT_TOGGLEBUTTON,keepTrue)
1552            hbox.Add(but)
1553            but.SetValue(True)
1554            G2G.HorizontalLine(vbox,dlg)         
1555           
1556            cpnl = wxscroll.ScrolledPanel(dlg,size=(400,300))
1557            cbox = wx.BoxSizer(wx.VERTICAL)
1558            for c in sorted(DistArray):
1559                karr = []
1560                UsedCols = {}
1561                cbox.Add(wx.StaticText(cpnl,wx.ID_ANY,
1562                                   'distances to/angles around atom '+AtomLabels[c]))
1563                #dbox = wx.GridBagSizer(hgap=5)
1564                dbox = wx.GridBagSizer()
1565                for i,D in enumerate(DistArray[c]):
1566                    karr.append(tuple(D[0:3]))
1567                    val = "{:.2f}".format(D[3])
1568                    sym = " [{:d} {:d} {:d}]".format(*D[1]) + " #{:d}".format(D[2])
1569                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
1570                             (i+1,0)
1571                             )                   
1572                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,sym),
1573                             (i+1,1)
1574                             )
1575                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1576                    but.key = (c,karr[-1])
1577                    but.DisAglSel = DisAngSel
1578                    if DisAngSel.get(but.key): but.SetValue(True)
1579                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1580                    dbox.Add(but,(i+1,2),border=1)
1581                for i,D in enumerate(AngArray[c]):
1582                    val = "{:.1f}".format(D[2][0])
1583                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1584                    but.key = (karr[D[0]],c,karr[D[1]])
1585                    but.DisAglSel = DisAngSel
1586                    if DisAngSel.get(but.key): but.SetValue(True)
1587                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1588                    dbox.Add(but,(D[0]+1,D[1]+3),border=1)
1589                    UsedCols[D[1]+3] = True
1590                for i,D in enumerate(DistArray[c][:-1]): # label columns that are used
1591                    if UsedCols.get(i+3):
1592                        dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
1593                                 (0,i+3),
1594                                 flag=wx.ALIGN_CENTER
1595                                 )
1596                dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,'distance'),
1597                                 (0,2),
1598                                 flag=wx.ALIGN_CENTER
1599                                 )
1600                cbox.Add(dbox)
1601                G2G.HorizontalLine(cbox,cpnl)         
1602            cpnl.SetSizer(cbox)
1603            cpnl.SetAutoLayout(1)
1604            cpnl.SetupScrolling()
1605            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1606            cpnl.Layout()
1607
1608            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1609
1610            btnsizer = wx.StdDialogButtonSizer()
1611            btn = wx.Button(dlg, wx.ID_OK, "Done")
1612            btn.SetDefault()
1613            btnsizer.AddButton(btn)
1614            btnsizer.Realize()
1615            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1616            dlg.SetSizer(vbox)
1617            vbox.Fit(dlg)
1618            dlg.Layout()
1619           
1620            dlg.CenterOnParent()
1621            dlg.ShowModal()
1622           
1623#=================================================================================
1624#===== end of function definitions for _Exporter =================================
1625#=================================================================================
1626        # make sure required information is present
1627        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
1628        if not self.CIFname: # Get a name for the CIF. If not defined, use the GPX name (save, if that is needed).
1629            if not self.G2frame.GSASprojectfile:
1630                self.G2frame.OnFileSaveas(None)
1631            if not self.G2frame.GSASprojectfile: return
1632            self.CIFname = os.path.splitext(
1633                os.path.split(self.G2frame.GSASprojectfile)[1]
1634                )[0]
1635            self.CIFname = self.CIFname.replace(' ','')
1636        # replace non-ASCII characters in CIFname with dots
1637        s = ''
1638        for c in self.CIFname:
1639            if ord(c) < 128:
1640                s += c
1641            else:
1642                s += '.'
1643        self.CIFname = s
1644        # load saved CIF author name
1645        try:
1646            self.author = self.OverallParms['Controls'].get("Author",'').strip()
1647        except KeyError:
1648            pass
1649        #=================================================================
1650        # write quick CIFs
1651        #=================================================================
1652        if phaseOnly: #====Phase only CIF ================================
1653            print('Writing CIF output to file '+self.filename)
1654            self.OpenFile()
1655            oneblock = True
1656            self.quickmode = True
1657            self.Write(' ')
1658            self.Write(70*'#')
1659            WriteCIFitem('data_'+self.CIFname)
1660            #phaseblk = self.Phases[phaseOnly] # pointer to current phase info
1661            # report the phase info
1662            WritePhaseInfo(phaseOnly)
1663            self.CloseFile()
1664            return
1665        elif histOnly: #====Histogram only CIF ================================
1666            print('Writing CIF output to file '+self.filename)
1667            self.OpenFile()
1668            hist = histOnly
1669            histname = histOnly.replace(' ','')
1670            oneblock = True
1671            self.quickmode = True
1672            self.ifHKLF = False
1673            self.ifPWDR = True
1674            self.Write(' ')
1675            self.Write(70*'#')
1676            #phasenam = self.Phases.keys()[0]
1677            WriteCIFitem('data_'+self.CIFname)
1678            #print 'phasenam',phasenam
1679            #phaseblk = self.Phases[phasenam] # pointer to current phase info
1680            #instnam = instnam.replace(' ','')
1681            #WriteCIFitem('_pd_block_id',
1682            #             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1683            #             str(self.shortauthorname) + "|" + instnam + '|' + histname)
1684            #WriteAudit()
1685            #writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
1686            #WriteOverall()
1687            #writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1688            # report the phase info
1689            #WritePhaseInfo(phasenam,hist)
1690            # preferred orientation
1691            #SH = FormatSH(phasenam)
1692            #MD = FormatHAPpo(phasenam)
1693            #if SH and MD:
1694            #    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1695            #elif SH or MD:
1696            #    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1697            #else:
1698            #    WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1699            # report profile, since one-block: include both histogram and phase info
1700            #WriteCIFitem('_pd_proc_ls_profile_function',
1701            #    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
1702            #        +'\n'+FormatPhaseProfile(phasenam))
1703            histblk = self.Histograms[hist]["Sample Parameters"]
1704            #writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
1705            WritePowderData(hist)
1706            self.CloseFile()
1707            return
1708        #===============================================================================
1709        # the export process for a full CIF starts here
1710        #===============================================================================
1711        self.InitExport(event)
1712        # load all of the tree into a set of dicts
1713        self.loadTree()
1714        # create a dict with refined values and their uncertainties
1715        self.loadParmDict()
1716        if self.ExportSelect('ask'): return
1717        #if self.ExportSelect('default'): return
1718        # Someday: get restraint & constraint info
1719        #restraintDict = self.OverallParms.get('Restraints',{})
1720        #for i in  self.OverallParms['Constraints']:
1721        #    print i
1722        #    for j in self.OverallParms['Constraints'][i]:
1723        #        print j
1724
1725        # is there anything to export?
1726        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
1727           self.G2frame.ErrorDialog(
1728               'Empty project',
1729               'Project does not contain any data or phases. Are they interconnected?')
1730           return
1731        self.quickmode = False # full CIF
1732        phasenam = None # include all phases
1733        # Will this require a multiblock CIF?
1734        if len(self.Phases) > 1:
1735            oneblock = False
1736        elif len(self.powderDict) + len(self.xtalDict) > 1:
1737            oneblock = False
1738        else: # one phase, one dataset, Full CIF
1739            oneblock = True
1740
1741        # check there is an instrument name for every histogram
1742        self.ifPWDR = False
1743        self.ifHKLF = False
1744        invalid = 0
1745        key3 = 'InstrName'
1746        for hist in self.Histograms:
1747            if hist.startswith("PWDR"):
1748                self.ifPWDR = True
1749                key2 = "Sample Parameters"
1750                d = self.Histograms[hist][key2]
1751            elif hist.startswith("HKLF"):
1752                self.ifHKLF = True
1753                key2 = "Instrument Parameters"
1754                d = self.Histograms[hist][key2][0]                   
1755            instrname = d.get(key3)
1756            if instrname is None:
1757                d[key3] = ''
1758                invalid += 1
1759            elif instrname.strip() == '':
1760                invalid += 1
1761        if invalid:
1762            msg = ""
1763            if invalid > 3: msg = (
1764                "\n\nNote: it may be faster to set the name for\n"
1765                "one histogram for each instrument and use the\n"
1766                "File/Copy option to duplicate the name"
1767                )
1768            if not EditInstNames(): return
1769           
1770        # check for a distance-angle range search range for each phase
1771        for phasenam in sorted(self.Phases.keys()):
1772            #i = self.Phases[phasenam]['pId']
1773            phasedict = self.Phases[phasenam] # pointer to current phase info           
1774            if 'DisAglCtls' not in phasedict['General']:
1775                dlg = G2gd.DisAglDialog(
1776                    self.G2frame,
1777                    {},
1778                    phasedict['General'])
1779                if dlg.ShowModal() == wx.ID_OK:
1780                    phasedict['General']['DisAglCtls'] = dlg.GetData()
1781                else:
1782                    dlg.Destroy()
1783                    return
1784                dlg.Destroy()
1785
1786        # check if temperature values & pressure are defaulted
1787        default = 0
1788        for hist in self.Histograms:
1789            if hist.startswith("PWDR"): 
1790                key2 = "Sample Parameters"
1791                T = self.Histograms[hist][key2].get('Temperature')
1792                if not T:
1793                    default += 1
1794                elif T == 300:
1795                    default += 1
1796                P = self.Histograms[hist][key2].get('Pressure')
1797                if not P:
1798                    default += 1
1799                elif P == 1:
1800                    default += 1
1801        if default > 0:
1802            dlg = wx.MessageDialog(
1803                self.G2frame,
1804                '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?',
1805                'Check T and P values',
1806                wx.OK|wx.CANCEL)
1807            ret = dlg.ShowModal()
1808            dlg.Destroy()
1809            if ret != wx.ID_OK: return
1810        if oneblock:
1811            # select a dataset to use (there should only be one set in one block,
1812            # but take whatever comes 1st)
1813            for hist in self.Histograms:
1814                histblk = self.Histograms[hist]
1815                if hist.startswith("PWDR"): 
1816                    instnam = histblk["Sample Parameters"]['InstrName']
1817                    break # ignore all but 1st data histogram
1818                elif hist.startswith("HKLF"): 
1819                    instnam = histblk["Instrument Parameters"][0]['InstrName']
1820                    break # ignore all but 1st data histogram
1821        # give the user a window to edit CIF contents
1822        if not self.author:
1823            if not EditAuthor(): return
1824        self.ValidateAscii([('Author name',self.author),]) # check for ASCII strings where needed, warn on problems
1825        self.cifdefs = wx.Dialog(
1826            self.G2frame,
1827            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
1828        EditCIFDefaults()
1829        self.cifdefs.CenterOnParent()
1830        if self.cifdefs.ShowModal() != wx.ID_OK:
1831            self.cifdefs.Destroy()
1832            return
1833        while self.ValidateAscii([('Author name',self.author),
1834                                  ]): # validate a few things as ASCII
1835            if self.cifdefs.ShowModal() != wx.ID_OK:
1836                self.cifdefs.Destroy()
1837                return
1838        self.cifdefs.Destroy()
1839        #======================================================================
1840        # Start writing the CIF - single block
1841        #======================================================================
1842        print('Writing CIF output to file '+self.filename+"...")
1843        self.OpenFile()
1844        if self.currentExportType == 'single' or self.currentExportType == 'powder':
1845            #======Data only CIF (powder/xtal) ====================================
1846            hist = self.histnam[0]
1847            self.CIFname = hist[5:40].replace(' ','')
1848            WriteCIFitem('data_'+self.CIFname)
1849            if hist.startswith("PWDR"):
1850                WritePowderData(hist)
1851            elif hist.startswith("HKLF"):
1852                WriteSingleXtalData(hist)
1853            else:
1854                print "should not happen"
1855        elif oneblock:
1856            #====Single block, data & phase CIF ===================================
1857            WriteCIFitem('data_'+self.CIFname)
1858            if phasenam is None: # if not already selected, select the first phase (should be one)
1859                phasenam = self.Phases.keys()[0]
1860            #print 'phasenam',phasenam
1861            #phaseblk = self.Phases[phasenam] # pointer to current phase info
1862            instnam = instnam.replace(' ','')
1863            WriteCIFitem('_pd_block_id',
1864                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1865                         str(self.shortauthorname) + "|" + instnam)
1866            WriteAudit()
1867            writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
1868            WriteOverall()
1869            writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1870            # report the phase info
1871            WritePhaseInfo(phasenam)
1872            if hist.startswith("PWDR"):
1873                # preferred orientation
1874                SH = FormatSH(phasenam)
1875                MD = FormatHAPpo(phasenam)
1876                if SH and MD:
1877                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1878                elif SH or MD:
1879                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1880                else:
1881                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1882                    # report profile, since one-block: include both histogram and phase info
1883                WriteCIFitem('_pd_proc_ls_profile_function',
1884                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
1885                    +'\n'+FormatPhaseProfile(phasenam))
1886                histblk = self.Histograms[hist]["Sample Parameters"]
1887                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
1888                WritePowderData(hist)
1889            elif hist.startswith("HKLF"):
1890                histprm = self.Histograms[hist]["Instrument Parameters"][0]
1891                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
1892                WriteSingleXtalData(hist)
1893        else:
1894            #=== multiblock: multiple phases and/or histograms ====================
1895            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
1896            dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
1897#                Size = dlg.GetSize()
1898#                Size = (int(Size[0]*3),Size[1]) # increase size along x
1899#                dlg.SetSize(Size)
1900            dlg.CenterOnParent()
1901
1902            # publication info
1903            step = 1
1904            dlg.Update(step,"Exporting overall section")
1905            WriteCIFitem('\ndata_'+self.CIFname+'_publ')
1906            WriteAudit()
1907            WriteCIFitem('_pd_block_id',
1908                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1909                         str(self.shortauthorname) + "|Overall")
1910            writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
1911            # ``template_publ.cif`` or a modified version
1912            # overall info
1913            WriteCIFitem('data_'+str(self.CIFname)+'_overall')
1914            WriteOverall()
1915            #============================================================
1916            WriteCIFitem('# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
1917            datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
1918            # loop over phase blocks
1919            if len(self.Phases) > 1:
1920                loopprefix = ''
1921                WriteCIFitem('loop_   _pd_phase_block_id')
1922            else:
1923                loopprefix = '_pd_phase_block_id'
1924
1925            for phasenam in sorted(self.Phases.keys()):
1926                i = self.Phases[phasenam]['pId']
1927                datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1928                             'phase_'+ str(i) + '|' + str(self.shortauthorname))
1929                WriteCIFitem(loopprefix,datablockidDict[phasenam])
1930            # loop over data blocks
1931            if len(self.powderDict) + len(self.xtalDict) > 1:
1932                loopprefix = ''
1933                WriteCIFitem('loop_   _pd_block_diffractogram_id')
1934            else:
1935                loopprefix = '_pd_block_diffractogram_id'
1936            for i in sorted(self.powderDict.keys()):
1937                hist = self.powderDict[i]
1938                histblk = self.Histograms[hist]
1939                instnam = histblk["Sample Parameters"]['InstrName']
1940                instnam = instnam.replace(' ','')
1941                j = histblk['hId']
1942                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1943                                         str(self.shortauthorname) + "|" +
1944                                         instnam + "_hist_"+str(j))
1945                WriteCIFitem(loopprefix,datablockidDict[hist])
1946            for i in sorted(self.xtalDict.keys()):
1947                hist = self.xtalDict[i]
1948                histblk = self.Histograms[hist]
1949                instnam = histblk["Instrument Parameters"][0]['InstrName']
1950                instnam = instnam.replace(' ','')
1951                i = histblk['hId']
1952                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1953                                         str(self.shortauthorname) + "|" +
1954                                         instnam + "_hist_"+str(i))
1955                WriteCIFitem(loopprefix,datablockidDict[hist])
1956            #============================================================
1957            # loop over phases, exporting them
1958            phasebyhistDict = {} # create a cross-reference to phases by histogram
1959            for j,phasenam in enumerate(sorted(self.Phases.keys())):
1960                step += 1
1961                dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
1962                i = self.Phases[phasenam]['pId']
1963                WriteCIFitem('\ndata_'+self.CIFname+"_phase_"+str(i))
1964                WriteCIFitem('# Information for phase '+str(i))
1965                WriteCIFitem('_pd_block_id',datablockidDict[phasenam])
1966                # report the phase
1967                writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1968                WritePhaseInfo(phasenam)
1969                # preferred orientation
1970                if self.ifPWDR:
1971                    SH = FormatSH(phasenam)
1972                    MD = FormatHAPpo(phasenam)
1973                    if SH and MD:
1974                        WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1975                    elif SH or MD:
1976                        WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1977                    else:
1978                        WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1979                # report sample profile terms
1980                PP = FormatPhaseProfile(phasenam)
1981                if PP:
1982                    WriteCIFitem('_pd_proc_ls_profile_function',PP)
1983
1984            #============================================================
1985            # loop over histograms, exporting them
1986            for i in sorted(self.powderDict.keys()):
1987                hist = self.powderDict[i]
1988                histblk = self.Histograms[hist]
1989                if hist.startswith("PWDR"): 
1990                    step += 1
1991                    dlg.Update(step,"Exporting "+hist.strip())
1992                    WriteCIFitem('\ndata_'+self.CIFname+"_pwd_"+str(i))
1993                    #instnam = histblk["Sample Parameters"]['InstrName']
1994                    # report instrumental profile terms
1995                    WriteCIFitem('_pd_proc_ls_profile_function',
1996                        FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1997                    WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
1998                    WriteCIFitem('_pd_block_id',datablockidDict[hist])
1999                    histprm = self.Histograms[hist]["Sample Parameters"]
2000                    writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
2001                    WritePowderData(hist)
2002            for i in sorted(self.xtalDict.keys()):
2003                hist = self.xtalDict[i]
2004                histblk = self.Histograms[hist]
2005                if hist.startswith("HKLF"): 
2006                    step += 1
2007                    dlg.Update(step,"Exporting "+hist.strip())
2008                    WriteCIFitem('\ndata_'+self.CIFname+"_sx_"+str(i))
2009                    #instnam = histblk["Instrument Parameters"][0]['InstrName']
2010                    WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
2011                    WriteCIFitem('_pd_block_id',datablockidDict[hist])
2012                    histprm = self.Histograms[hist]["Instrument Parameters"][0]
2013                    writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
2014                    WriteSingleXtalData(hist)
2015
2016            dlg.Destroy()
2017
2018        WriteCIFitem('#--' + 15*'eof--' + '#')
2019        self.CloseFile()
2020        print("...export completed")
2021        print('file '+self.fullpath)
2022        # end of CIF export
2023
2024class ExportProjectCIF(ExportCIF):
2025    '''Used to create a CIF of an entire project
2026
2027    :param wx.Frame G2frame: reference to main GSAS-II frame
2028    '''
2029    def __init__(self,G2frame):
2030        ExportCIF.__init__(self,
2031            G2frame=G2frame,
2032            formatName = 'Full CIF',
2033            extension='.cif',
2034            longFormatName = 'Export project as CIF'
2035            )
2036        self.exporttype = ['project']
2037       
2038    def Exporter(self,event=None):
2039        self._Exporter(event=event)
2040
2041    def Writer(self,hist,mode='w'):
2042        # set the project file name
2043        self.CIFname = os.path.splitext(
2044            os.path.split(self.G2frame.GSASprojectfile)[1]
2045            )[0]+'_'+hist
2046        self.CIFname = self.CIFname.replace(' ','')
2047        self.OpenFile(mode=mode)
2048        self._Exporter(histOnly=hist)
2049        if mode == 'w':
2050            print('CIF written to file '+self.fullpath)
2051        self.CloseFile()
2052       
2053class ExportPhaseCIF(ExportCIF):
2054    '''Used to create a simple CIF with one phase. Uses exact same code as
2055    :class:`ExportCIF` except that `phaseOnly` is set for the Exporter
2056    Shows up in menu as Quick CIF.
2057
2058    :param wx.Frame G2frame: reference to main GSAS-II frame
2059    '''
2060    def __init__(self,G2frame):
2061        ExportCIF.__init__(self,
2062            G2frame=G2frame,
2063            formatName = 'Quick CIF',
2064            extension='.cif',
2065            longFormatName = 'Export one phase in CIF'
2066            )
2067        self.exporttype = ['phase']
2068        # CIF-specific items
2069        self.author = ''
2070
2071    def Exporter(self,event=None):
2072        # get a phase and file name
2073        # the export process starts here
2074        self.InitExport(event)
2075        # load all of the tree into a set of dicts
2076        self.loadTree()
2077        # create a dict with refined values and their uncertainties
2078        self.loadParmDict()
2079        self.multiple = False
2080        self.currentExportType = 'phase'
2081        if self.ExportSelect('ask'): return
2082        self._Exporter(event=event,phaseOnly=self.phasenam[0])
2083
2084    def Writer(self,hist,phasenam,mode='w'):
2085        # set the project file name
2086        self.CIFname = os.path.splitext(
2087            os.path.split(self.G2frame.GSASprojectfile)[1]
2088            )[0]+'_'+phasenam+'_'+hist
2089        self.CIFname = self.CIFname.replace(' ','')
2090        self.OpenFile(mode=mode)
2091        self._Exporter(phaseOnly=phasenam)
2092        self.CloseFile()
2093
2094class ExportPwdrCIF(ExportCIF):
2095    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2096    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2097    Shows up in menu as Quick CIF.
2098
2099    :param wx.Frame G2frame: reference to main GSAS-II frame
2100    '''
2101    def __init__(self,G2frame):
2102        ExportCIF.__init__(self,
2103            G2frame=G2frame,
2104            formatName = 'Data-only CIF',
2105            extension='.cif',
2106            longFormatName = 'Export data as CIF'
2107            )
2108        self.exporttype = ['powder']
2109        # CIF-specific items
2110        self.author = ''
2111
2112    def Exporter(self,event=None):
2113        self.InitExport(event)
2114        # load all of the tree into a set of dicts
2115        self.currentExportType = None
2116        self.loadTree()
2117        self.currentExportType = 'powder'
2118        # create a dict with refined values and their uncertainties
2119        self.loadParmDict()
2120        self.multiple = False
2121        if self.ExportSelect( # set export parameters
2122            AskFile='ask' # get a file name/directory to save in
2123            ): return
2124        self._Exporter(event=event,histOnly=self.histnam[0])
2125
2126class ExportHKLCIF(ExportCIF):
2127    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2128    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2129    Shows up in menu as Quick CIF.
2130
2131    :param wx.Frame G2frame: reference to main GSAS-II frame
2132    '''
2133    def __init__(self,G2frame):
2134        ExportCIF.__init__(self,
2135            G2frame=G2frame,
2136            formatName = 'Data-only CIF',
2137            extension='.cif',
2138            longFormatName = 'Export data as CIF'
2139            )
2140        self.exporttype = ['single']
2141        # CIF-specific items
2142        self.author = ''
2143
2144    def Exporter(self,event=None):
2145        self.InitExport(event)
2146        # load all of the tree into a set of dicts
2147        self.currentExportType = None
2148        self.loadTree()
2149        self.currentExportType = 'single'
2150        # create a dict with refined values and their uncertainties
2151        self.loadParmDict()
2152        self.multiple = False
2153        if self.ExportSelect( # set export parameters
2154            AskFile='ask' # get a file name/directory to save in
2155            ): return
2156        self._Exporter(event=event,histOnly=self.histnam[0])
2157               
2158#===============================================================================
2159# misc CIF utilities
2160#===============================================================================
2161def PickleCIFdict(fil):
2162    '''Loads a CIF dictionary, cherry picks out the items needed
2163    by local code and sticks them into a python dict and writes
2164    that dict out as a cPickle file for later reuse.
2165    If the write fails a warning message is printed,
2166    but no exception occurs.
2167
2168    :param str fil: file name of CIF dictionary, will usually end
2169      in .dic
2170    :returns: the dict with the definitions 
2171    '''
2172    import CifFile as cif # PyCifRW from James Hester
2173    cifdic = {}
2174    try:
2175        fp = open(fil,'r')             # patch: open file to avoid windows bug
2176        dictobj = cif.CifDic(fp)
2177        fp.close()
2178    except IOError:
2179        dictobj = cif.CifDic(fil)
2180    if DEBUG: print('loaded '+fil)
2181    for item in dictobj.keys():
2182        cifdic[item] = {}
2183        for j in (
2184            '_definition','_type',
2185            '_enumeration',
2186            '_enumeration_detail',
2187            '_enumeration_range'):
2188            if dictobj[item].get(j):
2189                cifdic[item][j] = dictobj[item][j]
2190    try:
2191        fil = os.path.splitext(fil)[0]+'.cpickle'
2192        fp = open(fil,'w')
2193        cPickle.dump(cifdic,fp)
2194        fp.close()
2195        if DEBUG: print('wrote '+fil)
2196    except:
2197        print ('Unable to write '+fil)
2198    return cifdic
2199
2200def LoadCIFdic():
2201    '''Create a composite core+powder CIF lookup dict containing
2202    information about all items in the CIF dictionaries, loading
2203    pickled files if possible. The routine looks for files
2204    named cif_core.cpickle and cif_pd.cpickle in every
2205    directory in the path and if they are not found, files
2206    cif_core.dic and/or cif_pd.dic are read.
2207
2208    :returns: the dict with the definitions 
2209    '''
2210    cifdic = {}
2211    for ftyp in "cif_core","cif_pd":
2212        for loc in sys.path:
2213            fil = os.path.join(loc,ftyp+".cpickle")
2214            if not os.path.exists(fil): continue
2215            fp = open(fil,'r')
2216            try:
2217                cifdic.update(cPickle.load(fp))
2218                if DEBUG: print('reloaded '+fil)
2219                break
2220            finally:
2221                fp.close()
2222        else:
2223            for loc in sys.path:
2224                fil = os.path.join(loc,ftyp+".dic")
2225                if not os.path.exists(fil): continue
2226                #try:
2227                if True:
2228                    cifdic.update(PickleCIFdict(fil))
2229                    break
2230                #except:
2231                #    pass
2232            else:
2233                print('Could not load '+ftyp+' dictionary')
2234    return cifdic
2235
2236class CIFdefHelp(wx.Button):
2237    '''Create a help button that displays help information on
2238    the current data item
2239
2240    :param parent: the panel which will be the parent of the button
2241    :param str msg: the help text to be displayed
2242    :param wx.Dialog helpwin: Frame for CIF editing dialog
2243    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
2244    '''
2245    def __init__(self,parent,msg,helpwin,helptxt):
2246        wx.Button.__init__(self,parent,wx.ID_HELP)
2247        self.Bind(wx.EVT_BUTTON,self._onPress)
2248        self.msg=msg
2249        self.parent = parent
2250        #self.helpwin = self.parent.helpwin
2251        self.helpwin = helpwin
2252        self.helptxt = helptxt
2253    def _onPress(self,event):
2254        'Respond to a button press by displaying the requested text'
2255        try:
2256            #helptxt = self.helptxt
2257            ow,oh = self.helptxt.GetSize()
2258            self.helptxt.SetLabel(self.msg)
2259            w,h = self.helptxt.GetSize()
2260            if h > oh:
2261                self.helpwin.GetSizer().Fit(self.helpwin)
2262        except: # error posting help, ignore
2263            return
2264
2265def CIF2dict(cf):
2266    '''copy the contents of a CIF out from a PyCifRW block object
2267    into a dict
2268
2269    :returns: cifblk, loopstructure where cifblk is a dict with
2270      CIF items and loopstructure is a list of lists that defines
2271      which items are in which loops.
2272    '''
2273    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
2274    loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
2275    dblk = {}
2276    for item in cf[blk].keys(): # make a copy of all the items in the block
2277        dblk[item] = cf[blk][item]
2278    return dblk,loopstructure
2279
2280def dict2CIF(dblk,loopstructure,blockname='Template'):
2281    '''Create a PyCifRW CIF object containing a single CIF
2282    block object from a dict and loop structure list.
2283
2284    :param dblk: a dict containing values for each CIF item
2285    :param list loopstructure: a list of lists containing the contents of
2286      each loop, as an example::
2287
2288         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2289
2290      this describes a CIF with this type of structure::
2291
2292        loop_ _a _b <a1> <b1> <a2> ...
2293        loop_ _c <c1> <c2>...
2294        loop _d_1 _d_2 _d_3 ...
2295
2296      Note that the values for each looped CIF item, such as _a,
2297      are contained in a list, for example as cifblk["_a"]
2298
2299    :param str blockname: an optional name for the CIF block.
2300      Defaults to 'Template'
2301
2302    :returns: the newly created PyCifRW CIF object
2303    '''
2304
2305    import CifFile as cif # PyCifRW from James Hester
2306    # compile a 'list' of items in loops
2307    loopnames = set()
2308    for i in loopstructure:
2309        loopnames |= set(i)
2310    # create a new block
2311    newblk = cif.CifBlock()
2312    # add the looped items
2313    for keys in loopstructure:
2314        vals = []
2315        for key in keys:
2316            vals.append(dblk[key])
2317        newblk.AddCifItem(([keys],[vals]))
2318    # add the non-looped items
2319    for item in dblk:
2320        if item in loopnames: continue
2321        newblk[item] = dblk[item]
2322    # create a CIF and add the block
2323    newcf = cif.CifFile()
2324    newcf[blockname] = newblk   
2325    return newcf
2326
2327
2328class EditCIFtemplate(wx.Dialog):
2329    '''Create a dialog for editing a CIF template. The edited information is
2330    placed in cifblk. If the CIF is saved as a file, the name of that file
2331    is saved as ``self.newfile``.
2332   
2333    :param wx.Frame parent: parent frame or None
2334    :param cifblk: dict or PyCifRW block containing values for each CIF item
2335    :param list loopstructure: a list of lists containing the contents of
2336      each loop, as an example::
2337
2338         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2339
2340      this describes a CIF with this type of structure::
2341
2342        loop_ _a _b <a1> <b1> <a2> ...
2343        loop_ _c <c1> <c2>...
2344        loop _d_1 _d_2 _d_3 ...
2345
2346      Note that the values for each looped CIF item, such as _a,
2347      are contained in a list, for example as cifblk["_a"]
2348     
2349    :param str defaultname: specifies the default file name to be used for
2350      saving the CIF.
2351    '''
2352    def __init__(self,parent,cifblk,loopstructure,defaultname):
2353        OKbuttons = []
2354        self.cifblk = cifblk
2355        self.loopstructure = loopstructure
2356        self.newfile = None
2357        self.defaultname = defaultname       
2358        global CIFdic  # once this is loaded, keep it around
2359        if CIFdic is None:
2360            CIFdic = LoadCIFdic()
2361        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2362
2363        # define widgets that will be needed during panel creation
2364        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
2365        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
2366        OKbuttons.append(savebtn)
2367        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
2368        OKbtn = wx.Button(self, wx.ID_OK, "Use")
2369        OKbtn.SetDefault()
2370        OKbuttons.append(OKbtn)
2371
2372        self.SetTitle('Edit items in CIF template')
2373        vbox = wx.BoxSizer(wx.VERTICAL)
2374        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
2375        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2376        G2G.HorizontalLine(vbox,self)
2377        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
2378        G2G.HorizontalLine(vbox,self)
2379        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2380        btn = wx.Button(self, wx.ID_CANCEL)
2381        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
2382        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
2383        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
2384        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2385        self.SetSizer(vbox)
2386        vbox.Fit(self)
2387    def Post(self):
2388        '''Display the dialog
2389       
2390        :returns: True unless Cancel has been pressed.
2391        '''
2392        return (self.ShowModal() == wx.ID_OK)
2393    def _onSave(self,event):
2394        'Save CIF entries in a template file'
2395        pth = G2G.GetExportPath(self.G2frame)
2396        dlg = wx.FileDialog(
2397            self, message="Save as CIF template",
2398            defaultDir=pth,
2399            defaultFile=self.defaultname,
2400            wildcard="CIF (*.cif)|*.cif",
2401            style=wx.SAVE)
2402        val = (dlg.ShowModal() == wx.ID_OK)
2403        fil = dlg.GetPath()
2404        dlg.Destroy()
2405        if val: # ignore a Cancel button
2406            fil = os.path.splitext(fil)[0]+'.cif' # force extension
2407            fp = open(fil,'w')
2408            newcf = dict2CIF(self.cifblk,self.loopstructure)
2409            fp.write(newcf.WriteOut())
2410            fp.close()
2411            self.newfile = fil
2412            self.EndModal(wx.ID_OK)
2413
2414class EditCIFpanel(wxscroll.ScrolledPanel):
2415    '''Creates a scrolled panel for editing CIF template items
2416
2417    :param wx.Frame parent: parent frame where panel will be placed
2418    :param cifblk: dict or PyCifRW block containing values for each CIF item
2419    :param list loopstructure: a list of lists containing the contents of
2420      each loop, as an example::
2421
2422         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2423
2424      this describes a CIF with this type of structure::
2425
2426        loop_ _a _b <a1> <b1> <a2> ...
2427        loop_ _c <c1> <c2>...
2428        loop _d_1 _d_2 _d_3 ...
2429
2430      Note that the values for each looped CIF item, such as _a,
2431      are contained in a list, for example as cifblk["_a"]
2432
2433    :param dict cifdic: optional CIF dictionary definitions
2434    :param list OKbuttons: A list of wx.Button objects that should
2435      be disabled when information in the CIF is invalid
2436    :param (other): optional keyword parameters for wx.ScrolledPanel
2437    '''
2438    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
2439        self.parent = parent
2440        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
2441        self.vbox = None
2442        self.AddDict = None
2443        self.cifdic = cifdic
2444        self.cifblk = cifblk
2445        self.loops = loopstructure
2446        self.parent = parent
2447        self.LayoutCalled = False
2448        self.parentOKbuttons = OKbuttons
2449        self.ValidatedControlsList = []
2450        self._fill()
2451    def _fill(self):
2452        'Fill the scrolled panel with widgets for each CIF item'
2453        wx.BeginBusyCursor()
2454        self.AddDict = {}
2455        self.ValidatedControlsList = []
2456        # delete any only contents
2457        if self.vbox:
2458            self.vbox.DeleteWindows()
2459            self.vbox = None
2460            self.Update()
2461        vbox = wx.BoxSizer(wx.VERTICAL)
2462        self.vbox = vbox
2463        # compile a 'list' of items in loops
2464        loopnames = set()
2465        for i in self.loops:
2466            loopnames |= set(i)
2467        # post the looped CIF items
2468        for lnum,lp in enumerate(self.loops):
2469            hbox = wx.BoxSizer(wx.HORIZONTAL)
2470            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
2471            vbox.Add(hbox)
2472            but = wx.Button(self,wx.ID_ANY,"Add row")
2473            self.AddDict[but]=lnum
2474           
2475            hbox.Add(but)           
2476            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
2477            fbox = wx.GridBagSizer(0, 0)
2478            vbox.Add(fbox)
2479            rows = 0
2480            for i,item in enumerate(lp):
2481                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
2482                fbox.Add(txt,(0,i+1))
2483                # if self.cifdic.get(item):
2484                #     df = self.cifdic[item].get('_definition')
2485                #     if df:
2486                #         txt.SetToolTipString(G2IO.trim(df))
2487                #         but = CIFdefHelp(self,
2488                #                          "Definition for "+item+":\n\n"+G2IO.trim(df),
2489                #                          self.parent,
2490                #                          self.parent.helptxt)
2491                #         fbox.Add(but,(1,i+1),flag=wx.ALIGN_CENTER)
2492                for j,val in enumerate(self.cifblk[item]):
2493                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
2494                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
2495                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
2496                if self.cifdic.get(item):
2497                    df = self.cifdic[item].get('_definition')
2498                    if df:
2499                        txt.SetToolTipString(G2IO.trim(df))
2500                        but = CIFdefHelp(self,
2501                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2502                                         self.parent,
2503                                         self.parent.helptxt)
2504                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
2505                rows = max(rows,len(self.cifblk[item]))
2506            for i in range(rows):
2507                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
2508                fbox.Add(txt,(i+2,0))
2509            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
2510            vbox.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2511               
2512        # post the non-looped CIF items
2513        for item in sorted(self.cifblk.keys()):
2514            if item not in loopnames:
2515                hbox = wx.BoxSizer(wx.HORIZONTAL)
2516                vbox.Add(hbox)
2517                txt = wx.StaticText(self,wx.ID_ANY,item)
2518                hbox.Add(txt)
2519                ent = self.CIFEntryWidget(self.cifblk,item,item)
2520                hbox.Add(ent)
2521                if self.cifdic.get(item):
2522                    df = self.cifdic[item].get('_definition')
2523                    if df:
2524                        txt.SetToolTipString(G2IO.trim(df))
2525                        but = CIFdefHelp(self,
2526                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2527                                         self.parent,
2528                                         self.parent.helptxt)
2529                        hbox.Add(but,0,wx.ALL,2)
2530        self.SetSizer(vbox)
2531        #vbox.Fit(self.parent)
2532        self.SetAutoLayout(1)
2533        self.SetupScrolling()
2534        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
2535        self.Layout()
2536        wx.EndBusyCursor()
2537    def OnLayoutNeeded(self,event):
2538        '''Called when an update of the panel layout is needed. Calls
2539        self.DoLayout after the current operations are complete using
2540        CallAfter. This is called only once, according to flag
2541        self.LayoutCalled, which is cleared in self.DoLayout.
2542        '''
2543        if self.LayoutCalled: return # call already queued
2544        wx.CallAfter(self.DoLayout) # queue a call
2545        self.LayoutCalled = True
2546    def DoLayout(self):
2547        '''Update the Layout and scroll bars for the Panel. Clears
2548        self.LayoutCalled so that next change to panel can
2549        request a new update
2550        '''
2551        wx.BeginBusyCursor()
2552        self.Layout()
2553        self.SetupScrolling()
2554        wx.EndBusyCursor()
2555        self.LayoutCalled = False
2556    def OnAddRow(self,event):
2557        'add a row to a loop'
2558        lnum = self.AddDict.get(event.GetEventObject())
2559        if lnum is None: return
2560        for item in self.loops[lnum]:
2561            self.cifblk[item].append('?')
2562        self._fill()
2563
2564    def ControlOKButton(self,setvalue):
2565        '''Enable or Disable the OK button(s) for the dialog. Note that this is
2566        passed into the ValidatedTxtCtrl for use by validators.
2567
2568        :param bool setvalue: if True, all entries in the dialog are
2569          checked for validity. The first invalid control triggers
2570          disabling of buttons.
2571          If False then the OK button(s) are disabled with no checking
2572          of the invalid flag for each control.
2573        '''
2574        if setvalue: # turn button on, do only if all controls show as valid
2575            for ctrl in self.ValidatedControlsList:
2576                if ctrl.invalid:
2577                    for btn in self.parentOKbuttons:
2578                        btn.Disable()
2579                    return
2580            else:
2581                for btn in self.parentOKbuttons:
2582                    btn.Enable()
2583        else:
2584            for btn in self.parentOKbuttons:
2585                btn.Disable()
2586       
2587    def CIFEntryWidget(self,dct,item,dataname):
2588        '''Create an entry widget for a CIF item. Use a validated entry for numb values
2589        where int is required when limits are integers and floats otherwise.
2590        At present this does not allow entry of the special CIF values of "." and "?" for
2591        numerical values and highlights them as invalid.
2592        Use a selection widget when there are specific enumerated values for a string.       
2593        '''
2594        if self.cifdic.get(dataname):
2595            if self.cifdic[dataname].get('_enumeration'):
2596                values = ['?']+self.cifdic[dataname]['_enumeration']
2597                choices = ['undefined']
2598                for i in self.cifdic[dataname].get('_enumeration_detail',values):
2599                    choices.append(G2IO.trim(i))
2600                ent = G2G.EnumSelector(self, dct, item, choices, values, size=(200, -1))
2601                return ent
2602            if self.cifdic[dataname].get('_type') == 'numb':
2603                mn = None
2604                mx = None
2605                hint = int
2606                if self.cifdic[dataname].get('_enumeration_range'):
2607                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
2608                    if '.' in rng[0] or '.' in rng[1]: hint = float
2609                    if rng[0]: mn = hint(rng[0])
2610                    if rng[1]: mx = hint(rng[1])
2611                    ent = G2G.ValidatedTxtCtrl(
2612                        self,dct,item,typeHint=hint,min=mn,max=mx,
2613                        CIFinput=True,ASCIIonly=True,
2614                        OKcontrol=self.ControlOKButton)
2615                    self.ValidatedControlsList.append(ent)
2616                    return ent
2617        rw1 = rw.ResizeWidget(self)
2618        ent = G2G.ValidatedTxtCtrl(
2619            rw1,dct,item,size=(100, 20),
2620            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
2621            CIFinput=True,ASCIIonly=True,
2622            OKcontrol=self.ControlOKButton)
2623        self.ValidatedControlsList.append(ent)
2624        return rw1
2625
2626class CIFtemplateSelect(wx.BoxSizer):
2627    '''Create a set of buttons to show, select and edit a CIF template
2628   
2629    :param frame: wx.Frame object of parent
2630    :param panel: wx.Panel object where widgets should be placed
2631    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
2632      the type of template
2633    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
2634      "CIF_template" will be used to store either a list or a string.
2635      If a list, it will contain a dict and a list defining loops. If
2636      an str, it will contain a file name.   
2637    :param function repaint: reference to a routine to be called to repaint
2638      the frame after a change has been made
2639    :param str title: A line of text to show at the top of the window
2640    :param str defaultname: specifies the default file name to be used for
2641      saving the CIF.
2642    '''
2643    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
2644        wx.BoxSizer.__init__(self,wx.VERTICAL)
2645        self.cifdefs = frame
2646        self.dict = G2dict
2647        self.repaint = repaint
2648        templateDefName = 'template_'+tmplate+'.cif'
2649        self.CIF = G2dict.get("CIF_template")
2650        if defaultname:
2651            self.defaultname = defaultname.encode('ascii','replace').strip().replace(' ','_')
2652            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
2653            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
2654        else:
2655            self.defaultname = ''
2656           
2657        txt = wx.StaticText(panel,wx.ID_ANY,title)
2658        self.Add(txt,0,wx.ALIGN_CENTER)
2659        # change font on title
2660        txtfnt = txt.GetFont()
2661        txtfnt.SetWeight(wx.BOLD)
2662        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
2663        txt.SetFont(txtfnt)
2664        self.Add((-1,3))
2665
2666        if not self.CIF: # empty or None
2667            for pth in [os.getcwd()]+sys.path:
2668                fil = os.path.join(pth,self.defaultname)
2669                if os.path.exists(fil) and self.defaultname:
2670                    self.CIF = fil
2671                    CIFtxt = "Template: "+self.defaultname
2672                    break
2673            else:
2674                for pth in sys.path:
2675                    fil = os.path.join(pth,templateDefName)
2676                    if os.path.exists(fil):
2677                        self.CIF = fil
2678                        CIFtxt = "Template: "+templateDefName
2679                        break
2680                else:
2681                    print("Default CIF template "+self.defaultname+' not found in path!')
2682                    self.CIF = None
2683                    CIFtxt = "none! (No template found)"
2684        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
2685            if not os.path.exists(self.CIF):
2686                print("Error: template file has disappeared: "+self.CIF)
2687                self.CIF = None
2688                CIFtxt = "none! (file not found)"
2689            else:
2690                if len(self.CIF) < 50:
2691                    CIFtxt = "File: "+self.CIF
2692                else:
2693                    CIFtxt = "File: ..."+self.CIF[-50:]
2694        else:
2695            CIFtxt = "Template is customized"
2696        # show template source
2697        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
2698        # show str, button to select file; button to edit (if CIF defined)
2699        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
2700        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
2701        hbox =  wx.BoxSizer(wx.HORIZONTAL)
2702        hbox.Add(but,0,0,2)
2703        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
2704        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
2705        if self.CIF is None: but.Disable() # nothing to edit!
2706        hbox.Add(but,0,0,2)
2707        self.Add(hbox)
2708    def _onGetTemplateFile(self,event):
2709        'select a template file'
2710        pth = G2G.GetImportPath(self.G2frame)
2711        if not pth: pth = '.'
2712        dlg = wx.FileDialog(
2713            self.cifdefs, message="Read CIF template file",
2714            defaultDir=pth,
2715            defaultFile=self.defaultname,
2716            wildcard="CIF (*.cif)|*.cif",
2717            style=wx.OPEN)
2718        ret = dlg.ShowModal()
2719        fil = dlg.GetPath()
2720        dlg.Destroy()
2721        if ret == wx.ID_OK:
2722            cf = G2IO.ReadCIF(fil)
2723            if len(cf.keys()) == 0:
2724                raise Exception,"No CIF data_ blocks found"
2725            if len(cf.keys()) != 1:
2726                raise Exception, 'Error, CIF Template has more than one block: '+fil
2727            self.dict["CIF_template"] = fil
2728            self.repaint() #EditCIFDefaults()
2729
2730    def _onEditTemplateContents(self,event):
2731        'Called to edit the contents of a CIF template'
2732        if type(self.CIF) is list or  type(self.CIF) is tuple:
2733            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
2734        else:
2735            cf = G2IO.ReadCIF(self.CIF)
2736            dblk,loopstructure = CIF2dict(cf)
2737        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
2738        val = dlg.Post()
2739        if val:
2740            if dlg.newfile: # results saved in file
2741                self.dict["CIF_template"] = dlg.newfile
2742            else:
2743                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
2744            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
2745        else:
2746            dlg.Destroy()       
2747
2748#===============================================================================
2749# end of misc CIF utilities
2750#===============================================================================
Note: See TracBrowser for help on using the repository browser.