source: trunk/exports/G2export_CIF.py @ 2867

Last change on this file since 2867 was 2867, checked in by toby, 4 years ago

fix sequential exports

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