source: trunk/exports/G2export_CIF.py @ 1123

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

add import and export routines to sphinx

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