source: trunk/exports/G2export_CIF.py @ 1130

Last change on this file since 1130 was 1130, checked in by toby, 8 years ago

CIF export: use G2 T & P values, fix profile inclusion to show all hists

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