source: trunk/G2cif.py @ 1080

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

revise export menu; add multiple selection to G2gd.ItemSelector?

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