source: trunk/exports/G2cif.py @ 1074

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

more CIF work

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