source: trunk/exports/G2cif.py @ 1077

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

cleanup plot & svn bugs; set missing keywords; CIF export done; update docs

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