source: trunk/exports/G2export_CIF.py @ 2527

Last change on this file since 2527 was 2527, checked in by toby, 5 years ago

remove debug code; remove code causing error on CIF export

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