source: trunk/exports/G2export_CIF.py @ 2733

Last change on this file since 2733 was 2733, checked in by toby, 7 years ago

fix min/max in CIF

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 126.4 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2017-03-02 02:31:17 +0000 (Thu, 02 Mar 2017) $
5# $Author: toby $
6# $Revision: 2733 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 2733 2017-03-02 02:31:17Z toby $
9########### SVN repository information ###################
10'''
11*Module G2export_CIF: CIF Exports*
12------------------------------------------------------
13
14This implements a complex exporter :class:`ExportCIF` that can implement an
15entire project in a complete CIF intended for submission as a
16publication. In addition, there are three subclasses of :class:`ExportCIF`:
17:class:`ExportProjectCIF`,
18:class:`ExportPhaseCIF` and :class:`ExportDataCIF` where extra parameters
19for the _Exporter() determine if a project, single phase or data set are written.
20'''
21
22import datetime as dt
23import os.path
24import sys
25import numpy as np
26import cPickle
27import copy
28import re
29import wx
30import wx.lib.scrolledpanel as wxscroll
31import wx.lib.resizewidget as rw
32import GSASIIpath
33GSASIIpath.SetVersionNumber("$Revision: 2733 $")
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            try:
1897                dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
1898#                Size = dlg.GetSize()
1899#                Size = (int(Size[0]*3),Size[1]) # increase size along x
1900#                dlg.SetSize(Size)
1901                dlg.CenterOnParent()
1902
1903                # publication info
1904                step = 1
1905                dlg.Update(step,"Exporting overall section")
1906                WriteCIFitem('\ndata_'+self.CIFname+'_publ')
1907                WriteAudit()
1908                WriteCIFitem('_pd_block_id',
1909                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1910                             str(self.shortauthorname) + "|Overall")
1911                writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
1912                # ``template_publ.cif`` or a modified version
1913                # overall info
1914                WriteCIFitem('data_'+str(self.CIFname)+'_overall')
1915                WriteOverall()
1916                #============================================================
1917                WriteCIFitem('# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
1918                datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
1919                # loop over phase blocks
1920                if len(self.Phases) > 1:
1921                    loopprefix = ''
1922                    WriteCIFitem('loop_   _pd_phase_block_id')
1923                else:
1924                    loopprefix = '_pd_phase_block_id'
1925
1926                for phasenam in sorted(self.Phases.keys()):
1927                    i = self.Phases[phasenam]['pId']
1928                    datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1929                                 'phase_'+ str(i) + '|' + str(self.shortauthorname))
1930                    WriteCIFitem(loopprefix,datablockidDict[phasenam])
1931                # loop over data blocks
1932                if len(self.powderDict) + len(self.xtalDict) > 1:
1933                    loopprefix = ''
1934                    WriteCIFitem('loop_   _pd_block_diffractogram_id')
1935                else:
1936                    loopprefix = '_pd_block_diffractogram_id'
1937                for i in sorted(self.powderDict.keys()):
1938                    hist = self.powderDict[i]
1939                    histblk = self.Histograms[hist]
1940                    instnam = histblk["Sample Parameters"]['InstrName']
1941                    instnam = instnam.replace(' ','')
1942                    j = histblk['hId']
1943                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1944                                             str(self.shortauthorname) + "|" +
1945                                             instnam + "_hist_"+str(j))
1946                    WriteCIFitem(loopprefix,datablockidDict[hist])
1947                for i in sorted(self.xtalDict.keys()):
1948                    hist = self.xtalDict[i]
1949                    histblk = self.Histograms[hist]
1950                    instnam = histblk["Instrument Parameters"][0]['InstrName']
1951                    instnam = instnam.replace(' ','')
1952                    i = histblk['hId']
1953                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1954                                             str(self.shortauthorname) + "|" +
1955                                             instnam + "_hist_"+str(i))
1956                    WriteCIFitem(loopprefix,datablockidDict[hist])
1957                #============================================================
1958                # loop over phases, exporting them
1959                phasebyhistDict = {} # create a cross-reference to phases by histogram
1960                for j,phasenam in enumerate(sorted(self.Phases.keys())):
1961                    step += 1
1962                    dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
1963                    i = self.Phases[phasenam]['pId']
1964                    WriteCIFitem('\ndata_'+self.CIFname+"_phase_"+str(i))
1965                    WriteCIFitem('# Information for phase '+str(i))
1966                    WriteCIFitem('_pd_block_id',datablockidDict[phasenam])
1967                    # report the phase
1968                    writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1969                    WritePhaseInfo(phasenam)
1970                    # preferred orientation
1971                    if self.ifPWDR:
1972                        SH = FormatSH(phasenam)
1973                        MD = FormatHAPpo(phasenam)
1974                        if SH and MD:
1975                            WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1976                        elif SH or MD:
1977                            WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1978                        else:
1979                            WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1980                    # report sample profile terms
1981                    PP = FormatPhaseProfile(phasenam)
1982                    if PP:
1983                        WriteCIFitem('_pd_proc_ls_profile_function',PP)
1984
1985                #============================================================
1986                # loop over histograms, exporting them
1987                for i in sorted(self.powderDict.keys()):
1988                    hist = self.powderDict[i]
1989                    histblk = self.Histograms[hist]
1990                    if hist.startswith("PWDR"): 
1991                        step += 1
1992                        dlg.Update(step,"Exporting "+hist.strip())
1993                        WriteCIFitem('\ndata_'+self.CIFname+"_pwd_"+str(i))
1994                        #instnam = histblk["Sample Parameters"]['InstrName']
1995                        # report instrumental profile terms
1996                        WriteCIFitem('_pd_proc_ls_profile_function',
1997                            FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1998                        WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
1999                        WriteCIFitem('_pd_block_id',datablockidDict[hist])
2000                        histprm = self.Histograms[hist]["Sample Parameters"]
2001                        writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
2002                        WritePowderData(hist)
2003                for i in sorted(self.xtalDict.keys()):
2004                    hist = self.xtalDict[i]
2005                    histblk = self.Histograms[hist]
2006                    if hist.startswith("HKLF"): 
2007                        step += 1
2008                        dlg.Update(step,"Exporting "+hist.strip())
2009                        WriteCIFitem('\ndata_'+self.CIFname+"_sx_"+str(i))
2010                        #instnam = histblk["Instrument Parameters"][0]['InstrName']
2011                        WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
2012                        WriteCIFitem('_pd_block_id',datablockidDict[hist])
2013                        histprm = self.Histograms[hist]["Instrument Parameters"][0]
2014                        writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
2015                        WriteSingleXtalData(hist)
2016
2017            except Exception:
2018                import traceback
2019                print(traceback.format_exc())
2020                self.G2frame.ErrorDialog('Exception',
2021                                         'Error occurred in CIF creation. '+
2022                                         'See full error message in console output ')
2023            finally:
2024                dlg.Destroy()
2025
2026        WriteCIFitem('#--' + 15*'eof--' + '#')
2027        self.CloseFile()
2028        print("...export completed")
2029        print('file '+self.fullpath)
2030        # end of CIF export
2031
2032class ExportProjectCIF(ExportCIF):
2033    '''Used to create a CIF of an entire project
2034
2035    :param wx.Frame G2frame: reference to main GSAS-II frame
2036    '''
2037    def __init__(self,G2frame):
2038        ExportCIF.__init__(self,
2039            G2frame=G2frame,
2040            formatName = 'Full CIF',
2041            extension='.cif',
2042            longFormatName = 'Export project as CIF'
2043            )
2044        self.exporttype = ['project']
2045       
2046    def Exporter(self,event=None):
2047        self._Exporter(event=event)
2048
2049    def Writer(self,hist,mode='w'):
2050        # set the project file name
2051        self.CIFname = os.path.splitext(
2052            os.path.split(self.G2frame.GSASprojectfile)[1]
2053            )[0]+'_'+hist
2054        self.CIFname = self.CIFname.replace(' ','')
2055        self.OpenFile(mode=mode)
2056        self._Exporter(histOnly=hist)
2057        if mode == 'w':
2058            print('CIF written to file '+self.fullpath)
2059        self.CloseFile()
2060       
2061class ExportPhaseCIF(ExportCIF):
2062    '''Used to create a simple CIF with one phase. Uses exact same code as
2063    :class:`ExportCIF` except that `phaseOnly` is set for the Exporter
2064    Shows up in menu as Quick CIF.
2065
2066    :param wx.Frame G2frame: reference to main GSAS-II frame
2067    '''
2068    def __init__(self,G2frame):
2069        ExportCIF.__init__(self,
2070            G2frame=G2frame,
2071            formatName = 'Quick CIF',
2072            extension='.cif',
2073            longFormatName = 'Export one phase in CIF'
2074            )
2075        self.exporttype = ['phase']
2076        # CIF-specific items
2077        self.author = ''
2078
2079    def Exporter(self,event=None):
2080        # get a phase and file name
2081        # the export process starts here
2082        self.InitExport(event)
2083        # load all of the tree into a set of dicts
2084        self.loadTree()
2085        # create a dict with refined values and their uncertainties
2086        self.loadParmDict()
2087        self.multiple = False
2088        self.currentExportType = 'phase'
2089        if self.ExportSelect('ask'): return
2090        self._Exporter(event=event,phaseOnly=self.phasenam[0])
2091
2092    def Writer(self,hist,phasenam,mode='w'):
2093        # set the project file name
2094        self.CIFname = os.path.splitext(
2095            os.path.split(self.G2frame.GSASprojectfile)[1]
2096            )[0]+'_'+phasenam+'_'+hist
2097        self.CIFname = self.CIFname.replace(' ','')
2098        self.OpenFile(mode=mode)
2099        self._Exporter(phaseOnly=phasenam)
2100        self.CloseFile()
2101
2102class ExportPwdrCIF(ExportCIF):
2103    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2104    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2105    Shows up in menu as Quick CIF.
2106
2107    :param wx.Frame G2frame: reference to main GSAS-II frame
2108    '''
2109    def __init__(self,G2frame):
2110        ExportCIF.__init__(self,
2111            G2frame=G2frame,
2112            formatName = 'Data-only CIF',
2113            extension='.cif',
2114            longFormatName = 'Export data as CIF'
2115            )
2116        self.exporttype = ['powder']
2117        # CIF-specific items
2118        self.author = ''
2119
2120    def Exporter(self,event=None):
2121        self.InitExport(event)
2122        # load all of the tree into a set of dicts
2123        self.currentExportType = None
2124        self.loadTree()
2125        self.currentExportType = 'powder'
2126        # create a dict with refined values and their uncertainties
2127        self.loadParmDict()
2128        self.multiple = False
2129        if self.ExportSelect( # set export parameters
2130            AskFile='ask' # get a file name/directory to save in
2131            ): return
2132        self._Exporter(event=event,histOnly=self.histnam[0])
2133
2134class ExportHKLCIF(ExportCIF):
2135    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2136    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2137    Shows up in menu as Quick CIF.
2138
2139    :param wx.Frame G2frame: reference to main GSAS-II frame
2140    '''
2141    def __init__(self,G2frame):
2142        ExportCIF.__init__(self,
2143            G2frame=G2frame,
2144            formatName = 'Data-only CIF',
2145            extension='.cif',
2146            longFormatName = 'Export data as CIF'
2147            )
2148        self.exporttype = ['single']
2149        # CIF-specific items
2150        self.author = ''
2151
2152    def Exporter(self,event=None):
2153        self.InitExport(event)
2154        # load all of the tree into a set of dicts
2155        self.currentExportType = None
2156        self.loadTree()
2157        self.currentExportType = 'single'
2158        # create a dict with refined values and their uncertainties
2159        self.loadParmDict()
2160        self.multiple = False
2161        if self.ExportSelect( # set export parameters
2162            AskFile='ask' # get a file name/directory to save in
2163            ): return
2164        self._Exporter(event=event,histOnly=self.histnam[0])
2165               
2166#===============================================================================
2167# misc CIF utilities
2168#===============================================================================
2169def PickleCIFdict(fil):
2170    '''Loads a CIF dictionary, cherry picks out the items needed
2171    by local code and sticks them into a python dict and writes
2172    that dict out as a cPickle file for later reuse.
2173    If the write fails a warning message is printed,
2174    but no exception occurs.
2175
2176    :param str fil: file name of CIF dictionary, will usually end
2177      in .dic
2178    :returns: the dict with the definitions 
2179    '''
2180    import CifFile as cif # PyCifRW from James Hester
2181    cifdic = {}
2182    try:
2183        fp = open(fil,'r')             # patch: open file to avoid windows bug
2184        dictobj = cif.CifDic(fp)
2185        fp.close()
2186    except IOError:
2187        dictobj = cif.CifDic(fil)
2188    if DEBUG: print('loaded '+str(fil))
2189    for item in dictobj.keys():
2190        cifdic[item] = {}
2191        for j in (
2192            '_definition','_type',
2193            '_enumeration',
2194            '_enumeration_detail',
2195            '_enumeration_range'):
2196            if dictobj[item].get(j):
2197                cifdic[item][j] = dictobj[item][j]
2198    try:
2199        fil = os.path.splitext(fil)[0]+'.cpickle'
2200        fp = open(fil,'w')
2201        cPickle.dump(cifdic,fp)
2202        fp.close()
2203        if DEBUG: print('wrote '+str(fil))
2204    except:
2205        print ('Unable to write '+str(fil))
2206    return cifdic
2207
2208def LoadCIFdic():
2209    '''Create a composite core+powder CIF lookup dict containing
2210    information about all items in the CIF dictionaries, loading
2211    pickled files if possible. The routine looks for files
2212    named cif_core.cpickle and cif_pd.cpickle in every
2213    directory in the path and if they are not found, files
2214    cif_core.dic and/or cif_pd.dic are read.
2215
2216    :returns: the dict with the definitions 
2217    '''
2218    cifdic = {}
2219    for ftyp in "cif_core","cif_pd":
2220        for loc in sys.path:
2221            fil = os.path.join(loc,ftyp+".cpickle")
2222            if not os.path.exists(fil): continue
2223            fp = open(fil,'r')
2224            try:
2225                cifdic.update(cPickle.load(fp))
2226                if DEBUG: print('reloaded '+str(fil))
2227                break
2228            finally:
2229                fp.close()
2230        else:
2231            for loc in sys.path:
2232                fil = os.path.join(loc,ftyp+".dic")
2233                if not os.path.exists(fil): continue
2234                #try:
2235                if True:
2236                    cifdic.update(PickleCIFdict(fil))
2237                    break
2238                #except:
2239                #    pass
2240            else:
2241                print('Could not load '+ftyp+' dictionary')
2242    return cifdic
2243
2244class CIFdefHelp(wx.Button):
2245    '''Create a help button that displays help information on
2246    the current data item
2247
2248    :param parent: the panel which will be the parent of the button
2249    :param str msg: the help text to be displayed
2250    :param wx.Dialog helpwin: Frame for CIF editing dialog
2251    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
2252    '''
2253    def __init__(self,parent,msg,helpwin,helptxt):
2254        wx.Button.__init__(self,parent,wx.ID_HELP)
2255        self.Bind(wx.EVT_BUTTON,self._onPress)
2256        self.msg=msg
2257        self.parent = parent
2258        #self.helpwin = self.parent.helpwin
2259        self.helpwin = helpwin
2260        self.helptxt = helptxt
2261    def _onPress(self,event):
2262        'Respond to a button press by displaying the requested text'
2263        try:
2264            #helptxt = self.helptxt
2265            ow,oh = self.helptxt.GetSize()
2266            self.helptxt.SetLabel(self.msg)
2267            w,h = self.helptxt.GetSize()
2268            if h > oh:
2269                self.helpwin.GetSizer().Fit(self.helpwin)
2270        except: # error posting help, ignore
2271            return
2272
2273def CIF2dict(cf):
2274    '''copy the contents of a CIF out from a PyCifRW block object
2275    into a dict
2276
2277    :returns: cifblk, loopstructure where cifblk is a dict with
2278      CIF items and loopstructure is a list of lists that defines
2279      which items are in which loops.
2280    '''
2281    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
2282    loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
2283    dblk = {}
2284    for item in cf[blk].keys(): # make a copy of all the items in the block
2285        dblk[item] = cf[blk][item]
2286    return dblk,loopstructure
2287
2288def dict2CIF(dblk,loopstructure,blockname='Template'):
2289    '''Create a PyCifRW CIF object containing a single CIF
2290    block object from a dict and loop structure list.
2291
2292    :param dblk: a dict containing values for each CIF item
2293    :param list loopstructure: a list of lists containing the contents of
2294      each loop, as an example::
2295
2296         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2297
2298      this describes a CIF with this type of structure::
2299
2300        loop_ _a _b <a1> <b1> <a2> ...
2301        loop_ _c <c1> <c2>...
2302        loop _d_1 _d_2 _d_3 ...
2303
2304      Note that the values for each looped CIF item, such as _a,
2305      are contained in a list, for example as cifblk["_a"]
2306
2307    :param str blockname: an optional name for the CIF block.
2308      Defaults to 'Template'
2309
2310    :returns: the newly created PyCifRW CIF object
2311    '''
2312
2313    import CifFile as cif # PyCifRW from James Hester
2314    # compile a 'list' of items in loops
2315    loopnames = set()
2316    for i in loopstructure:
2317        loopnames |= set(i)
2318    # create a new block
2319    newblk = cif.CifBlock()
2320    # add the looped items
2321    for keys in loopstructure:
2322        vals = []
2323        for key in keys:
2324            vals.append(dblk[key])
2325        newblk.AddCifItem(([keys],[vals]))
2326    # add the non-looped items
2327    for item in dblk:
2328        if item in loopnames: continue
2329        newblk[item] = dblk[item]
2330    # create a CIF and add the block
2331    newcf = cif.CifFile()
2332    newcf[blockname] = newblk   
2333    return newcf
2334
2335
2336class EditCIFtemplate(wx.Dialog):
2337    '''Create a dialog for editing a CIF template. The edited information is
2338    placed in cifblk. If the CIF is saved as a file, the name of that file
2339    is saved as ``self.newfile``.
2340   
2341    :param wx.Frame parent: parent frame or None
2342    :param cifblk: dict or PyCifRW block containing values for each CIF item
2343    :param list loopstructure: a list of lists containing the contents of
2344      each loop, as an example::
2345
2346         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2347
2348      this describes a CIF with this type of structure::
2349
2350        loop_ _a _b <a1> <b1> <a2> ...
2351        loop_ _c <c1> <c2>...
2352        loop _d_1 _d_2 _d_3 ...
2353
2354      Note that the values for each looped CIF item, such as _a,
2355      are contained in a list, for example as cifblk["_a"]
2356     
2357    :param str defaultname: specifies the default file name to be used for
2358      saving the CIF.
2359    '''
2360    def __init__(self,parent,cifblk,loopstructure,defaultname):
2361        OKbuttons = []
2362        self.cifblk = cifblk
2363        self.loopstructure = loopstructure
2364        self.newfile = None
2365        self.defaultname = defaultname       
2366        global CIFdic  # once this is loaded, keep it around
2367        if CIFdic is None:
2368            CIFdic = LoadCIFdic()
2369        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2370
2371        # define widgets that will be needed during panel creation
2372        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
2373        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
2374        OKbuttons.append(savebtn)
2375        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
2376        OKbtn = wx.Button(self, wx.ID_OK, "Use")
2377        OKbtn.SetDefault()
2378        OKbuttons.append(OKbtn)
2379
2380        self.SetTitle('Edit items in CIF template')
2381        vbox = wx.BoxSizer(wx.VERTICAL)
2382        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
2383        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2384        G2G.HorizontalLine(vbox,self)
2385        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
2386        G2G.HorizontalLine(vbox,self)
2387        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2388        btn = wx.Button(self, wx.ID_CANCEL)
2389        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
2390        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
2391        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
2392        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2393        self.SetSizer(vbox)
2394        vbox.Fit(self)
2395    def Post(self):
2396        '''Display the dialog
2397       
2398        :returns: True unless Cancel has been pressed.
2399        '''
2400        return (self.ShowModal() == wx.ID_OK)
2401    def _onSave(self,event):
2402        'Save CIF entries in a template file'
2403        pth = G2G.GetExportPath(self.G2frame)
2404        dlg = wx.FileDialog(
2405            self, message="Save as CIF template",
2406            defaultDir=pth,
2407            defaultFile=self.defaultname,
2408            wildcard="CIF (*.cif)|*.cif",
2409            style=wx.SAVE)
2410        val = (dlg.ShowModal() == wx.ID_OK)
2411        fil = dlg.GetPath()
2412        dlg.Destroy()
2413        if val: # ignore a Cancel button
2414            fil = os.path.splitext(fil)[0]+'.cif' # force extension
2415            fp = open(fil,'w')
2416            newcf = dict2CIF(self.cifblk,self.loopstructure)
2417            fp.write(newcf.WriteOut())
2418            fp.close()
2419            self.newfile = fil
2420            self.EndModal(wx.ID_OK)
2421
2422class EditCIFpanel(wxscroll.ScrolledPanel):
2423    '''Creates a scrolled panel for editing CIF template items
2424
2425    :param wx.Frame parent: parent frame where panel will be placed
2426    :param cifblk: dict or PyCifRW block containing values for each CIF item
2427    :param list loopstructure: a list of lists containing the contents of
2428      each loop, as an example::
2429
2430         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2431
2432      this describes a CIF with this type of structure::
2433
2434        loop_ _a _b <a1> <b1> <a2> ...
2435        loop_ _c <c1> <c2>...
2436        loop _d_1 _d_2 _d_3 ...
2437
2438      Note that the values for each looped CIF item, such as _a,
2439      are contained in a list, for example as cifblk["_a"]
2440
2441    :param dict cifdic: optional CIF dictionary definitions
2442    :param list OKbuttons: A list of wx.Button objects that should
2443      be disabled when information in the CIF is invalid
2444    :param (other): optional keyword parameters for wx.ScrolledPanel
2445    '''
2446    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
2447        self.parent = parent
2448        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
2449        self.vbox = None
2450        self.AddDict = None
2451        self.cifdic = cifdic
2452        self.cifblk = cifblk
2453        self.loops = loopstructure
2454        self.parent = parent
2455        self.LayoutCalled = False
2456        self.parentOKbuttons = OKbuttons
2457        self.ValidatedControlsList = []
2458        self._fill()
2459    def _fill(self):
2460        'Fill the scrolled panel with widgets for each CIF item'
2461        wx.BeginBusyCursor()
2462        self.AddDict = {}
2463        self.ValidatedControlsList = []
2464        # delete any only contents
2465        if self.vbox:
2466            self.vbox.DeleteWindows()
2467            self.vbox = None
2468            self.Update()
2469        vbox = wx.BoxSizer(wx.VERTICAL)
2470        self.vbox = vbox
2471        # compile a 'list' of items in loops
2472        loopnames = set()
2473        for i in self.loops:
2474            loopnames |= set(i)
2475        # post the looped CIF items
2476        for lnum,lp in enumerate(self.loops):
2477            hbox = wx.BoxSizer(wx.HORIZONTAL)
2478            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
2479            vbox.Add(hbox)
2480            but = wx.Button(self,wx.ID_ANY,"Add row")
2481            self.AddDict[but]=lnum
2482           
2483            hbox.Add(but)           
2484            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
2485            fbox = wx.GridBagSizer(0, 0)
2486            vbox.Add(fbox)
2487            rows = 0
2488            for i,item in enumerate(lp):
2489                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
2490                fbox.Add(txt,(0,i+1))
2491                # if self.cifdic.get(item):
2492                #     df = self.cifdic[item].get('_definition')
2493                #     if df:
2494                #         txt.SetToolTipString(G2IO.trim(df))
2495                #         but = CIFdefHelp(self,
2496                #                          "Definition for "+item+":\n\n"+G2IO.trim(df),
2497                #                          self.parent,
2498                #                          self.parent.helptxt)
2499                #         fbox.Add(but,(1,i+1),flag=wx.ALIGN_CENTER)
2500                for j,val in enumerate(self.cifblk[item]):
2501                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
2502                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
2503                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
2504                if self.cifdic.get(item):
2505                    df = self.cifdic[item].get('_definition')
2506                    if df:
2507                        txt.SetToolTipString(G2IO.trim(df))
2508                        but = CIFdefHelp(self,
2509                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2510                                         self.parent,
2511                                         self.parent.helptxt)
2512                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
2513                rows = max(rows,len(self.cifblk[item]))
2514            for i in range(rows):
2515                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
2516                fbox.Add(txt,(i+2,0))
2517            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
2518            vbox.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2519               
2520        # post the non-looped CIF items
2521        for item in sorted(self.cifblk.keys()):
2522            if item not in loopnames:
2523                hbox = wx.BoxSizer(wx.HORIZONTAL)
2524                vbox.Add(hbox)
2525                txt = wx.StaticText(self,wx.ID_ANY,item)
2526                hbox.Add(txt)
2527                ent = self.CIFEntryWidget(self.cifblk,item,item)
2528                hbox.Add(ent)
2529                if self.cifdic.get(item):
2530                    df = self.cifdic[item].get('_definition')
2531                    if df:
2532                        txt.SetToolTipString(G2IO.trim(df))
2533                        but = CIFdefHelp(self,
2534                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2535                                         self.parent,
2536                                         self.parent.helptxt)
2537                        hbox.Add(but,0,wx.ALL,2)
2538        self.SetSizer(vbox)
2539        #vbox.Fit(self.parent)
2540        self.SetAutoLayout(1)
2541        self.SetupScrolling()
2542        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
2543        self.Layout()
2544        wx.EndBusyCursor()
2545    def OnLayoutNeeded(self,event):
2546        '''Called when an update of the panel layout is needed. Calls
2547        self.DoLayout after the current operations are complete using
2548        CallAfter. This is called only once, according to flag
2549        self.LayoutCalled, which is cleared in self.DoLayout.
2550        '''
2551        if self.LayoutCalled: return # call already queued
2552        wx.CallAfter(self.DoLayout) # queue a call
2553        self.LayoutCalled = True
2554    def DoLayout(self):
2555        '''Update the Layout and scroll bars for the Panel. Clears
2556        self.LayoutCalled so that next change to panel can
2557        request a new update
2558        '''
2559        wx.BeginBusyCursor()
2560        self.Layout()
2561        self.SetupScrolling()
2562        wx.EndBusyCursor()
2563        self.LayoutCalled = False
2564    def OnAddRow(self,event):
2565        'add a row to a loop'
2566        lnum = self.AddDict.get(event.GetEventObject())
2567        if lnum is None: return
2568        for item in self.loops[lnum]:
2569            self.cifblk[item].append('?')
2570        self._fill()
2571
2572    def ControlOKButton(self,setvalue):
2573        '''Enable or Disable the OK button(s) for the dialog. Note that this is
2574        passed into the ValidatedTxtCtrl for use by validators.
2575
2576        :param bool setvalue: if True, all entries in the dialog are
2577          checked for validity. The first invalid control triggers
2578          disabling of buttons.
2579          If False then the OK button(s) are disabled with no checking
2580          of the invalid flag for each control.
2581        '''
2582        if setvalue: # turn button on, do only if all controls show as valid
2583            for ctrl in self.ValidatedControlsList:
2584                if ctrl.invalid:
2585                    for btn in self.parentOKbuttons:
2586                        btn.Disable()
2587                    return
2588            else:
2589                for btn in self.parentOKbuttons:
2590                    btn.Enable()
2591        else:
2592            for btn in self.parentOKbuttons:
2593                btn.Disable()
2594       
2595    def CIFEntryWidget(self,dct,item,dataname):
2596        '''Create an entry widget for a CIF item. Use a validated entry for numb values
2597        where int is required when limits are integers and floats otherwise.
2598        At present this does not allow entry of the special CIF values of "." and "?" for
2599        numerical values and highlights them as invalid.
2600        Use a selection widget when there are specific enumerated values for a string.       
2601        '''
2602        if self.cifdic.get(dataname):
2603            if self.cifdic[dataname].get('_enumeration'):
2604                values = ['?']+self.cifdic[dataname]['_enumeration']
2605                choices = ['undefined']
2606                for i in self.cifdic[dataname].get('_enumeration_detail',values):
2607                    choices.append(G2IO.trim(i))
2608                ent = G2G.EnumSelector(self, dct, item, choices, values, size=(200, -1))
2609                return ent
2610            if self.cifdic[dataname].get('_type') == 'numb':
2611                mn = None
2612                mx = None
2613                hint = int
2614                if self.cifdic[dataname].get('_enumeration_range'):
2615                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
2616                    if '.' in rng[0] or '.' in rng[1]: hint = float
2617                    if rng[0]: mn = hint(rng[0])
2618                    if rng[1]: mx = hint(rng[1])
2619                    ent = G2G.ValidatedTxtCtrl(
2620                        self,dct,item,typeHint=hint,min=mn,max=mx,
2621                        CIFinput=True,ASCIIonly=True,
2622                        OKcontrol=self.ControlOKButton)
2623                    self.ValidatedControlsList.append(ent)
2624                    return ent
2625        rw1 = rw.ResizeWidget(self)
2626        ent = G2G.ValidatedTxtCtrl(
2627            rw1,dct,item,size=(100, 20),
2628            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
2629            CIFinput=True,ASCIIonly=True,
2630            OKcontrol=self.ControlOKButton)
2631        self.ValidatedControlsList.append(ent)
2632        return rw1
2633
2634class CIFtemplateSelect(wx.BoxSizer):
2635    '''Create a set of buttons to show, select and edit a CIF template
2636   
2637    :param frame: wx.Frame object of parent
2638    :param panel: wx.Panel object where widgets should be placed
2639    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
2640      the type of template
2641    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
2642      "CIF_template" will be used to store either a list or a string.
2643      If a list, it will contain a dict and a list defining loops. If
2644      an str, it will contain a file name.   
2645    :param function repaint: reference to a routine to be called to repaint
2646      the frame after a change has been made
2647    :param str title: A line of text to show at the top of the window
2648    :param str defaultname: specifies the default file name to be used for
2649      saving the CIF.
2650    '''
2651    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
2652        wx.BoxSizer.__init__(self,wx.VERTICAL)
2653        self.cifdefs = frame
2654        self.dict = G2dict
2655        self.repaint = repaint
2656        templateDefName = 'template_'+tmplate+'.cif'
2657        self.CIF = G2dict.get("CIF_template")
2658        if defaultname:
2659            self.defaultname = defaultname.encode('ascii','replace').strip().replace(' ','_')
2660            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
2661            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
2662        else:
2663            self.defaultname = ''
2664           
2665        txt = wx.StaticText(panel,wx.ID_ANY,title)
2666        self.Add(txt,0,wx.ALIGN_CENTER)
2667        # change font on title
2668        txtfnt = txt.GetFont()
2669        txtfnt.SetWeight(wx.BOLD)
2670        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
2671        txt.SetFont(txtfnt)
2672        self.Add((-1,3))
2673
2674        if not self.CIF: # empty or None
2675            for pth in [os.getcwd()]+sys.path:
2676                fil = os.path.join(pth,self.defaultname)
2677                if os.path.exists(fil) and self.defaultname:
2678                    self.CIF = fil
2679                    CIFtxt = "Template: "+self.defaultname
2680                    break
2681            else:
2682                for pth in sys.path:
2683                    fil = os.path.join(pth,templateDefName)
2684                    if os.path.exists(fil):
2685                        self.CIF = fil
2686                        CIFtxt = "Template: "+templateDefName
2687                        break
2688                else:
2689                    print("Default CIF template "+self.defaultname+' not found in path!')
2690                    self.CIF = None
2691                    CIFtxt = "none! (No template found)"
2692        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
2693            if not os.path.exists(self.CIF):
2694                print("Error: template file has disappeared: "+self.CIF)
2695                self.CIF = None
2696                CIFtxt = "none! (file not found)"
2697            else:
2698                if len(self.CIF) < 50:
2699                    CIFtxt = "File: "+self.CIF
2700                else:
2701                    CIFtxt = "File: ..."+self.CIF[-50:]
2702        else:
2703            CIFtxt = "Template is customized"
2704        # show template source
2705        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
2706        # show str, button to select file; button to edit (if CIF defined)
2707        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
2708        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
2709        hbox =  wx.BoxSizer(wx.HORIZONTAL)
2710        hbox.Add(but,0,0,2)
2711        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
2712        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
2713        if self.CIF is None: but.Disable() # nothing to edit!
2714        hbox.Add(but,0,0,2)
2715        self.Add(hbox)
2716    def _onGetTemplateFile(self,event):
2717        'select a template file'
2718        pth = G2G.GetImportPath(self.G2frame)
2719        if not pth: pth = '.'
2720        dlg = wx.FileDialog(
2721            self.cifdefs, message="Read CIF template file",
2722            defaultDir=pth,
2723            defaultFile=self.defaultname,
2724            wildcard="CIF (*.cif)|*.cif",
2725            style=wx.OPEN)
2726        ret = dlg.ShowModal()
2727        fil = dlg.GetPath()
2728        dlg.Destroy()
2729        if ret == wx.ID_OK:
2730            try:
2731                cf = G2IO.ReadCIF(fil)
2732                if len(cf.keys()) == 0:
2733                    raise Exception,"No CIF data_ blocks found"
2734                if len(cf.keys()) != 1:
2735                    raise Exception, 'Error, CIF Template has more than one block: '+fil
2736                self.dict["CIF_template"] = fil
2737            except Exception as err:
2738                print('\nError reading CIF: '+fil)
2739                dlg = wx.MessageDialog(self.cifdefs,
2740                                   'Error reading CIF '+fil,
2741                                   'Error in CIF file',
2742                                   wx.OK)
2743                dlg.ShowModal()
2744                dlg.Destroy()
2745                print(err.message)
2746                return
2747            self.repaint() #EditCIFDefaults()
2748
2749    def _onEditTemplateContents(self,event):
2750        'Called to edit the contents of a CIF template'
2751        if type(self.CIF) is list or  type(self.CIF) is tuple:
2752            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
2753        else:
2754            cf = G2IO.ReadCIF(self.CIF)
2755            dblk,loopstructure = CIF2dict(cf)
2756        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
2757        val = dlg.Post()
2758        if val:
2759            if dlg.newfile: # results saved in file
2760                self.dict["CIF_template"] = dlg.newfile
2761            else:
2762                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
2763            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
2764        else:
2765            dlg.Destroy()       
2766
2767#===============================================================================
2768# end of misc CIF utilities
2769#===============================================================================
Note: See TracBrowser for help on using the repository browser.