source: trunk/exports/G2export_CIF.py @ 1127

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

more export changes to catch up to data structure updates; update docs; change RF2 to use superscript since 2 is not working -- at least on Mac

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 114.8 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2013-10-29 16:42:23 +0000 (Tue, 29 Oct 2013) $
5# $Author: toby $
6# $Revision: 1127 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 1127 2013-10-29 16:42:23Z 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: 1127 $")
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            # create a list of atoms, but skip atoms with zero occupancy
703            xyz = []
704            fpfx = str(phasedict['pId'])+'::Afrac:'       
705            for i,atom in enumerate(Atoms):
706                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
707                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
708            if 'DisAglCtls' not in generalData:
709                # should not happen, since DisAglDialog should be called
710                # for all phases before getting here
711                dlg = G2gd.DisAglDialog(
712                    self.G2frame,
713                    {},
714                    generalData)
715                if dlg.ShowModal() == wx.ID_OK:
716                    generalData['DisAglCtls'] = dlg.GetData()
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(
739                    generalData['DisAglCtls'],
740                    DisAglData)
741            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
742                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
743                   
744            # loop over interatomic distances for this phase
745            WriteCIFitem('\n# MOLECULAR GEOMETRY')
746            WriteCIFitem('loop_' + 
747                         '\n   _geom_bond_atom_site_label_1' +
748                         '\n   _geom_bond_atom_site_label_2' + 
749                         '\n   _geom_bond_distance' + 
750                         '\n   _geom_bond_site_symmetry_1' + 
751                         '\n   _geom_bond_site_symmetry_2' + 
752                         '\n   _geom_bond_publ_flag')
753
754            for i in sorted(AtomLabels.keys()):
755                Dist = DistArray[i]
756                for D in Dist:
757                    line = '  '+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[D[0]],6)
758                    sig = D[4]
759                    if sig == 0: sig = -0.00009
760                    line += PutInCol(G2mth.ValEsd(D[3],sig,True),10)
761                    line += "  1_555 "
762                    line += " {:3d}_".format(D[2])
763                    for d in D[1]:
764                        line += "{:1d}".format(d+5)
765                    if DisAngSel.get((i,tuple(D[0:3]))):
766                        line += " no"
767                    else:
768                        line += " yes"
769                    WriteCIFitem(line)
770
771            # loop over interatomic angles for this phase
772            WriteCIFitem('\nloop_' + 
773                         '\n   _geom_angle_atom_site_label_1' + 
774                         '\n   _geom_angle_atom_site_label_2' + 
775                         '\n   _geom_angle_atom_site_label_3' + 
776                         '\n   _geom_angle' + 
777                         '\n   _geom_angle_site_symmetry_1' +
778                         '\n   _geom_angle_site_symmetry_2' + 
779                         '\n   _geom_angle_site_symmetry_3' + 
780                         '\n   _geom_angle_publ_flag')
781
782            for i in sorted(AtomLabels.keys()):
783                Dist = DistArray[i]
784                for k,j,tup in AngArray[i]:
785                    Dj = Dist[j]
786                    Dk = Dist[k]
787                    line = '  '+PutInCol(AtomLabels[Dj[0]],6)+PutInCol(AtomLabels[i],6)+PutInCol(AtomLabels[Dk[0]],6)
788                    sig = tup[1]
789                    if sig == 0: sig = -0.009
790                    line += PutInCol(G2mth.ValEsd(tup[0],sig,True),10)
791                    line += " {:3d}_".format(Dj[2])
792                    for d in Dj[1]:
793                        line += "{:1d}".format(d+5)
794                    line += "  1_555 "
795                    line += " {:3d}_".format(Dk[2])
796                    for d in Dk[1]:
797                        line += "{:1d}".format(d+5)
798                    key = (tuple(Dk[0:3]),i,tuple(Dj[0:3]))
799                    if DisAngSel.get(key):
800                        line += " no"
801                    else:
802                        line += " yes"
803                    WriteCIFitem(line)
804
805        def WritePhaseInfo(phasenam):
806            'Write out the phase information for the selected phase'
807            WriteCIFitem('\n# phase info for '+str(phasenam) + ' follows')
808            phasedict = self.Phases[phasenam] # pointer to current phase info           
809            WriteCIFitem('_pd_phase_name', phasenam)
810            cellList,cellSig = self.GetCell(phasenam)
811            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
812            names = ['length_a','length_b','length_c',
813                     'angle_alpha','angle_beta ','angle_gamma',
814                     'volume']
815            prevsig = 0
816            for lbl,defsig,val,sig in zip(names,defsigL,cellList,cellSig):
817                if sig:
818                    txt = G2mth.ValEsd(val,sig)
819                    prevsig = -sig # use this as the significance for next value
820                else:
821                    txt = G2mth.ValEsd(val,min(defsig,prevsig),True)
822                WriteCIFitem('_cell_'+lbl,txt)
823                   
824            WriteCIFitem('_symmetry_cell_setting',
825                         phasedict['General']['SGData']['SGSys'])
826
827            spacegroup = phasedict['General']['SGData']['SpGrp'].strip()
828            # regularize capitalization and remove trailing H/R
829            spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
830            WriteCIFitem('_symmetry_space_group_name_H-M',spacegroup)
831
832            # generate symmetry operations including centering and center of symmetry
833            SymOpList,offsetList,symOpList,G2oprList = G2spc.AllOps(
834                phasedict['General']['SGData'])
835            WriteCIFitem('loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
836            for i,op in enumerate(SymOpList,start=1):
837                WriteCIFitem('   {:3d}  {:}'.format(i,op.lower()))
838
839            # loop over histogram(s) used in this phase
840            if not oneblock and not self.quickmode:
841                # report pointers to the histograms used in this phase
842                histlist = []
843                for hist in self.Phases[phasenam]['Histograms']:
844                    if self.Phases[phasenam]['Histograms'][hist]['Use']:
845                        if phasebyhistDict.get(hist):
846                            phasebyhistDict[hist].append(phasenam)
847                        else:
848                            phasebyhistDict[hist] = [phasenam,]
849                        blockid = datablockidDict.get(hist)
850                        if not blockid:
851                            print("Internal error: no block for data. Phase "+str(
852                                phasenam)+" histogram "+str(hist))
853                            histlist = []
854                            break
855                        histlist.append(blockid)
856
857                if len(histlist) == 0:
858                    WriteCIFitem('# Note: phase has no associated data')
859
860            # report atom params
861            if phasedict['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
862                WriteAtomsNuclear(phasenam)
863            else:
864                raise Exception,"no export for "+str(phasedict['General']['Type'])+" coordinates implemented"
865            # report cell contents
866            WriteComposition(phasenam)
867            if not self.quickmode and phasedict['General']['Type'] == 'nuclear':      # report distances and angles
868                WriteDistances(phasenam,SymOpList,offsetList,symOpList,G2oprList)
869               
870        def Yfmt(ndec,val):
871            'Format intensity values'
872            out = ("{:."+str(ndec)+"f}").format(val)
873            out = out.rstrip('0')  # strip zeros to right of decimal
874            return out.rstrip('.')  # and decimal place when not needed
875           
876        def WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,nRefSets=1):
877            'Write reflection statistics'
878            WriteCIFitem('_reflns_number_total', str(refcount))
879            if hklmin is not None and nRefSets == 1: # hkl range has no meaning with multiple phases
880                WriteCIFitem('_reflns_limit_h_min', str(int(hklmin[0])))
881                WriteCIFitem('_reflns_limit_h_max', str(int(hklmax[0])))
882                WriteCIFitem('_reflns_limit_k_min', str(int(hklmin[1])))
883                WriteCIFitem('_reflns_limit_k_max', str(int(hklmax[1])))
884                WriteCIFitem('_reflns_limit_l_min', str(int(hklmin[2])))
885                WriteCIFitem('_reflns_limit_l_max', str(int(hklmax[2])))
886            if hklmin is not None:
887                WriteCIFitem('_reflns_d_resolution_low  ', G2mth.ValEsd(dmax,-0.009))
888                WriteCIFitem('_reflns_d_resolution_high ', G2mth.ValEsd(dmin,-0.009))
889
890        def WritePowderData(histlbl):
891            'Write out the selected powder diffraction histogram info'
892            histblk = self.Histograms[histlbl]
893            inst = histblk['Instrument Parameters'][0]
894            hId = histblk['hId']
895            pfx = ':' + str(hId) + ':'
896           
897            if 'Lam1' in inst:
898                ratio = self.parmDict.get('I(L2)/I(L1)',inst['I(L2)/I(L1)'][1])
899                sratio = self.sigDict.get('I(L2)/I(L1)',-0.0009)
900                lam1 = self.parmDict.get('Lam1',inst['Lam1'][1])
901                slam1 = self.sigDict.get('Lam1',-0.00009)
902                lam2 = self.parmDict.get('Lam2',inst['Lam2'][1])
903                slam2 = self.sigDict.get('Lam2',-0.00009)
904                # always assume Ka1 & Ka2 if two wavelengths are present
905                WriteCIFitem('_diffrn_radiation_type','K\\a~1,2~')
906                WriteCIFitem('loop_' + 
907                             '\n   _diffrn_radiation_wavelength' +
908                             '\n   _diffrn_radiation_wavelength_wt' + 
909                             '\n   _diffrn_radiation_wavelength_id')
910                WriteCIFitem('  ' + PutInCol(G2mth.ValEsd(lam1,slam1),15)+
911                             PutInCol('1.0',15) + 
912                             PutInCol('1',5))
913                WriteCIFitem('  ' + PutInCol(G2mth.ValEsd(lam2,slam2),15)+
914                             PutInCol(G2mth.ValEsd(ratio,sratio),15)+
915                             PutInCol('2',5))               
916            else:
917                lam1 = self.parmDict.get('Lam',inst['Lam'][1])
918                slam1 = self.sigDict.get('Lam',-0.00009)
919                WriteCIFitem('_diffrn_radiation_wavelength',G2mth.ValEsd(lam1,slam1))
920
921            if not oneblock:
922                if not phasebyhistDict.get(histlbl):
923                    WriteCIFitem('\n# No phases associated with this data set')
924                else:
925                    WriteCIFitem('\n# PHASE TABLE')
926                    WriteCIFitem('loop_' +
927                                 '\n   _pd_phase_id' + 
928                                 '\n   _pd_phase_block_id' + 
929                                 '\n   _pd_phase_mass_%')
930                    wtFrSum = 0.
931                    for phasenam in phasebyhistDict.get(histlbl):
932                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
933                        General = self.Phases[phasenam]['General']
934                        wtFrSum += hapData['Scale'][0]*General['Mass']
935
936                    for phasenam in phasebyhistDict.get(histlbl):
937                        hapData = self.Phases[phasenam]['Histograms'][histlbl]
938                        General = self.Phases[phasenam]['General']
939                        wtFr = hapData['Scale'][0]*General['Mass']/wtFrSum
940                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
941                        if pfx+'Scale' in self.sigDict:
942                            sig = self.sigDict[pfx+'Scale']*wtFr/hapData['Scale'][0]
943                        else:
944                            sig = -0.0001
945                        WriteCIFitem(
946                            '  '+
947                            str(self.Phases[phasenam]['pId']) +
948                            '  '+datablockidDict[phasenam]+
949                            '  '+G2mth.ValEsd(wtFr,sig)
950                            )
951                    WriteCIFitem('loop_' +
952                                 '\n   _gsas_proc_phase_R_F_factor' +
953                                 '\n   _gsas_proc_phase_R_Fsqd_factor' +
954                                 '\n   _gsas_proc_phase_id' +
955                                 '\n   _gsas_proc_phase_block_id')
956                    for phasenam in phasebyhistDict.get(histlbl):
957                        pfx = str(self.Phases[phasenam]['pId'])+':'+str(hId)+':'
958                        WriteCIFitem(
959                            '  '+
960                            '  '+G2mth.ValEsd(histblk[pfx+'Rf']/100.,-.00009) +
961                            '  '+G2mth.ValEsd(histblk[pfx+'Rf^2']/100.,-.00009)+
962                            '  '+str(self.Phases[phasenam]['pId'])+
963                            '  '+datablockidDict[phasenam]
964                            )
965            else:
966                # single phase in this histogram
967                pfx = '0:'+str(hId)+':'
968                WriteCIFitem('_refine_ls_R_F_factor      ','%.5f'%(histblk[pfx+'Rf']/100.))
969                WriteCIFitem('_refine_ls_R_Fsqd_factor   ','%.5f'%(histblk[pfx+'Rf^2']/100.))
970               
971            WriteCIFitem('_pd_proc_ls_prof_R_factor   ','%.5f'%(histblk['R']/100.))
972            WriteCIFitem('_pd_proc_ls_prof_wR_factor  ','%.5f'%(histblk['wR']/100.))
973            WriteCIFitem('_gsas_proc_ls_prof_R_B_factor ','%.5f'%(histblk['Rb']/100.))
974            WriteCIFitem('_gsas_proc_ls_prof_wR_B_factor','%.5f'%(histblk['wRb']/100.))
975            WriteCIFitem('_pd_proc_ls_prof_wR_expected','%.5f'%(histblk['wRmin']/100.))
976
977            if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
978                WriteCIFitem('_diffrn_radiation_probe','x-ray')
979                pola = histblk['Instrument Parameters'][0].get('Polariz.')
980                if pola:
981                    pfx = ':' + str(hId) + ':'
982                    sig = self.sigDict.get(pfx+'Polariz.',-0.0009)
983                    txt = G2mth.ValEsd(pola[1],sig)
984                    WriteCIFitem('_diffrn_radiation_polarisn_ratio',txt)
985            elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
986                WriteCIFitem('_diffrn_radiation_probe','neutron')
987            # TOF (note that this may not be defined)
988            #if histblk['Instrument Parameters'][0]['Type'][1][2] == 'T':
989            #    WriteCIFitem('_pd_meas_2theta_fixed',text)
990           
991
992            # TODO: this will need help from Bob
993            #if not oneblock:
994            #WriteCIFitem('\n# SCATTERING FACTOR INFO')
995            #WriteCIFitem('loop_  _atom_type_symbol')
996            #if histblk['Instrument Parameters'][0]['Type'][1][1] == 'X':
997            #    WriteCIFitem('      _atom_type_scat_dispersion_real')
998            #    WriteCIFitem('      _atom_type_scat_dispersion_imag')
999            #    for lbl in ('a1','a2','a3', 'a4', 'b1', 'b2', 'b3', 'b4', 'c'):
1000            #        WriteCIFitem('      _atom_type_scat_Cromer_Mann_'+lbl)
1001            #elif histblk['Instrument Parameters'][0]['Type'][1][1] == 'N':
1002            #    WriteCIFitem('      _atom_type_scat_length_neutron')
1003            #WriteCIFitem('      _atom_type_scat_source')
1004
1005            WriteCIFitem('_pd_proc_ls_background_function',FormatBackground(histblk['Background'],histblk['hId']))
1006
1007            # TODO: this will need help from Bob
1008            #WriteCIFitem('_exptl_absorpt_process_details','?')
1009            #WriteCIFitem('_exptl_absorpt_correction_T_min','?')
1010            #WriteCIFitem('_exptl_absorpt_correction_T_max','?')
1011            #C extinction
1012            #WRITE(IUCIF,'(A)') '# Extinction correction'
1013            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_min',TEXT(1:10))
1014            #CALL WRVAL(IUCIF,'_gsas_exptl_extinct_corr_T_max',TEXT(11:20))
1015
1016            if not oneblock:                 # instrumental profile terms go here
1017                WriteCIFitem('_pd_proc_ls_profile_function', 
1018                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1019
1020            #refprx = '_refln.' # mm
1021            refprx = '_refln_' # normal
1022            WriteCIFitem('\n# STRUCTURE FACTOR TABLE')           
1023            # compute maximum intensity reflection
1024            Imax = 0
1025            for phasenam in histblk['Reflection Lists']:
1026                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1027                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
1028                I100 = scale*refList.T[8]*refList.T[11]
1029                #Icorr = np.array([refl[13] for refl in histblk['Reflection Lists'][phasenam]])[0]
1030                #FO2 = np.array([refl[8] for refl in histblk['Reflection Lists'][phasenam]])
1031                #I100 = scale*FO2*Icorr
1032                Imax = max(Imax,max(I100))
1033
1034            WriteCIFitem('loop_')
1035            if len(histblk['Reflection Lists'].keys()) > 1:
1036                WriteCIFitem('   _pd_refln_phase_id')
1037            WriteCIFitem('   ' + refprx + 'index_h' + 
1038                         '\n   ' + refprx + 'index_k' + 
1039                         '\n   ' + refprx + 'index_l' + 
1040                         '\n   ' + refprx + 'F_squared_meas' + 
1041                         '\n   ' + refprx + 'F_squared_calc' + 
1042                         '\n   ' + refprx + 'phase_calc' + 
1043                         '\n   _pd_refln_d_spacing')
1044            if Imax > 0:
1045                WriteCIFitem('   _gsas_i100_meas')
1046
1047            refcount = 0
1048            hklmin = None
1049            hklmax = None
1050            dmax = None
1051            dmin = None
1052            for phasenam in histblk['Reflection Lists']:
1053                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1054                phaseid = self.Phases[phasenam]['pId']
1055                refcount += len(histblk['Reflection Lists'][phasenam]['RefList'])
1056                for j,ref in enumerate(histblk['Reflection Lists'][phasenam]['RefList']):
1057                    if DEBUG:
1058                        print('DEBUG: skipping reflection list')
1059                        break
1060                    if hklmin is None:
1061                        hklmin = ref[0:3]
1062                        hklmax = ref[0:3]
1063                        dmax = dmin = ref[4]
1064                    if len(histblk['Reflection Lists'].keys()) > 1:
1065                        s = PutInCol(phaseid,2)
1066                    else:
1067                        s = ""
1068                    for i,hkl in enumerate(ref[0:3]):
1069                        hklmax[i] = max(hkl,hklmax[i])
1070                        hklmin[i] = min(hkl,hklmin[i])
1071                        s += PutInCol(int(hkl),4)
1072                    for I in ref[8:10]:
1073                        s += PutInCol(G2mth.ValEsd(I,-0.0009),10)
1074                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1075                    dmax = max(dmax,ref[4])
1076                    dmin = min(dmin,ref[4])
1077                    s += PutInCol(G2mth.ValEsd(ref[4],-0.009),8)
1078                    if Imax > 0:
1079                        s += PutInCol(G2mth.ValEsd(100.*I100[j]/Imax,-0.09),6)
1080                    WriteCIFitem("  "+s)
1081
1082            WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,len(histblk['Reflection Lists']))
1083            WriteCIFitem('\n# POWDER DATA TABLE')
1084            # is data fixed step? If the step varies by <0.01% treat as fixed step
1085            steps = histblk['Data'][0][1:] - histblk['Data'][0][:-1]
1086            if abs(max(steps)-min(steps)) > abs(max(steps))/10000.:
1087                fixedstep = False
1088            else:
1089                fixedstep = True
1090
1091            if fixedstep: # and not TOF
1092                WriteCIFitem('_pd_meas_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0],-0.00009))
1093                WriteCIFitem('_pd_meas_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1],-0.00009))
1094                WriteCIFitem('_pd_meas_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1095                # zero correct, if defined
1096                zero = None
1097                zerolst = histblk['Instrument Parameters'][0].get('Zero')
1098                if zerolst: zero = zerolst[1]
1099                zero = self.parmDict.get('Zero',zero)
1100                if zero:
1101                    WriteCIFitem('_pd_proc_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0]-zero,-0.00009))
1102                    WriteCIFitem('_pd_proc_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1]-zero,-0.00009))
1103                    WriteCIFitem('_pd_proc_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1104               
1105            if zero:
1106                WriteCIFitem('_pd_proc_number_of_points', str(len(histblk['Data'][0])))
1107            else:
1108                WriteCIFitem('_pd_meas_number_of_points', str(len(histblk['Data'][0])))
1109            WriteCIFitem('\nloop_')
1110            #            WriteCIFitem('   _pd_proc_d_spacing') # need easy way to get this
1111            if not fixedstep:
1112                if zero:
1113                    WriteCIFitem('   _pd_proc_2theta_corrected')
1114                else:
1115                    WriteCIFitem('   _pd_meas_2theta_scan')
1116            # at least for now, always report weights.
1117            #if countsdata:
1118            #    WriteCIFitem('   _pd_meas_counts_total')
1119            #else:
1120            WriteCIFitem('   _pd_meas_intensity_total')
1121            WriteCIFitem('   _pd_calc_intensity_total')
1122            WriteCIFitem('   _pd_proc_intensity_bkg_calc')
1123            WriteCIFitem('   _pd_proc_ls_weight')
1124            maxY = max(histblk['Data'][1].max(),histblk['Data'][3].max())
1125            if maxY < 0: maxY *= -10 # this should never happen, but...
1126            ndec = max(0,10-int(np.log10(maxY))-1) # 10 sig figs should be enough
1127            maxSU = histblk['Data'][2].max()
1128            if maxSU < 0: maxSU *= -1 # this should never happen, but...
1129            ndecSU = max(0,8-int(np.log10(maxSU))-1) # 8 sig figs should be enough
1130            lowlim,highlim = histblk['Limits'][1]
1131
1132            if DEBUG:
1133                print('DEBUG: skipping profile list')
1134            else:   
1135                for x,yobs,yw,ycalc,ybkg in zip(histblk['Data'][0],
1136                                                histblk['Data'][1],
1137                                                histblk['Data'][2],
1138                                                histblk['Data'][3],
1139                                                histblk['Data'][4]):
1140                    if lowlim <= x <= highlim:
1141                        pass
1142                    else:
1143                        yw = 0.0 # show the point is not in use
1144   
1145                    if fixedstep:
1146                        s = ""
1147                    else:
1148                        s = PutInCol(G2mth.ValEsd(x-zero,-0.00009),10)
1149                    s += PutInCol(Yfmt(ndec,yobs),12)
1150                    s += PutInCol(Yfmt(ndec,ycalc),12)
1151                    s += PutInCol(Yfmt(ndec,ybkg),11)
1152                    s += PutInCol(Yfmt(ndecSU,yw),9)
1153                    WriteCIFitem("  "+s)
1154
1155        def WriteSingleXtalData(histlbl):
1156            'Write out the selected single crystal histogram info'
1157            histblk = self.Histograms[histlbl]
1158
1159            #refprx = '_refln.' # mm
1160            refprx = '_refln_' # normal
1161
1162            WriteCIFitem('\n# STRUCTURE FACTOR TABLE')           
1163            WriteCIFitem('loop_' + 
1164                         '\n   ' + refprx + 'index_h' + 
1165                         '\n   ' + refprx + 'index_k' + 
1166                         '\n   ' + refprx + 'index_l' +
1167                         '\n   ' + refprx + 'F_squared_meas' + 
1168                         '\n   ' + refprx + 'F_squared_sigma' + 
1169                         '\n   ' + refprx + 'F_squared_calc' + 
1170                         '\n   ' + refprx + 'phase_calc'
1171                         )
1172
1173            hklmin = None
1174            hklmax = None
1175            dmax = None
1176            dmin = None
1177            refcount = len(histblk['Data']['RefList'])
1178            for ref in histblk['Data']['RefList']:
1179                s = "  "
1180                if hklmin is None:
1181                    hklmin = ref[0:3]
1182                    hklmax = ref[0:3]
1183                    dmax = dmin = ref[4]
1184                for i,hkl in enumerate(ref[0:3]):
1185                    hklmax[i] = max(hkl,hklmax[i])
1186                    hklmin[i] = min(hkl,hklmin[i])
1187                    s += PutInCol(int(hkl),4)
1188                import sys
1189                if ref[5] == 0.0:
1190                    s += PutInCol(G2mth.ValEsd(ref[8],0),12)
1191                    s += PutInCol('.',10)
1192                    s += PutInCol(G2mth.ValEsd(ref[9],0),12)
1193                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1194                else:
1195                    sig = ref[6] * ref[8] / ref[5]
1196                    s += PutInCol(G2mth.ValEsd(ref[8],-abs(sig/10)),12)
1197                    s += PutInCol(G2mth.ValEsd(sig,-abs(sig)/10.),10)
1198                    s += PutInCol(G2mth.ValEsd(ref[9],-abs(sig/10)),12)
1199                s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1200                dmax = max(dmax,ref[4])
1201                dmin = min(dmin,ref[4])
1202                WriteCIFitem(s)
1203            if not self.quickmode: # statistics only in a full CIF
1204                WriteReflStat(refcount,hklmin,hklmax,dmin,dmax)
1205                hId = histblk[0]['hId']
1206                pfx = '0:'+str(hId)+':'
1207                WriteCIFitem('_reflns_wR_factor_obs    ','%.4f'%(histblk[0]['wR']/100.))
1208                WriteCIFitem('_reflns_R_F_factor_obs   ','%.4f'%(histblk[0][pfx+'Rf']/100.))
1209                WriteCIFitem('_reflns_R_Fsqd_factor_obs','%.4f'%(histblk[0][pfx+'Rf^2']/100.))
1210        def EditAuthor(event=None):
1211            'dialog to edit the CIF author info'
1212            'Edit the CIF author name'
1213            dlg = G2gd.SingleStringDialog(self.G2frame,
1214                                          'Get CIF Author',
1215                                          'Provide CIF Author name (Last, First)',
1216                                          value=self.author)
1217            if not dlg.Show():
1218                dlg.Destroy()
1219                return False  # cancel was pressed
1220            self.author = dlg.GetValue()
1221            dlg.Destroy()
1222            try:
1223                self.OverallParms['Controls']["Author"] = self.author # save for future
1224            except KeyError:
1225                pass
1226            return True
1227        def EditInstNames(event=None):
1228            'Provide a dialog for editing instrument names'
1229            dictlist = []
1230            keylist = []
1231            lbllist = []
1232            for hist in self.Histograms:
1233                if hist.startswith("PWDR"): 
1234                    key2 = "Sample Parameters"
1235                    d = self.Histograms[hist][key2]
1236                elif hist.startswith("HKLF"): 
1237                    key2 = "Instrument Parameters"
1238                    d = self.Histograms[hist][key2][0]
1239                   
1240                lbllist.append(hist)
1241                dictlist.append(d)
1242                keylist.append('InstrName')
1243                instrname = d.get('InstrName')
1244                if instrname is None:
1245                    d['InstrName'] = ''
1246            return G2gd.CallScrolledMultiEditor(
1247                self.G2frame,dictlist,keylist,
1248                prelbl=range(1,len(dictlist)+1),
1249                postlbl=lbllist,
1250                title='Instrument names',
1251                header="Edit instrument names. Note that a non-blank\nname is required for all histograms",
1252                CopyButton=True)
1253           
1254        def EditRanges(event):
1255            '''Edit the bond distance/angle search range; phase is determined from
1256            a pointer placed in the button object (.phasedict) that references the
1257            phase dictionary
1258            '''
1259            but = event.GetEventObject()
1260            phasedict = but.phasedict
1261            dlg = G2gd.DisAglDialog(
1262                self.G2frame,
1263                phasedict['General']['DisAglCtls'], # edited
1264                phasedict['General'], # defaults
1265                )
1266            if dlg.ShowModal() == wx.ID_OK:
1267                phasedict['General']['DisAglCtls'] = dlg.GetData()
1268            dlg.Destroy()
1269           
1270        def EditCIFDefaults():
1271            '''Fills the CIF Defaults window with controls for editing various CIF export
1272            parameters (mostly related to templates).
1273            '''
1274            self.cifdefs.DestroyChildren()
1275            self.cifdefs.SetTitle('Edit CIF settings')
1276            vbox = wx.BoxSizer(wx.VERTICAL)
1277            vbox.Add(wx.StaticText(self.cifdefs, wx.ID_ANY,'Creating file '+str(self.filename)))
1278            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit CIF Author')
1279            but.Bind(wx.EVT_BUTTON,EditAuthor)
1280            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1281            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit Instrument Name(s)')
1282            but.Bind(wx.EVT_BUTTON,EditInstNames)
1283            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1284            cpnl = wxscroll.ScrolledPanel(self.cifdefs,size=(300,300))
1285            cbox = wx.BoxSizer(wx.VERTICAL)
1286            G2gd.HorizontalLine(cbox,cpnl)         
1287            cbox.Add(
1288                CIFtemplateSelect(self.cifdefs,
1289                                  cpnl,'publ',self.OverallParms['Controls'],
1290                                  EditCIFDefaults,
1291                                  "Publication (overall) template",
1292                                  ),
1293                0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1294            for phasenam in sorted(self.Phases.keys()):
1295                G2gd.HorizontalLine(cbox,cpnl)         
1296                title = 'Phase '+phasenam
1297                phasedict = self.Phases[phasenam] # pointer to current phase info           
1298                cbox.Add(
1299                    CIFtemplateSelect(self.cifdefs,
1300                                      cpnl,'phase',phasedict['General'],
1301                                      EditCIFDefaults,
1302                                      title,
1303                                      phasenam),
1304                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1305                cpnl.SetSizer(cbox)
1306                if phasedict['General']['Type'] == 'nuclear': 
1307                    but = wx.Button(cpnl, wx.ID_ANY,'Edit distance/angle ranges')
1308                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1309                    cbox.Add((-1,2))
1310                    but.phasedict = self.Phases[phasenam]  # set a pointer to current phase info
1311                    but.Bind(wx.EVT_BUTTON,EditRanges)     # phase bond/angle ranges
1312                    but = wx.Button(cpnl, wx.ID_ANY,'Set distance/angle publication flags')
1313                    but.phase = phasenam  # set a pointer to current phase info     
1314                    but.Bind(wx.EVT_BUTTON,SelectDisAglFlags)     # phase bond/angle ranges
1315                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1316                cbox.Add((-1,2))
1317            for i in sorted(self.powderDict.keys()):
1318                G2gd.HorizontalLine(cbox,cpnl)         
1319                hist = self.powderDict[i]
1320                histblk = self.Histograms[hist]
1321                title = 'Powder dataset '+hist[5:]
1322                cbox.Add(
1323                    CIFtemplateSelect(self.cifdefs,
1324                                      cpnl,'powder',histblk["Sample Parameters"],
1325                                      EditCIFDefaults,
1326                                      title,
1327                                      histblk["Sample Parameters"]['InstrName']),
1328                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1329            for i in sorted(self.xtalDict.keys()):
1330                G2gd.HorizontalLine(cbox,cpnl)         
1331                hist = self.xtalDict[i]
1332                histblk = self.Histograms[hist]
1333                title = 'Single Xtal dataset '+hist[5:]
1334                cbox.Add(
1335                    CIFtemplateSelect(self.cifdefs,
1336                                      cpnl,'single',histblk["Instrument Parameters"][0],
1337                                      EditCIFDefaults,
1338                                      title,
1339                                      histblk["Instrument Parameters"][0]['InstrName']),
1340                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1341            cpnl.SetSizer(cbox)
1342            cpnl.SetAutoLayout(1)
1343            cpnl.SetupScrolling()
1344            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1345            cpnl.Layout()
1346
1347            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1348            btnsizer = wx.StdDialogButtonSizer()
1349            btn = wx.Button(self.cifdefs, wx.ID_OK, "Create CIF")
1350            btn.SetDefault()
1351            btnsizer.AddButton(btn)
1352            btn = wx.Button(self.cifdefs, wx.ID_CANCEL)
1353            btnsizer.AddButton(btn)
1354            btnsizer.Realize()
1355            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1356            self.cifdefs.SetSizer(vbox)
1357            vbox.Fit(self.cifdefs)
1358            self.cifdefs.Layout()
1359           
1360        def OnToggleButton(event):
1361            'Respond to press of ToggleButton in SelectDisAglFlags'
1362            but = event.GetEventObject()
1363            if but.GetValue():                   
1364                but.DisAglSel[but.key] = True
1365            else:
1366                try:
1367                    del but.DisAglSel[but.key]
1368                except KeyError:
1369                    pass
1370        def keepTrue(event):
1371            event.GetEventObject().SetValue(True)
1372        def keepFalse(event):
1373            event.GetEventObject().SetValue(False)
1374               
1375        def SelectDisAglFlags(event):
1376            'Select Distance/Angle use flags for the selected phase'
1377            phasenam = event.GetEventObject().phase
1378            phasedict = self.Phases[phasenam]
1379            SymOpList,offsetList,symOpList,G2oprList = G2spc.AllOps(phasedict['General']['SGData'])
1380            generalData = phasedict['General']
1381            # create a dict for storing Pub flag for bonds/angles, if needed
1382            if phasedict['General'].get("DisAglHideFlag") is None:
1383                phasedict['General']["DisAglHideFlag"] = {}
1384            DisAngSel = phasedict['General']["DisAglHideFlag"]
1385
1386            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1387            cn = ct-1
1388            cfrac = cx+3
1389            DisAglData = {}
1390            # create a list of atoms, but skip atoms with zero occupancy
1391            xyz = []
1392            fpfx = str(phasedict['pId'])+'::Afrac:'       
1393            for i,atom in enumerate(phasedict['Atoms']):
1394                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
1395                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
1396            if 'DisAglCtls' not in generalData:
1397                # should not be used, since DisAglDialog should be called
1398                # for all phases before getting here
1399                dlg = G2gd.DisAglDialog(
1400                    self.cifdefs,
1401                    {},
1402                    generalData)
1403                if dlg.ShowModal() == wx.ID_OK:
1404                    generalData['DisAglCtls'] = dlg.GetData()
1405                else:
1406                    dlg.Destroy()
1407                    return
1408                dlg.Destroy()
1409            dlg = wx.Dialog(
1410                self.G2frame,
1411                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
1412            vbox = wx.BoxSizer(wx.VERTICAL)
1413            txt = wx.StaticText(dlg,wx.ID_ANY,'Searching distances for phase '+phasenam
1414                                +'\nPlease wait...')
1415            vbox.Add(txt,0,wx.ALL|wx.EXPAND)
1416            dlg.SetSizer(vbox)
1417            dlg.CenterOnParent()
1418            dlg.Show() # post "please wait"
1419            wx.BeginBusyCursor() # and change cursor
1420
1421            DisAglData['OrigAtoms'] = xyz
1422            DisAglData['TargAtoms'] = xyz
1423            SymOpList,offsetList,symOpList,G2oprList = G2spc.AllOps(
1424                generalData['SGData'])
1425
1426            xpandSGdata = generalData['SGData'].copy()
1427            xpandSGdata.update({'SGOps':symOpList,
1428                                'SGInv':False,
1429                                'SGLatt':'P',
1430                                'SGCen':np.array([[0, 0, 0]]),})
1431            DisAglData['SGData'] = xpandSGdata
1432
1433            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
1434            if 'pId' in phasedict:
1435                DisAglData['pId'] = phasedict['pId']
1436                DisAglData['covData'] = self.OverallParms['Covariance']
1437            try:
1438                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
1439                    generalData['DisAglCtls'],
1440                    DisAglData)
1441            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
1442                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
1443            wx.EndBusyCursor()
1444            txt.SetLabel('Set publication flags for distances and angles in\nphase '+phasenam)
1445            vbox.Add((5,5)) 
1446            vbox.Add(wx.StaticText(dlg,wx.ID_ANY,
1447                                   'The default is to flag all distances and angles as to be'+
1448                                   '\npublished. Change this by pressing appropriate buttons.'),
1449                     0,wx.ALL|wx.EXPAND)
1450            hbox = wx.BoxSizer(wx.HORIZONTAL)
1451            vbox.Add(hbox)
1452            hbox.Add(wx.StaticText(dlg,wx.ID_ANY,'Button appearance: '))
1453            but = wx.ToggleButton(dlg,wx.ID_ANY,'Publish')
1454            but.Bind(wx.EVT_TOGGLEBUTTON,keepFalse)
1455            hbox.Add(but)
1456            but = wx.ToggleButton(dlg,wx.ID_ANY,"Don't publish")
1457            but.Bind(wx.EVT_TOGGLEBUTTON,keepTrue)
1458            hbox.Add(but)
1459            but.SetValue(True)
1460            G2gd.HorizontalLine(vbox,dlg)         
1461           
1462            cpnl = wxscroll.ScrolledPanel(dlg,size=(400,300))
1463            cbox = wx.BoxSizer(wx.VERTICAL)
1464            for c in sorted(DistArray):
1465                karr = []
1466                UsedCols = {}
1467                cbox.Add(wx.StaticText(cpnl,wx.ID_ANY,
1468                                   'distances to/angles around atom '+AtomLabels[c]))
1469                #dbox = wx.GridBagSizer(hgap=5)
1470                dbox = wx.GridBagSizer()
1471                for i,D in enumerate(DistArray[c]):
1472                    karr.append(tuple(D[0:3]))
1473                    val = "{:.2f}".format(D[3])
1474                    sym = " [{:d} {:d} {:d}]".format(*D[1]) + " #{:d}".format(D[2])
1475                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
1476                             (i+1,0)
1477                             )                   
1478                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,sym),
1479                             (i+1,1)
1480                             )
1481                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1482                    but.key = (c,karr[-1])
1483                    but.DisAglSel = DisAngSel
1484                    if DisAngSel.get(but.key): but.SetValue(True)
1485                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1486                    dbox.Add(but,(i+1,2),border=1)
1487                for i,D in enumerate(AngArray[c]):
1488                    val = "{:.1f}".format(D[2][0])
1489                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1490                    but.key = (karr[D[0]],c,karr[D[1]])
1491                    but.DisAglSel = DisAngSel
1492                    if DisAngSel.get(but.key): but.SetValue(True)
1493                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1494                    dbox.Add(but,(D[0]+1,D[1]+3),border=1)
1495                    UsedCols[D[1]+3] = True
1496                for i,D in enumerate(DistArray[c][:-1]): # label columns that are used
1497                    if UsedCols.get(i+3):
1498                        dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
1499                                 (0,i+3),
1500                                 flag=wx.ALIGN_CENTER
1501                                 )
1502                dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,'distance'),
1503                                 (0,2),
1504                                 flag=wx.ALIGN_CENTER
1505                                 )
1506                cbox.Add(dbox)
1507                G2gd.HorizontalLine(cbox,cpnl)         
1508            cpnl.SetSizer(cbox)
1509            cpnl.SetAutoLayout(1)
1510            cpnl.SetupScrolling()
1511            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1512            cpnl.Layout()
1513
1514            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1515
1516            btnsizer = wx.StdDialogButtonSizer()
1517            btn = wx.Button(dlg, wx.ID_OK, "Done")
1518            btn.SetDefault()
1519            btnsizer.AddButton(btn)
1520            btnsizer.Realize()
1521            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1522            dlg.SetSizer(vbox)
1523            vbox.Fit(dlg)
1524            dlg.Layout()
1525           
1526            dlg.CenterOnParent()
1527            dlg.ShowModal()
1528           
1529#***** end of functions for export method =======================================
1530#=================================================================================
1531
1532        # the export process starts here
1533        self.InitExport(event)
1534        # load all of the tree into a set of dicts
1535        self.loadTree()
1536        # create a dict with refined values and their uncertainties
1537        self.loadParmDict()
1538        if self.ExportSelect(AskFile=(self.mode=='simple')):    # set export parameters
1539            return 
1540        # Someday: get restraint & constraint info
1541        #restraintDict = self.OverallParms.get('Restraints',{})
1542        #for i in  self.OverallParms['Constraints']:
1543        #    print i
1544        #    for j in self.OverallParms['Constraints'][i]:
1545        #        print j
1546
1547        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
1548        # is there anything to export?
1549        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
1550           self.G2frame.ErrorDialog(
1551               'Empty project',
1552               'Project does not contain any data or phases. Are they interconnected?')
1553           return
1554        # get the project file name
1555        self.CIFname = os.path.splitext(
1556            os.path.split(self.G2frame.GSASprojectfile)[1]
1557            )[0]
1558        self.CIFname = self.CIFname.replace(' ','')
1559        if not self.CIFname: # none defined & needed, save as GPX to get one
1560            self.G2frame.OnFileSaveas(None)
1561            if not self.G2frame.GSASprojectfile: return
1562            self.CIFname = os.path.splitext(
1563                os.path.split(self.G2frame.GSASprojectfile)[1]
1564                )[0]
1565            self.CIFname = self.CIFname.replace(' ','')
1566        # test for quick CIF mode or no data
1567        self.quickmode = False
1568        phasenam = None # include all phases
1569        if self.mode == "simple" and self.currentExportType == 'phase':
1570            if len(self.Phases) == 0: # this check is probably not needed
1571                self.G2frame.ErrorDialog(
1572                    'No phase present',
1573                    'Cannot create a coordinates CIF with no phases')
1574                return
1575            self.quickmode = True
1576            oneblock = True
1577            if len(self.Phases) > 1: # quick mode: get selected phase
1578                phasenam = self.phasenam[0]
1579        elif self.mode == "simple": # powder/single xtal data export
1580            self.quickmode = True
1581            oneblock = True
1582        # Project export: will this require a multiblock CIF?
1583        elif len(self.Phases) > 1:
1584            oneblock = False
1585        elif len(self.powderDict) + len(self.xtalDict) > 1:
1586            oneblock = False
1587        else: # one phase, one dataset, Full CIF
1588            oneblock = True
1589
1590        # make sure required information is present
1591        # get CIF author name -- required for full CIFs
1592        try:
1593            self.author = self.OverallParms['Controls'].get("Author",'').strip()
1594        except KeyError:
1595            pass
1596        while not (self.author or self.quickmode):
1597            if not EditAuthor(): return
1598        self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
1599
1600        # check there is an instrument name for every histogram
1601        if not self.quickmode:
1602            invalid = 0
1603            key3 = 'InstrName'
1604            for hist in self.Histograms:
1605                if hist.startswith("PWDR"): 
1606                    key2 = "Sample Parameters"
1607                    d = self.Histograms[hist][key2]
1608                elif hist.startswith("HKLF"): 
1609                    key2 = "Instrument Parameters"
1610                    d = self.Histograms[hist][key2][0]                   
1611                instrname = d.get(key3)
1612                if instrname is None:
1613                    d[key3] = ''
1614                    invalid += 1
1615                elif instrname.strip() == '':
1616                    invalid += 1
1617            if invalid:
1618                msg = ""
1619                if invalid > 3: msg = (
1620                    "\n\nNote: it may be faster to set the name for\n"
1621                    "one histogram for each instrument and use the\n"
1622                    "File/Copy option to duplicate the name"
1623                    )
1624                if not EditInstNames(): return
1625        if not self.quickmode:
1626            # check for a distance-angle range search range for each phase
1627            for phasenam in sorted(self.Phases.keys()):
1628                #i = self.Phases[phasenam]['pId']
1629                phasedict = self.Phases[phasenam] # pointer to current phase info           
1630                if 'DisAglCtls' not in phasedict['General']:
1631                    dlg = G2gd.DisAglDialog(
1632                        self.G2frame,
1633                        {},
1634                        phasedict['General'])
1635                    if dlg.ShowModal() == wx.ID_OK:
1636                        phasedict['General']['DisAglCtls'] = dlg.GetData()
1637                    else:
1638                        dlg.Destroy()
1639                        return
1640                    dlg.Destroy()
1641
1642        if oneblock and not self.quickmode:
1643            # select a dataset to use (there should only be one set in one block,
1644            # but take whatever comes 1st)
1645            for hist in self.Histograms:
1646                histblk = self.Histograms[hist]
1647                if hist.startswith("PWDR"): 
1648                    instnam = histblk["Sample Parameters"]['InstrName']
1649                    break # ignore all but 1st data histogram
1650                elif hist.startswith("HKLF"): 
1651                    instnam = histblk["Instrument Parameters"][0]['InstrName']
1652                    break # ignore all but 1st data histogram
1653        if not self.quickmode: # give the user a chance to edit all defaults
1654            self.cifdefs = wx.Dialog(
1655                self.G2frame,
1656                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
1657            EditCIFDefaults()
1658            self.cifdefs.CenterOnParent()
1659            val = self.cifdefs.ShowModal()
1660            self.cifdefs.Destroy()
1661            if val != wx.ID_OK:
1662                return
1663        #======================================================================
1664        # Start writing the CIF - single block
1665        #======================================================================
1666        print('Writing CIF output to file '+str(self.filename)+"...")
1667        openCIF(self.filename)
1668        if self.currentExportType == 'single' or self.currentExportType == 'powder':
1669            #======Data only CIF (powder/xtal) ====================================
1670            hist = self.histnam[0]
1671            self.CIFname = hist[5:40].replace(' ','')
1672            WriteCIFitem('data_'+self.CIFname)
1673            if hist.startswith("PWDR"):
1674                WritePowderData(hist)
1675            elif hist.startswith("HKLF"):
1676                WriteSingleXtalData(hist)
1677            else:
1678                print "should not happen"
1679        elif self.quickmode:
1680            #====Phase only CIF ====================================================
1681            WriteCIFitem('data_'+self.CIFname)
1682            if phasenam is None: # if not already selected, select the first phase (should be one)
1683                phasenam = self.Phases.keys()[0]
1684            #print 'phasenam',phasenam
1685            phaseblk = self.Phases[phasenam] # pointer to current phase info
1686            # report the phase info
1687            WritePhaseInfo(phasenam)
1688        elif oneblock:
1689            #====Single block, data & phase CIF ===================================
1690            WriteCIFitem('data_'+self.CIFname)
1691            if phasenam is None: # if not already selected, select the first phase (should be one)
1692                phasenam = self.Phases.keys()[0]
1693            #print 'phasenam',phasenam
1694            phaseblk = self.Phases[phasenam] # pointer to current phase info
1695            instnam = instnam.replace(' ','')
1696            WriteCIFitem('_pd_block_id',
1697                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1698                         str(self.shortauthorname) + "|" + instnam)
1699            WriteAudit()
1700            writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
1701            WriteOverall()
1702            writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1703            # report the phase info
1704            WritePhaseInfo(phasenam)
1705            if hist.startswith("PWDR"):
1706                # preferred orientation
1707                SH = FormatSH(phasenam)
1708                MD = FormatHAPpo(phasenam)
1709                if SH and MD:
1710                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1711                elif SH or MD:
1712                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1713                else:
1714                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1715                    # report profile, since one-block: include both histogram and phase info
1716                WriteCIFitem('_pd_proc_ls_profile_function',
1717                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
1718                    +'\n'+FormatPhaseProfile(phasenam))
1719                histblk = self.Histograms[hist]["Sample Parameters"]
1720                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
1721                WritePowderData(hist)
1722            elif hist.startswith("HKLF"):
1723                histprm = self.Histograms[hist]["Instrument Parameters"][0]
1724                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
1725                WriteSingleXtalData(hist)
1726        else:
1727            #=== multiblock: multiple phases and/or histograms ====================
1728            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
1729            try:
1730                dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
1731                Size = dlg.GetSize()
1732                Size = (int(Size[0]*3),Size[1]) # increase size along x
1733                dlg.SetSize(Size)
1734                dlg.CenterOnParent()
1735
1736                # publication info
1737                step = 1
1738                dlg.Update(step,"Exporting overall section")
1739                WriteCIFitem('\ndata_'+self.CIFname+'_publ')
1740                WriteAudit()
1741                WriteCIFitem('_pd_block_id',
1742                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1743                             str(self.shortauthorname) + "|Overall")
1744                writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
1745                # ``template_publ.cif`` or a modified version
1746                # overall info
1747                WriteCIFitem('data_'+str(self.CIFname)+'_overall')
1748                WriteOverall()
1749                #============================================================
1750                WriteCIFitem('# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
1751                datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
1752                # loop over phase blocks
1753                if len(self.Phases) > 1:
1754                    loopprefix = ''
1755                    WriteCIFitem('loop_   _pd_phase_block_id')
1756                else:
1757                    loopprefix = '_pd_phase_block_id'
1758
1759                for phasenam in sorted(self.Phases.keys()):
1760                    i = self.Phases[phasenam]['pId']
1761                    datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1762                                 'phase_'+ str(i) + '|' + str(self.shortauthorname))
1763                    WriteCIFitem(loopprefix,datablockidDict[phasenam])
1764                # loop over data blocks
1765                if len(self.powderDict) + len(self.xtalDict) > 1:
1766                    loopprefix = ''
1767                    WriteCIFitem('loop_   _pd_block_diffractogram_id')
1768                else:
1769                    loopprefix = '_pd_block_diffractogram_id'
1770                for i in sorted(self.powderDict.keys()):
1771                    hist = self.powderDict[i]
1772                    histblk = self.Histograms[hist]
1773                    instnam = histblk["Sample Parameters"]['InstrName']
1774                    instnam = instnam.replace(' ','')
1775                    j = histblk['hId']
1776                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1777                                             str(self.shortauthorname) + "|" +
1778                                             instnam + "_hist_"+str(j))
1779                    WriteCIFitem(loopprefix,datablockidDict[hist])
1780                for i in sorted(self.xtalDict.keys()):
1781                    hist = self.xtalDict[i]
1782                    histblk = self.Histograms[hist]
1783                    instnam = histblk["Instrument Parameters"][0]['InstrName']
1784                    instnam = instnam.replace(' ','')
1785                    i = histblk['hId']
1786                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1787                                             str(self.shortauthorname) + "|" +
1788                                             instnam + "_hist_"+str(i))
1789                    WriteCIFitem(loopprefix,datablockidDict[hist])
1790                #============================================================
1791                # loop over phases, exporting them
1792                phasebyhistDict = {} # create a cross-reference to phases by histogram
1793                for j,phasenam in enumerate(sorted(self.Phases.keys())):
1794                    step += 1
1795                    dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
1796                    i = self.Phases[phasenam]['pId']
1797                    WriteCIFitem('\ndata_'+self.CIFname+"_phase_"+str(i))
1798                    WriteCIFitem('# Information for phase '+str(i))
1799                    WriteCIFitem('_pd_block_id',datablockidDict[phasenam])
1800                    # report the phase
1801                    writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1802                    WritePhaseInfo(phasenam)
1803                    # preferred orientation
1804                    SH = FormatSH(phasenam)
1805                    MD = FormatHAPpo(phasenam)
1806                    if SH and MD:
1807                        WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1808                    elif SH or MD:
1809                        WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1810                    else:
1811                        WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1812                    # report sample profile terms
1813                    PP = FormatPhaseProfile(phasenam)
1814                    if PP:
1815                        WriteCIFitem('_pd_proc_ls_profile_function',PP)
1816
1817                #============================================================
1818                # loop over histograms, exporting them
1819                for i in sorted(self.powderDict.keys()):
1820                    hist = self.powderDict[i]
1821                    histblk = self.Histograms[hist]
1822                    if hist.startswith("PWDR"): 
1823                        step += 1
1824                        dlg.Update(step,"Exporting "+hist.strip())
1825                        WriteCIFitem('\ndata_'+self.CIFname+"_pwd_"+str(i))
1826                        #instnam = histblk["Sample Parameters"]['InstrName']
1827                        # report instrumental profile terms
1828                        WriteCIFitem('_pd_proc_ls_profile_function',
1829                            FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1830                        WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
1831                        WriteCIFitem('_pd_block_id',datablockidDict[hist])
1832                        histprm = self.Histograms[hist]["Sample Parameters"]
1833                        writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
1834                        WritePowderData(hist)
1835                for i in sorted(self.xtalDict.keys()):
1836                    hist = self.xtalDict[i]
1837                    histblk = self.Histograms[hist]
1838                    if hist.startswith("HKLF"): 
1839                        step += 1
1840                        dlg.Update(step,"Exporting "+hist.strip())
1841                        WriteCIFitem('\ndata_'+self.CIFname+"_sx_"+str(i))
1842                        #instnam = histblk["Instrument Parameters"][0]['InstrName']
1843                        WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
1844                        WriteCIFitem('_pd_block_id',datablockidDict[hist])
1845                        histprm = self.Histograms[hist]["Instrument Parameters"][0]
1846                        writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
1847                        WriteSingleXtalData(hist)
1848
1849            except Exception:
1850                import traceback
1851                print(traceback.format_exc())
1852                self.G2frame.ErrorDialog('Exception',
1853                                         'Error occurred in CIF creation. '+
1854                                         'See full error message in console output ')
1855            finally:
1856                dlg.Destroy()
1857
1858        WriteCIFitem('#--' + 15*'eof--' + '#')
1859        closeCIF()
1860        print("...export completed")
1861        # end of CIF export
1862
1863class ExportPhaseCIF(ExportCIF):
1864    '''Used to create a simple CIF of at most one phase. Uses exact same code as
1865    :class:`ExportCIF` except that `self.mode` is set to "simple" in `self.InitExport`.
1866    Shows up in menu as Quick CIF.
1867
1868    :param wx.Frame G2frame: reference to main GSAS-II frame
1869    '''
1870    def __init__(self,G2frame):
1871        G2IO.ExportBaseclass.__init__(self,
1872            G2frame=G2frame,
1873            formatName = 'Quick CIF',
1874            extension='.cif',
1875            longFormatName = 'Export one phase in CIF'
1876            )
1877        self.exporttype = ['phase']
1878        # CIF-specific items
1879        self.author = ''
1880        self.mode = 'simple'
1881
1882class ExportDataCIF(ExportCIF):
1883    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
1884    :class:`ExportCIF` except that `self.mode` is set to "simple" and `self.currentExportType`
1885    is set to "single" or "powder" in `self.InitExport`. Shows up in menus as Data-only CIF.
1886
1887    :param wx.Frame G2frame: reference to main GSAS-II frame
1888    '''
1889    def __init__(self,G2frame):
1890        G2IO.ExportBaseclass.__init__(self,
1891            G2frame=G2frame,
1892            formatName = 'Data-only CIF',
1893            extension='.cif',
1894            longFormatName = 'Export data as CIF'
1895            )
1896        self.exporttype = ['single','powder']
1897        # CIF-specific items
1898        self.author = ''
1899        self.mode = 'simple'
1900
1901#===============================================================================
1902# misc CIF utilities
1903#===============================================================================
1904def PickleCIFdict(fil):
1905    '''Loads a CIF dictionary, cherry picks out the items needed
1906    by local code and sticks them into a python dict and writes
1907    that dict out as a cPickle file for later reuse.
1908    If the write fails a warning message is printed,
1909    but no exception occurs.
1910
1911    :param str fil: file name of CIF dictionary, will usually end
1912      in .dic
1913    :returns: the dict with the definitions 
1914    '''
1915    import CifFile as cif # PyCifRW from James Hester
1916    cifdic = {}
1917    try:
1918        fp = open(fil,'r')             # patch: open file to avoid windows bug
1919        dictobj = cif.CifDic(fp)
1920        fp.close()
1921    except IOError:
1922        dictobj = cif.CifDic(fil)
1923    if DEBUG: print('loaded '+str(fil))
1924    for item in dictobj.keys():
1925        cifdic[item] = {}
1926        for j in (
1927            '_definition','_type',
1928            '_enumeration',
1929            '_enumeration_detail',
1930            '_enumeration_range'):
1931            if dictobj[item].get(j):
1932                cifdic[item][j] = dictobj[item][j]
1933    try:
1934        fil = os.path.splitext(fil)[0]+'.cpickle'
1935        fp = open(fil,'w')
1936        cPickle.dump(cifdic,fp)
1937        fp.close()
1938        if DEBUG: print('wrote '+str(fil))
1939    except:
1940        print ('Unable to write '+str(fil))
1941    return cifdic
1942
1943def LoadCIFdic():
1944    '''Create a composite core+powder CIF lookup dict containing
1945    information about all items in the CIF dictionaries, loading
1946    pickled files if possible. The routine looks for files
1947    named cif_core.cpickle and cif_pd.cpickle in every
1948    directory in the path and if they are not found, files
1949    cif_core.dic and/or cif_pd.dic are read.
1950
1951    :returns: the dict with the definitions 
1952    '''
1953    cifdic = {}
1954    for ftyp in "cif_core","cif_pd":
1955        for loc in sys.path:
1956            fil = os.path.join(loc,ftyp+".cpickle")
1957            if not os.path.exists(fil): continue
1958            fp = open(fil,'r')
1959            try:
1960                cifdic.update(cPickle.load(fp))
1961                if DEBUG: print('reloaded '+str(fil))
1962                break
1963            finally:
1964                fp.close()
1965        else:
1966            for loc in sys.path:
1967                fil = os.path.join(loc,ftyp+".dic")
1968                if not os.path.exists(fil): continue
1969                #try:
1970                if True:
1971                    cifdic.update(PickleCIFdict(fil))
1972                    break
1973                #except:
1974                #    pass
1975            else:
1976                print('Could not load '+ftyp+' dictionary')
1977    return cifdic
1978
1979class CIFdefHelp(wx.Button):
1980    '''Create a help button that displays help information on
1981    the current data item
1982
1983    :param parent: the panel which will be the parent of the button
1984    :param str msg: the help text to be displayed
1985    :param wx.Dialog helpwin: Frame for CIF editing dialog
1986    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
1987    '''
1988    def __init__(self,parent,msg,helpwin,helptxt):
1989        wx.Button.__init__(self,parent,wx.ID_HELP)
1990        self.Bind(wx.EVT_BUTTON,self._onPress)
1991        self.msg=msg
1992        self.parent = parent
1993        #self.helpwin = self.parent.helpwin
1994        self.helpwin = helpwin
1995        self.helptxt = helptxt
1996    def _onPress(self,event):
1997        'Respond to a button press by displaying the requested text'
1998        try:
1999            #helptxt = self.helptxt
2000            ow,oh = self.helptxt.GetSize()
2001            self.helptxt.SetLabel(self.msg)
2002            w,h = self.helptxt.GetSize()
2003            if h > oh:
2004                self.helpwin.GetSizer().Fit(self.helpwin)
2005        except: # error posting help, ignore
2006            return
2007
2008def CIF2dict(cf):
2009    '''copy the contents of a CIF out from a PyCifRW block object
2010    into a dict
2011
2012    :returns: cifblk, loopstructure where cifblk is a dict with
2013      CIF items and loopstructure is a list of lists that defines
2014      which items are in which loops.
2015    '''
2016    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
2017    loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
2018    dblk = {}
2019    for item in cf[blk].keys(): # make a copy of all the items in the block
2020        dblk[item] = cf[blk][item]
2021    return dblk,loopstructure
2022
2023def dict2CIF(dblk,loopstructure,blockname='Template'):
2024    '''Create a PyCifRW CIF object containing a single CIF
2025    block object from a dict and loop structure list.
2026
2027    :param dblk: a dict containing values for each CIF item
2028    :param list loopstructure: a list of lists containing the contents of
2029      each loop, as an example::
2030
2031         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2032
2033      this describes a CIF with this type of structure::
2034
2035        loop_ _a _b <a1> <b1> <a2> ...
2036        loop_ _c <c1> <c2>...
2037        loop _d_1 _d_2 _d_3 ...
2038
2039      Note that the values for each looped CIF item, such as _a,
2040      are contained in a list, for example as cifblk["_a"]
2041
2042    :param str blockname: an optional name for the CIF block.
2043      Defaults to 'Template'
2044
2045    :returns: the newly created PyCifRW CIF object
2046    '''
2047
2048    import CifFile as cif # PyCifRW from James Hester
2049    # compile a 'list' of items in loops
2050    loopnames = set()
2051    for i in loopstructure:
2052        loopnames |= set(i)
2053    # create a new block
2054    newblk = cif.CifBlock()
2055    # add the looped items
2056    for keys in loopstructure:
2057        vals = []
2058        for key in keys:
2059            vals.append(dblk[key])
2060        newblk.AddCifItem(([keys],[vals]))
2061    # add the non-looped items
2062    for item in dblk:
2063        if item in loopnames: continue
2064        newblk[item] = dblk[item]
2065    # create a CIF and add the block
2066    newcf = cif.CifFile()
2067    newcf[blockname] = newblk   
2068    return newcf
2069
2070
2071class EditCIFtemplate(wx.Dialog):
2072    '''Create a dialog for editing a CIF template. The edited information is
2073    placed in cifblk. If the CIF is saved as a file, the name of that file
2074    is saved as ``self.newfile``.
2075   
2076    :param wx.Frame parent: parent frame or None
2077    :param cifblk: dict or PyCifRW block containing values for each CIF item
2078    :param list loopstructure: a list of lists containing the contents of
2079      each loop, as an example::
2080
2081         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2082
2083      this describes a CIF with this type of structure::
2084
2085        loop_ _a _b <a1> <b1> <a2> ...
2086        loop_ _c <c1> <c2>...
2087        loop _d_1 _d_2 _d_3 ...
2088
2089      Note that the values for each looped CIF item, such as _a,
2090      are contained in a list, for example as cifblk["_a"]
2091     
2092    :param str defaultname: specifies the default file name to be used for
2093      saving the CIF.
2094    '''
2095    def __init__(self,parent,cifblk,loopstructure,defaultname):
2096        OKbuttons = []
2097        self.cifblk = cifblk
2098        self.loopstructure = loopstructure
2099        self.newfile = None
2100        self.defaultname = defaultname       
2101        global CIFdic  # once this is loaded, keep it around
2102        if CIFdic is None:
2103            CIFdic = LoadCIFdic()
2104        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2105
2106        # define widgets that will be needed during panel creation
2107        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
2108        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
2109        OKbuttons.append(savebtn)
2110        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
2111        OKbtn = wx.Button(self, wx.ID_OK, "Use")
2112        OKbtn.SetDefault()
2113        OKbuttons.append(OKbtn)
2114
2115        self.SetTitle('Edit items in CIF template')
2116        vbox = wx.BoxSizer(wx.VERTICAL)
2117        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
2118        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2119        G2gd.HorizontalLine(vbox,self)
2120        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
2121        G2gd.HorizontalLine(vbox,self)
2122        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2123        btn = wx.Button(self, wx.ID_CANCEL)
2124        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
2125        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
2126        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
2127        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2128        self.SetSizer(vbox)
2129        vbox.Fit(self)
2130    def Post(self):
2131        '''Display the dialog
2132       
2133        :returns: True unless Cancel has been pressed.
2134        '''
2135        return (self.ShowModal() == wx.ID_OK)
2136    def _onSave(self,event):
2137        'Save CIF entries in a template file'
2138        dlg = wx.FileDialog(
2139            self, message="Save as CIF template",
2140            defaultDir=os.getcwd(),
2141            defaultFile=self.defaultname,
2142            wildcard="CIF (*.cif)|*.cif",
2143            style=wx.SAVE | wx.CHANGE_DIR
2144            )
2145        val = (dlg.ShowModal() == wx.ID_OK)
2146        fil = dlg.GetPath()
2147        dlg.Destroy()
2148        if val: # ignore a Cancel button
2149            fil = os.path.splitext(fil)[0]+'.cif' # force extension
2150            fp = open(fil,'w')
2151            newcf = dict2CIF(self.cifblk,self.loopstructure)
2152            fp.write(newcf.WriteOut())
2153            fp.close()
2154            self.newfile = fil
2155            self.EndModal(wx.ID_OK)
2156
2157class EditCIFpanel(wxscroll.ScrolledPanel):
2158    '''Creates a scrolled panel for editing CIF template items
2159
2160    :param wx.Frame parent: parent frame where panel will be placed
2161    :param cifblk: dict or PyCifRW block containing values for each CIF item
2162    :param list loopstructure: a list of lists containing the contents of
2163      each loop, as an example::
2164
2165         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2166
2167      this describes a CIF with this type of structure::
2168
2169        loop_ _a _b <a1> <b1> <a2> ...
2170        loop_ _c <c1> <c2>...
2171        loop _d_1 _d_2 _d_3 ...
2172
2173      Note that the values for each looped CIF item, such as _a,
2174      are contained in a list, for example as cifblk["_a"]
2175
2176    :param dict cifdic: optional CIF dictionary definitions
2177    :param list OKbuttons: A list of wx.Button objects that should
2178      be disabled when information in the CIF is invalid
2179    :param (other): optional keyword parameters for wx.ScrolledPanel
2180    '''
2181    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
2182        self.parent = parent
2183        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
2184        self.vbox = None
2185        self.AddDict = None
2186        self.cifdic = cifdic
2187        self.cifblk = cifblk
2188        self.loops = loopstructure
2189        self.parent = parent
2190        self.LayoutCalled = False
2191        self.parentOKbuttons = OKbuttons
2192        self.ValidatedControlsList = []
2193        self._fill()
2194    def _fill(self):
2195        'Fill the scrolled panel with widgets for each CIF item'
2196        wx.BeginBusyCursor()
2197        self.AddDict = {}
2198        self.ValidatedControlsList = []
2199        # delete any only contents
2200        if self.vbox:
2201            self.vbox.DeleteWindows()
2202            self.vbox = None
2203            self.Update()
2204        vbox = wx.BoxSizer(wx.VERTICAL)
2205        self.vbox = vbox
2206        # compile a 'list' of items in loops
2207        loopnames = set()
2208        for i in self.loops:
2209            loopnames |= set(i)
2210        # post the looped CIF items
2211        for lnum,lp in enumerate(self.loops):
2212            hbox = wx.BoxSizer(wx.HORIZONTAL)
2213            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
2214            vbox.Add(hbox)
2215            but = wx.Button(self,wx.ID_ANY,"Add row")
2216            self.AddDict[but]=lnum
2217           
2218            hbox.Add(but)           
2219            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
2220            fbox = wx.GridBagSizer(0, 0)
2221            vbox.Add(fbox)
2222            rows = 0
2223            for i,item in enumerate(lp):
2224                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
2225                fbox.Add(txt,(0,i+1))
2226                # if self.cifdic.get(item):
2227                #     df = self.cifdic[item].get('_definition')
2228                #     if df:
2229                #         txt.SetToolTipString(G2IO.trim(df))
2230                #         but = CIFdefHelp(self,
2231                #                          "Definition for "+item+":\n\n"+G2IO.trim(df),
2232                #                          self.parent,
2233                #                          self.parent.helptxt)
2234                #         fbox.Add(but,(1,i+1),flag=wx.ALIGN_CENTER)
2235                for j,val in enumerate(self.cifblk[item]):
2236                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
2237                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
2238                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
2239                if self.cifdic.get(item):
2240                    df = self.cifdic[item].get('_definition')
2241                    if df:
2242                        txt.SetToolTipString(G2IO.trim(df))
2243                        but = CIFdefHelp(self,
2244                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2245                                         self.parent,
2246                                         self.parent.helptxt)
2247                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
2248                rows = max(rows,len(self.cifblk[item]))
2249            for i in range(rows):
2250                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
2251                fbox.Add(txt,(i+2,0))
2252            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
2253            vbox.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2254               
2255        # post the non-looped CIF items
2256        for item in sorted(self.cifblk.keys()):
2257            if item not in loopnames:
2258                hbox = wx.BoxSizer(wx.HORIZONTAL)
2259                vbox.Add(hbox)
2260                txt = wx.StaticText(self,wx.ID_ANY,item)
2261                hbox.Add(txt)
2262                ent = self.CIFEntryWidget(self.cifblk,item,item)
2263                hbox.Add(ent)
2264                if self.cifdic.get(item):
2265                    df = self.cifdic[item].get('_definition')
2266                    if df:
2267                        txt.SetToolTipString(G2IO.trim(df))
2268                        but = CIFdefHelp(self,
2269                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2270                                         self.parent,
2271                                         self.parent.helptxt)
2272                        hbox.Add(but,0,wx.ALL,2)
2273        self.SetSizer(vbox)
2274        #vbox.Fit(self.parent)
2275        self.SetAutoLayout(1)
2276        self.SetupScrolling()
2277        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
2278        self.Layout()
2279        wx.EndBusyCursor()
2280    def OnLayoutNeeded(self,event):
2281        '''Called when an update of the panel layout is needed. Calls
2282        self.DoLayout after the current operations are complete using
2283        CallAfter. This is called only once, according to flag
2284        self.LayoutCalled, which is cleared in self.DoLayout.
2285        '''
2286        if self.LayoutCalled: return # call already queued
2287        wx.CallAfter(self.DoLayout) # queue a call
2288        self.LayoutCalled = True
2289    def DoLayout(self):
2290        '''Update the Layout and scroll bars for the Panel. Clears
2291        self.LayoutCalled so that next change to panel can
2292        request a new update
2293        '''
2294        wx.BeginBusyCursor()
2295        self.Layout()
2296        self.SetupScrolling()
2297        wx.EndBusyCursor()
2298        self.LayoutCalled = False
2299    def OnAddRow(self,event):
2300        'add a row to a loop'
2301        lnum = self.AddDict.get(event.GetEventObject())
2302        if lnum is None: return
2303        for item in self.loops[lnum]:
2304            self.cifblk[item].append('?')
2305        self._fill()
2306
2307    def ControlOKButton(self,setvalue):
2308        '''Enable or Disable the OK button(s) for the dialog. Note that this is
2309        passed into the ValidatedTxtCtrl for use by validators.
2310
2311        :param bool setvalue: if True, all entries in the dialog are
2312          checked for validity. The first invalid control triggers
2313          disabling of buttons.
2314          If False then the OK button(s) are disabled with no checking
2315          of the invalid flag for each control.
2316        '''
2317        if setvalue: # turn button on, do only if all controls show as valid
2318            for ctrl in self.ValidatedControlsList:
2319                if ctrl.invalid:
2320                    for btn in self.parentOKbuttons:
2321                        btn.Disable()
2322                    return
2323            else:
2324                for btn in self.parentOKbuttons:
2325                    btn.Enable()
2326        else:
2327            for btn in self.parentOKbuttons:
2328                btn.Disable()
2329       
2330    def CIFEntryWidget(self,dct,item,dataname):
2331        '''Create an entry widget for a CIF item. Use a validated entry for numb values
2332        where int is required when limits are integers and floats otherwise.
2333        At present this does not allow entry of the special CIF values of "." and "?" for
2334        numerical values and highlights them as invalid.
2335        Use a selection widget when there are specific enumerated values for a string.       
2336        '''
2337        if self.cifdic.get(dataname):
2338            if self.cifdic[dataname].get('_enumeration'):
2339                values = ['?']+self.cifdic[dataname]['_enumeration']
2340                choices = ['undefined']
2341                for i in self.cifdic[dataname].get('_enumeration_detail',values):
2342                    choices.append(G2IO.trim(i))
2343                ent = G2gd.EnumSelector(self, dct, item, choices, values, size=(200, -1))
2344                return ent
2345            if self.cifdic[dataname].get('_type') == 'numb':
2346                mn = None
2347                mx = None
2348                hint = int
2349                if self.cifdic[dataname].get('_enumeration_range'):
2350                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
2351                    if '.' in rng[0] or '.' in rng[1]: hint = float
2352                    if rng[0]: mn = hint(rng[0])
2353                    if rng[1]: mx = hint(rng[1])
2354                    ent = G2gd.ValidatedTxtCtrl(
2355                        self,dct,item,typeHint=hint,min=mn,max=mx,
2356                        CIFinput=True,
2357                        OKcontrol=self.ControlOKButton)
2358                    self.ValidatedControlsList.append(ent)
2359                    return ent
2360        rw1 = rw.ResizeWidget(self)
2361        ent = G2gd.ValidatedTxtCtrl(
2362            rw1,dct,item,size=(100, 20),
2363            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
2364            CIFinput=True,
2365            OKcontrol=self.ControlOKButton)
2366        self.ValidatedControlsList.append(ent)
2367        return rw1
2368
2369class CIFtemplateSelect(wx.BoxSizer):
2370    '''Create a set of buttons to show, select and edit a CIF template
2371   
2372    :param frame: wx.Frame object of parent
2373    :param panel: wx.Panel object where widgets should be placed
2374    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
2375      the type of template
2376    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
2377      "CIF_template" will be used to store either a list or a string.
2378      If a list, it will contain a dict and a list defining loops. If
2379      an str, it will contain a file name.   
2380    :param function repaint: reference to a routine to be called to repaint
2381      the frame after a change has been made
2382    :param str title: A line of text to show at the top of the window
2383    :param str defaultname: specifies the default file name to be used for
2384      saving the CIF.
2385    '''
2386    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
2387        wx.BoxSizer.__init__(self,wx.VERTICAL)
2388        self.cifdefs = frame
2389        self.dict = G2dict
2390        self.repaint = repaint
2391        templateDefName = 'template_'+tmplate+'.cif'
2392        self.CIF = G2dict.get("CIF_template")
2393        if defaultname:
2394            self.defaultname = defaultname.encode('ascii','replace').strip().replace(' ','_')
2395            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
2396            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
2397        else:
2398            self.defaultname = ''
2399           
2400        txt = wx.StaticText(panel,wx.ID_ANY,title)
2401        self.Add(txt,0,wx.ALIGN_CENTER)
2402        # change font on title
2403        txtfnt = txt.GetFont()
2404        txtfnt.SetWeight(wx.BOLD)
2405        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
2406        txt.SetFont(txtfnt)
2407        self.Add((-1,3))
2408
2409        if not self.CIF: # empty or None
2410            for pth in [os.getcwd()]+sys.path:
2411                fil = os.path.join(pth,self.defaultname)
2412                if os.path.exists(fil) and self.defaultname:
2413                    self.CIF = fil
2414                    CIFtxt = "Template: "+self.defaultname
2415                    break
2416            else:
2417                for pth in sys.path:
2418                    fil = os.path.join(pth,templateDefName)
2419                    if os.path.exists(fil):
2420                        self.CIF = fil
2421                        CIFtxt = "Template: "+templateDefName
2422                        break
2423                else:
2424                    print("Default CIF template "+self.defaultname+' not found in path!')
2425                    self.CIF = None
2426                    CIFtxt = "none! (No template found)"
2427        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
2428            if not os.path.exists(self.CIF):
2429                print("Error: template file has disappeared: "+self.CIF)
2430                self.CIF = None
2431                CIFtxt = "none! (file not found)"
2432            else:
2433                if len(self.CIF) < 50:
2434                    CIFtxt = "File: "+self.CIF
2435                else:
2436                    CIFtxt = "File: ..."+self.CIF[-50:]
2437        else:
2438            CIFtxt = "Template is customized"
2439        # show template source
2440        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
2441        # show str, button to select file; button to edit (if CIF defined)
2442        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
2443        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
2444        hbox =  wx.BoxSizer(wx.HORIZONTAL)
2445        hbox.Add(but,0,0,2)
2446        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
2447        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
2448        if self.CIF is None: but.Disable() # nothing to edit!
2449        hbox.Add(but,0,0,2)
2450        self.Add(hbox)
2451    def _onGetTemplateFile(self,event):
2452        'select a template file'
2453        dlg = wx.FileDialog(
2454            self.cifdefs, message="Read CIF template file",
2455            defaultDir=os.getcwd(),
2456            defaultFile=self.defaultname,
2457            wildcard="CIF (*.cif)|*.cif",
2458            style=wx.OPEN | wx.CHANGE_DIR
2459            )
2460        ret = dlg.ShowModal()
2461        fil = dlg.GetPath()
2462        dlg.Destroy()
2463        if ret == wx.ID_OK:
2464            try:
2465                cf = G2IO.ReadCIF(fil)
2466                if len(cf.keys()) == 0:
2467                    raise Exception,"No CIF data_ blocks found"
2468                if len(cf.keys()) != 1:
2469                    raise Exception, 'Error, CIF Template has more than one block: '+fil
2470                self.dict["CIF_template"] = fil
2471            except Exception as err:
2472                print('\nError reading CIF: '+fil)
2473                dlg = wx.MessageDialog(self.cifdefs,
2474                                   'Error reading CIF '+fil,
2475                                   'Error in CIF file',
2476                                   wx.OK)
2477                dlg.ShowModal()
2478                dlg.Destroy()
2479                print(err.message)
2480                return
2481            self.repaint() #EditCIFDefaults()
2482
2483    def _onEditTemplateContents(self,event):
2484        'Called to edit the contents of a CIF template'
2485        if type(self.CIF) is list or  type(self.CIF) is tuple:
2486            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
2487        else:
2488            cf = G2IO.ReadCIF(self.CIF)
2489            dblk,loopstructure = CIF2dict(cf)
2490        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
2491        val = dlg.Post()
2492        if val:
2493            if dlg.newfile: # results saved in file
2494                self.dict["CIF_template"] = dlg.newfile
2495            else:
2496                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
2497            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
2498        else:
2499            dlg.Destroy()       
2500
2501#===============================================================================
2502# end of misc CIF utilities
2503#===============================================================================
Note: See TracBrowser for help on using the repository browser.