source: trunk/exports/G2export_CIF.py @ 2516

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

revise import to not assume Bank 1 with multibank instparm files; deal with unicode problem in CIF files; improve atoms use of selection from menu; add Pawley menu variable selection (plenty more to do)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 126.4 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2016-11-07 04:18:52 +0000 (Mon, 07 Nov 2016) $
5# $Author: toby $
6# $Revision: 2516 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 2516 2016-11-07 04:18:52Z 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: 2516 $")
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        # test code ***************************************************************************************************
1843        WriteCIFitem('data_'+'\xc3\x81vila')
1844        # test code ***************************************************************************************************
1845        if self.currentExportType == 'single' or self.currentExportType == 'powder':
1846            #======Data only CIF (powder/xtal) ====================================
1847            hist = self.histnam[0]
1848            self.CIFname = hist[5:40].replace(' ','')
1849            WriteCIFitem('data_'+self.CIFname)
1850            if hist.startswith("PWDR"):
1851                WritePowderData(hist)
1852            elif hist.startswith("HKLF"):
1853                WriteSingleXtalData(hist)
1854            else:
1855                print "should not happen"
1856        elif oneblock:
1857            #====Single block, data & phase CIF ===================================
1858            WriteCIFitem('data_'+self.CIFname)
1859            if phasenam is None: # if not already selected, select the first phase (should be one)
1860                phasenam = self.Phases.keys()[0]
1861            #print 'phasenam',phasenam
1862            #phaseblk = self.Phases[phasenam] # pointer to current phase info
1863            instnam = instnam.replace(' ','')
1864            WriteCIFitem('_pd_block_id',
1865                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1866                         str(self.shortauthorname) + "|" + instnam)
1867            WriteAudit()
1868            writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
1869            WriteOverall()
1870            writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1871            # report the phase info
1872            WritePhaseInfo(phasenam)
1873            if hist.startswith("PWDR"):
1874                # preferred orientation
1875                SH = FormatSH(phasenam)
1876                MD = FormatHAPpo(phasenam)
1877                if SH and MD:
1878                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1879                elif SH or MD:
1880                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1881                else:
1882                    WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1883                    # report profile, since one-block: include both histogram and phase info
1884                WriteCIFitem('_pd_proc_ls_profile_function',
1885                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
1886                    +'\n'+FormatPhaseProfile(phasenam))
1887                histblk = self.Histograms[hist]["Sample Parameters"]
1888                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
1889                WritePowderData(hist)
1890            elif hist.startswith("HKLF"):
1891                histprm = self.Histograms[hist]["Instrument Parameters"][0]
1892                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
1893                WriteSingleXtalData(hist)
1894        else:
1895            #=== multiblock: multiple phases and/or histograms ====================
1896            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
1897            try:
1898                dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
1899                Size = dlg.GetSize()
1900                Size = (int(Size[0]*3),Size[1]) # increase size along x
1901                dlg.SetSize(Size)
1902                dlg.CenterOnParent()
1903
1904                # publication info
1905                step = 1
1906                dlg.Update(step,"Exporting overall section")
1907                WriteCIFitem('\ndata_'+self.CIFname+'_publ')
1908                WriteAudit()
1909                WriteCIFitem('_pd_block_id',
1910                             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1911                             str(self.shortauthorname) + "|Overall")
1912                writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
1913                # ``template_publ.cif`` or a modified version
1914                # overall info
1915                WriteCIFitem('data_'+str(self.CIFname)+'_overall')
1916                WriteOverall()
1917                #============================================================
1918                WriteCIFitem('# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
1919                datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
1920                # loop over phase blocks
1921                if len(self.Phases) > 1:
1922                    loopprefix = ''
1923                    WriteCIFitem('loop_   _pd_phase_block_id')
1924                else:
1925                    loopprefix = '_pd_phase_block_id'
1926
1927                for phasenam in sorted(self.Phases.keys()):
1928                    i = self.Phases[phasenam]['pId']
1929                    datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1930                                 'phase_'+ str(i) + '|' + str(self.shortauthorname))
1931                    WriteCIFitem(loopprefix,datablockidDict[phasenam])
1932                # loop over data blocks
1933                if len(self.powderDict) + len(self.xtalDict) > 1:
1934                    loopprefix = ''
1935                    WriteCIFitem('loop_   _pd_block_diffractogram_id')
1936                else:
1937                    loopprefix = '_pd_block_diffractogram_id'
1938                for i in sorted(self.powderDict.keys()):
1939                    hist = self.powderDict[i]
1940                    histblk = self.Histograms[hist]
1941                    instnam = histblk["Sample Parameters"]['InstrName']
1942                    instnam = instnam.replace(' ','')
1943                    j = histblk['hId']
1944                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1945                                             str(self.shortauthorname) + "|" +
1946                                             instnam + "_hist_"+str(j))
1947                    WriteCIFitem(loopprefix,datablockidDict[hist])
1948                for i in sorted(self.xtalDict.keys()):
1949                    hist = self.xtalDict[i]
1950                    histblk = self.Histograms[hist]
1951                    instnam = histblk["Instrument Parameters"][0]['InstrName']
1952                    instnam = instnam.replace(' ','')
1953                    i = histblk['hId']
1954                    datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1955                                             str(self.shortauthorname) + "|" +
1956                                             instnam + "_hist_"+str(i))
1957                    WriteCIFitem(loopprefix,datablockidDict[hist])
1958                #============================================================
1959                # loop over phases, exporting them
1960                phasebyhistDict = {} # create a cross-reference to phases by histogram
1961                for j,phasenam in enumerate(sorted(self.Phases.keys())):
1962                    step += 1
1963                    dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
1964                    i = self.Phases[phasenam]['pId']
1965                    WriteCIFitem('\ndata_'+self.CIFname+"_phase_"+str(i))
1966                    WriteCIFitem('# Information for phase '+str(i))
1967                    WriteCIFitem('_pd_block_id',datablockidDict[phasenam])
1968                    # report the phase
1969                    writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1970                    WritePhaseInfo(phasenam)
1971                    # preferred orientation
1972                    if self.ifPWDR:
1973                        SH = FormatSH(phasenam)
1974                        MD = FormatHAPpo(phasenam)
1975                        if SH and MD:
1976                            WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1977                        elif SH or MD:
1978                            WriteCIFitem('_pd_proc_ls_pref_orient_corr', SH + MD)
1979                        else:
1980                            WriteCIFitem('_pd_proc_ls_pref_orient_corr', 'none')
1981                    # report sample profile terms
1982                    PP = FormatPhaseProfile(phasenam)
1983                    if PP:
1984                        WriteCIFitem('_pd_proc_ls_profile_function',PP)
1985
1986                #============================================================
1987                # loop over histograms, exporting them
1988                for i in sorted(self.powderDict.keys()):
1989                    hist = self.powderDict[i]
1990                    histblk = self.Histograms[hist]
1991                    if hist.startswith("PWDR"): 
1992                        step += 1
1993                        dlg.Update(step,"Exporting "+hist.strip())
1994                        WriteCIFitem('\ndata_'+self.CIFname+"_pwd_"+str(i))
1995                        #instnam = histblk["Sample Parameters"]['InstrName']
1996                        # report instrumental profile terms
1997                        WriteCIFitem('_pd_proc_ls_profile_function',
1998                            FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
1999                        WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
2000                        WriteCIFitem('_pd_block_id',datablockidDict[hist])
2001                        histprm = self.Histograms[hist]["Sample Parameters"]
2002                        writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
2003                        WritePowderData(hist)
2004                for i in sorted(self.xtalDict.keys()):
2005                    hist = self.xtalDict[i]
2006                    histblk = self.Histograms[hist]
2007                    if hist.startswith("HKLF"): 
2008                        step += 1
2009                        dlg.Update(step,"Exporting "+hist.strip())
2010                        WriteCIFitem('\ndata_'+self.CIFname+"_sx_"+str(i))
2011                        #instnam = histblk["Instrument Parameters"][0]['InstrName']
2012                        WriteCIFitem('# Information for histogram '+str(i)+': '+hist)
2013                        WriteCIFitem('_pd_block_id',datablockidDict[hist])
2014                        histprm = self.Histograms[hist]["Instrument Parameters"][0]
2015                        writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
2016                        WriteSingleXtalData(hist)
2017
2018            except Exception:
2019                import traceback
2020                print(traceback.format_exc())
2021                self.G2frame.ErrorDialog('Exception',
2022                                         'Error occurred in CIF creation. '+
2023                                         'See full error message in console output ')
2024            finally:
2025                dlg.Destroy()
2026
2027        WriteCIFitem('#--' + 15*'eof--' + '#')
2028        self.CloseFile()
2029        print("...export completed")
2030        print('file '+str(self.fullpath))
2031        # end of CIF export
2032
2033class ExportProjectCIF(ExportCIF):
2034    '''Used to create a CIF of an entire project
2035
2036    :param wx.Frame G2frame: reference to main GSAS-II frame
2037    '''
2038    def __init__(self,G2frame):
2039        ExportCIF.__init__(self,
2040            G2frame=G2frame,
2041            formatName = 'Full CIF',
2042            extension='.cif',
2043            longFormatName = 'Export project as CIF'
2044            )
2045        self.exporttype = ['project']
2046       
2047    def Exporter(self,event=None):
2048        self._Exporter(event=event)
2049
2050    def Writer(self,hist,mode='w'):
2051        # set the project file name
2052        self.CIFname = os.path.splitext(
2053            os.path.split(self.G2frame.GSASprojectfile)[1]
2054            )[0]+'_'+hist
2055        self.CIFname = self.CIFname.replace(' ','')
2056        self.OpenFile(mode=mode)
2057        self._Exporter(histOnly=hist)
2058        if mode == 'w':
2059            print('CIF written to file '+str(self.fullpath))
2060        self.CloseFile()
2061       
2062class ExportPhaseCIF(ExportCIF):
2063    '''Used to create a simple CIF with one phase. Uses exact same code as
2064    :class:`ExportCIF` except that `phaseOnly` is set for the Exporter
2065    Shows up in menu as Quick CIF.
2066
2067    :param wx.Frame G2frame: reference to main GSAS-II frame
2068    '''
2069    def __init__(self,G2frame):
2070        ExportCIF.__init__(self,
2071            G2frame=G2frame,
2072            formatName = 'Quick CIF',
2073            extension='.cif',
2074            longFormatName = 'Export one phase in CIF'
2075            )
2076        self.exporttype = ['phase']
2077        # CIF-specific items
2078        self.author = ''
2079
2080    def Exporter(self,event=None):
2081        # get a phase and file name
2082        # the export process starts here
2083        self.InitExport(event)
2084        # load all of the tree into a set of dicts
2085        self.loadTree()
2086        # create a dict with refined values and their uncertainties
2087        self.loadParmDict()
2088        self.multiple = False
2089        self.currentExportType = 'phase'
2090        if self.ExportSelect('ask'): return
2091        self._Exporter(event=event,phaseOnly=self.phasenam[0])
2092
2093    def Writer(self,hist,phasenam,mode='w'):
2094        # set the project file name
2095        self.CIFname = os.path.splitext(
2096            os.path.split(self.G2frame.GSASprojectfile)[1]
2097            )[0]+'_'+phasenam+'_'+hist
2098        self.CIFname = self.CIFname.replace(' ','')
2099        self.OpenFile(mode=mode)
2100        self._Exporter(phaseOnly=phasenam)
2101        self.CloseFile()
2102
2103class ExportPwdrCIF(ExportCIF):
2104    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2105    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2106    Shows up in menu as Quick CIF.
2107
2108    :param wx.Frame G2frame: reference to main GSAS-II frame
2109    '''
2110    def __init__(self,G2frame):
2111        ExportCIF.__init__(self,
2112            G2frame=G2frame,
2113            formatName = 'Data-only CIF',
2114            extension='.cif',
2115            longFormatName = 'Export data as CIF'
2116            )
2117        self.exporttype = ['powder']
2118        # CIF-specific items
2119        self.author = ''
2120
2121    def Exporter(self,event=None):
2122        self.InitExport(event)
2123        # load all of the tree into a set of dicts
2124        self.currentExportType = None
2125        self.loadTree()
2126        self.currentExportType = 'powder'
2127        # create a dict with refined values and their uncertainties
2128        self.loadParmDict()
2129        self.multiple = False
2130        if self.ExportSelect( # set export parameters
2131            AskFile='ask' # get a file name/directory to save in
2132            ): return
2133        self._Exporter(event=event,histOnly=self.histnam[0])
2134
2135class ExportHKLCIF(ExportCIF):
2136    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2137    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2138    Shows up in menu as Quick CIF.
2139
2140    :param wx.Frame G2frame: reference to main GSAS-II frame
2141    '''
2142    def __init__(self,G2frame):
2143        ExportCIF.__init__(self,
2144            G2frame=G2frame,
2145            formatName = 'Data-only CIF',
2146            extension='.cif',
2147            longFormatName = 'Export data as CIF'
2148            )
2149        self.exporttype = ['single']
2150        # CIF-specific items
2151        self.author = ''
2152
2153    def Exporter(self,event=None):
2154        self.InitExport(event)
2155        # load all of the tree into a set of dicts
2156        self.currentExportType = None
2157        self.loadTree()
2158        self.currentExportType = 'single'
2159        # create a dict with refined values and their uncertainties
2160        self.loadParmDict()
2161        self.multiple = False
2162        if self.ExportSelect( # set export parameters
2163            AskFile='ask' # get a file name/directory to save in
2164            ): return
2165        self._Exporter(event=event,histOnly=self.histnam[0])
2166               
2167#===============================================================================
2168# misc CIF utilities
2169#===============================================================================
2170def PickleCIFdict(fil):
2171    '''Loads a CIF dictionary, cherry picks out the items needed
2172    by local code and sticks them into a python dict and writes
2173    that dict out as a cPickle file for later reuse.
2174    If the write fails a warning message is printed,
2175    but no exception occurs.
2176
2177    :param str fil: file name of CIF dictionary, will usually end
2178      in .dic
2179    :returns: the dict with the definitions 
2180    '''
2181    import CifFile as cif # PyCifRW from James Hester
2182    cifdic = {}
2183    try:
2184        fp = open(fil,'r')             # patch: open file to avoid windows bug
2185        dictobj = cif.CifDic(fp)
2186        fp.close()
2187    except IOError:
2188        dictobj = cif.CifDic(fil)
2189    if DEBUG: print('loaded '+str(fil))
2190    for item in dictobj.keys():
2191        cifdic[item] = {}
2192        for j in (
2193            '_definition','_type',
2194            '_enumeration',
2195            '_enumeration_detail',
2196            '_enumeration_range'):
2197            if dictobj[item].get(j):
2198                cifdic[item][j] = dictobj[item][j]
2199    try:
2200        fil = os.path.splitext(fil)[0]+'.cpickle'
2201        fp = open(fil,'w')
2202        cPickle.dump(cifdic,fp)
2203        fp.close()
2204        if DEBUG: print('wrote '+str(fil))
2205    except:
2206        print ('Unable to write '+str(fil))
2207    return cifdic
2208
2209def LoadCIFdic():
2210    '''Create a composite core+powder CIF lookup dict containing
2211    information about all items in the CIF dictionaries, loading
2212    pickled files if possible. The routine looks for files
2213    named cif_core.cpickle and cif_pd.cpickle in every
2214    directory in the path and if they are not found, files
2215    cif_core.dic and/or cif_pd.dic are read.
2216
2217    :returns: the dict with the definitions 
2218    '''
2219    cifdic = {}
2220    for ftyp in "cif_core","cif_pd":
2221        for loc in sys.path:
2222            fil = os.path.join(loc,ftyp+".cpickle")
2223            if not os.path.exists(fil): continue
2224            fp = open(fil,'r')
2225            try:
2226                cifdic.update(cPickle.load(fp))
2227                if DEBUG: print('reloaded '+str(fil))
2228                break
2229            finally:
2230                fp.close()
2231        else:
2232            for loc in sys.path:
2233                fil = os.path.join(loc,ftyp+".dic")
2234                if not os.path.exists(fil): continue
2235                #try:
2236                if True:
2237                    cifdic.update(PickleCIFdict(fil))
2238                    break
2239                #except:
2240                #    pass
2241            else:
2242                print('Could not load '+ftyp+' dictionary')
2243    return cifdic
2244
2245class CIFdefHelp(wx.Button):
2246    '''Create a help button that displays help information on
2247    the current data item
2248
2249    :param parent: the panel which will be the parent of the button
2250    :param str msg: the help text to be displayed
2251    :param wx.Dialog helpwin: Frame for CIF editing dialog
2252    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
2253    '''
2254    def __init__(self,parent,msg,helpwin,helptxt):
2255        wx.Button.__init__(self,parent,wx.ID_HELP)
2256        self.Bind(wx.EVT_BUTTON,self._onPress)
2257        self.msg=msg
2258        self.parent = parent
2259        #self.helpwin = self.parent.helpwin
2260        self.helpwin = helpwin
2261        self.helptxt = helptxt
2262    def _onPress(self,event):
2263        'Respond to a button press by displaying the requested text'
2264        try:
2265            #helptxt = self.helptxt
2266            ow,oh = self.helptxt.GetSize()
2267            self.helptxt.SetLabel(self.msg)
2268            w,h = self.helptxt.GetSize()
2269            if h > oh:
2270                self.helpwin.GetSizer().Fit(self.helpwin)
2271        except: # error posting help, ignore
2272            return
2273
2274def CIF2dict(cf):
2275    '''copy the contents of a CIF out from a PyCifRW block object
2276    into a dict
2277
2278    :returns: cifblk, loopstructure where cifblk is a dict with
2279      CIF items and loopstructure is a list of lists that defines
2280      which items are in which loops.
2281    '''
2282    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
2283    loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
2284    dblk = {}
2285    for item in cf[blk].keys(): # make a copy of all the items in the block
2286        dblk[item] = cf[blk][item]
2287    return dblk,loopstructure
2288
2289def dict2CIF(dblk,loopstructure,blockname='Template'):
2290    '''Create a PyCifRW CIF object containing a single CIF
2291    block object from a dict and loop structure list.
2292
2293    :param dblk: a dict containing values for each CIF item
2294    :param list loopstructure: a list of lists containing the contents of
2295      each loop, as an example::
2296
2297         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2298
2299      this describes a CIF with this type of structure::
2300
2301        loop_ _a _b <a1> <b1> <a2> ...
2302        loop_ _c <c1> <c2>...
2303        loop _d_1 _d_2 _d_3 ...
2304
2305      Note that the values for each looped CIF item, such as _a,
2306      are contained in a list, for example as cifblk["_a"]
2307
2308    :param str blockname: an optional name for the CIF block.
2309      Defaults to 'Template'
2310
2311    :returns: the newly created PyCifRW CIF object
2312    '''
2313
2314    import CifFile as cif # PyCifRW from James Hester
2315    # compile a 'list' of items in loops
2316    loopnames = set()
2317    for i in loopstructure:
2318        loopnames |= set(i)
2319    # create a new block
2320    newblk = cif.CifBlock()
2321    # add the looped items
2322    for keys in loopstructure:
2323        vals = []
2324        for key in keys:
2325            vals.append(dblk[key])
2326        newblk.AddCifItem(([keys],[vals]))
2327    # add the non-looped items
2328    for item in dblk:
2329        if item in loopnames: continue
2330        newblk[item] = dblk[item]
2331    # create a CIF and add the block
2332    newcf = cif.CifFile()
2333    newcf[blockname] = newblk   
2334    return newcf
2335
2336
2337class EditCIFtemplate(wx.Dialog):
2338    '''Create a dialog for editing a CIF template. The edited information is
2339    placed in cifblk. If the CIF is saved as a file, the name of that file
2340    is saved as ``self.newfile``.
2341   
2342    :param wx.Frame parent: parent frame or None
2343    :param cifblk: dict or PyCifRW block containing values for each CIF item
2344    :param list loopstructure: a list of lists containing the contents of
2345      each loop, as an example::
2346
2347         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2348
2349      this describes a CIF with this type of structure::
2350
2351        loop_ _a _b <a1> <b1> <a2> ...
2352        loop_ _c <c1> <c2>...
2353        loop _d_1 _d_2 _d_3 ...
2354
2355      Note that the values for each looped CIF item, such as _a,
2356      are contained in a list, for example as cifblk["_a"]
2357     
2358    :param str defaultname: specifies the default file name to be used for
2359      saving the CIF.
2360    '''
2361    def __init__(self,parent,cifblk,loopstructure,defaultname):
2362        OKbuttons = []
2363        self.cifblk = cifblk
2364        self.loopstructure = loopstructure
2365        self.newfile = None
2366        self.defaultname = defaultname       
2367        global CIFdic  # once this is loaded, keep it around
2368        if CIFdic is None:
2369            CIFdic = LoadCIFdic()
2370        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2371
2372        # define widgets that will be needed during panel creation
2373        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
2374        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
2375        OKbuttons.append(savebtn)
2376        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
2377        OKbtn = wx.Button(self, wx.ID_OK, "Use")
2378        OKbtn.SetDefault()
2379        OKbuttons.append(OKbtn)
2380
2381        self.SetTitle('Edit items in CIF template')
2382        vbox = wx.BoxSizer(wx.VERTICAL)
2383        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
2384        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2385        G2G.HorizontalLine(vbox,self)
2386        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
2387        G2G.HorizontalLine(vbox,self)
2388        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2389        btn = wx.Button(self, wx.ID_CANCEL)
2390        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
2391        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
2392        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
2393        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2394        self.SetSizer(vbox)
2395        vbox.Fit(self)
2396    def Post(self):
2397        '''Display the dialog
2398       
2399        :returns: True unless Cancel has been pressed.
2400        '''
2401        return (self.ShowModal() == wx.ID_OK)
2402    def _onSave(self,event):
2403        'Save CIF entries in a template file'
2404        pth = G2G.GetExportPath(self.G2frame)
2405        dlg = wx.FileDialog(
2406            self, message="Save as CIF template",
2407            defaultDir=pth,
2408            defaultFile=self.defaultname,
2409            wildcard="CIF (*.cif)|*.cif",
2410            style=wx.SAVE)
2411        val = (dlg.ShowModal() == wx.ID_OK)
2412        fil = dlg.GetPath()
2413        dlg.Destroy()
2414        if val: # ignore a Cancel button
2415            fil = os.path.splitext(fil)[0]+'.cif' # force extension
2416            fp = open(fil,'w')
2417            newcf = dict2CIF(self.cifblk,self.loopstructure)
2418            fp.write(newcf.WriteOut())
2419            fp.close()
2420            self.newfile = fil
2421            self.EndModal(wx.ID_OK)
2422
2423class EditCIFpanel(wxscroll.ScrolledPanel):
2424    '''Creates a scrolled panel for editing CIF template items
2425
2426    :param wx.Frame parent: parent frame where panel will be placed
2427    :param cifblk: dict or PyCifRW block containing values for each CIF item
2428    :param list loopstructure: a list of lists containing the contents of
2429      each loop, as an example::
2430
2431         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2432
2433      this describes a CIF with this type of structure::
2434
2435        loop_ _a _b <a1> <b1> <a2> ...
2436        loop_ _c <c1> <c2>...
2437        loop _d_1 _d_2 _d_3 ...
2438
2439      Note that the values for each looped CIF item, such as _a,
2440      are contained in a list, for example as cifblk["_a"]
2441
2442    :param dict cifdic: optional CIF dictionary definitions
2443    :param list OKbuttons: A list of wx.Button objects that should
2444      be disabled when information in the CIF is invalid
2445    :param (other): optional keyword parameters for wx.ScrolledPanel
2446    '''
2447    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
2448        self.parent = parent
2449        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
2450        self.vbox = None
2451        self.AddDict = None
2452        self.cifdic = cifdic
2453        self.cifblk = cifblk
2454        self.loops = loopstructure
2455        self.parent = parent
2456        self.LayoutCalled = False
2457        self.parentOKbuttons = OKbuttons
2458        self.ValidatedControlsList = []
2459        self._fill()
2460    def _fill(self):
2461        'Fill the scrolled panel with widgets for each CIF item'
2462        wx.BeginBusyCursor()
2463        self.AddDict = {}
2464        self.ValidatedControlsList = []
2465        # delete any only contents
2466        if self.vbox:
2467            self.vbox.DeleteWindows()
2468            self.vbox = None
2469            self.Update()
2470        vbox = wx.BoxSizer(wx.VERTICAL)
2471        self.vbox = vbox
2472        # compile a 'list' of items in loops
2473        loopnames = set()
2474        for i in self.loops:
2475            loopnames |= set(i)
2476        # post the looped CIF items
2477        for lnum,lp in enumerate(self.loops):
2478            hbox = wx.BoxSizer(wx.HORIZONTAL)
2479            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
2480            vbox.Add(hbox)
2481            but = wx.Button(self,wx.ID_ANY,"Add row")
2482            self.AddDict[but]=lnum
2483           
2484            hbox.Add(but)           
2485            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
2486            fbox = wx.GridBagSizer(0, 0)
2487            vbox.Add(fbox)
2488            rows = 0
2489            for i,item in enumerate(lp):
2490                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
2491                fbox.Add(txt,(0,i+1))
2492                # if self.cifdic.get(item):
2493                #     df = self.cifdic[item].get('_definition')
2494                #     if df:
2495                #         txt.SetToolTipString(G2IO.trim(df))
2496                #         but = CIFdefHelp(self,
2497                #                          "Definition for "+item+":\n\n"+G2IO.trim(df),
2498                #                          self.parent,
2499                #                          self.parent.helptxt)
2500                #         fbox.Add(but,(1,i+1),flag=wx.ALIGN_CENTER)
2501                for j,val in enumerate(self.cifblk[item]):
2502                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
2503                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
2504                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
2505                if self.cifdic.get(item):
2506                    df = self.cifdic[item].get('_definition')
2507                    if df:
2508                        txt.SetToolTipString(G2IO.trim(df))
2509                        but = CIFdefHelp(self,
2510                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2511                                         self.parent,
2512                                         self.parent.helptxt)
2513                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
2514                rows = max(rows,len(self.cifblk[item]))
2515            for i in range(rows):
2516                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
2517                fbox.Add(txt,(i+2,0))
2518            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
2519            vbox.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2520               
2521        # post the non-looped CIF items
2522        for item in sorted(self.cifblk.keys()):
2523            if item not in loopnames:
2524                hbox = wx.BoxSizer(wx.HORIZONTAL)
2525                vbox.Add(hbox)
2526                txt = wx.StaticText(self,wx.ID_ANY,item)
2527                hbox.Add(txt)
2528                ent = self.CIFEntryWidget(self.cifblk,item,item)
2529                hbox.Add(ent)
2530                if self.cifdic.get(item):
2531                    df = self.cifdic[item].get('_definition')
2532                    if df:
2533                        txt.SetToolTipString(G2IO.trim(df))
2534                        but = CIFdefHelp(self,
2535                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2536                                         self.parent,
2537                                         self.parent.helptxt)
2538                        hbox.Add(but,0,wx.ALL,2)
2539        self.SetSizer(vbox)
2540        #vbox.Fit(self.parent)
2541        self.SetAutoLayout(1)
2542        self.SetupScrolling()
2543        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
2544        self.Layout()
2545        wx.EndBusyCursor()
2546    def OnLayoutNeeded(self,event):
2547        '''Called when an update of the panel layout is needed. Calls
2548        self.DoLayout after the current operations are complete using
2549        CallAfter. This is called only once, according to flag
2550        self.LayoutCalled, which is cleared in self.DoLayout.
2551        '''
2552        if self.LayoutCalled: return # call already queued
2553        wx.CallAfter(self.DoLayout) # queue a call
2554        self.LayoutCalled = True
2555    def DoLayout(self):
2556        '''Update the Layout and scroll bars for the Panel. Clears
2557        self.LayoutCalled so that next change to panel can
2558        request a new update
2559        '''
2560        wx.BeginBusyCursor()
2561        self.Layout()
2562        self.SetupScrolling()
2563        wx.EndBusyCursor()
2564        self.LayoutCalled = False
2565    def OnAddRow(self,event):
2566        'add a row to a loop'
2567        lnum = self.AddDict.get(event.GetEventObject())
2568        if lnum is None: return
2569        for item in self.loops[lnum]:
2570            self.cifblk[item].append('?')
2571        self._fill()
2572
2573    def ControlOKButton(self,setvalue):
2574        '''Enable or Disable the OK button(s) for the dialog. Note that this is
2575        passed into the ValidatedTxtCtrl for use by validators.
2576
2577        :param bool setvalue: if True, all entries in the dialog are
2578          checked for validity. The first invalid control triggers
2579          disabling of buttons.
2580          If False then the OK button(s) are disabled with no checking
2581          of the invalid flag for each control.
2582        '''
2583        if setvalue: # turn button on, do only if all controls show as valid
2584            for ctrl in self.ValidatedControlsList:
2585                if ctrl.invalid:
2586                    for btn in self.parentOKbuttons:
2587                        btn.Disable()
2588                    return
2589            else:
2590                for btn in self.parentOKbuttons:
2591                    btn.Enable()
2592        else:
2593            for btn in self.parentOKbuttons:
2594                btn.Disable()
2595       
2596    def CIFEntryWidget(self,dct,item,dataname):
2597        '''Create an entry widget for a CIF item. Use a validated entry for numb values
2598        where int is required when limits are integers and floats otherwise.
2599        At present this does not allow entry of the special CIF values of "." and "?" for
2600        numerical values and highlights them as invalid.
2601        Use a selection widget when there are specific enumerated values for a string.       
2602        '''
2603        if self.cifdic.get(dataname):
2604            if self.cifdic[dataname].get('_enumeration'):
2605                values = ['?']+self.cifdic[dataname]['_enumeration']
2606                choices = ['undefined']
2607                for i in self.cifdic[dataname].get('_enumeration_detail',values):
2608                    choices.append(G2IO.trim(i))
2609                ent = G2G.EnumSelector(self, dct, item, choices, values, size=(200, -1))
2610                return ent
2611            if self.cifdic[dataname].get('_type') == 'numb':
2612                mn = None
2613                mx = None
2614                hint = int
2615                if self.cifdic[dataname].get('_enumeration_range'):
2616                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
2617                    if '.' in rng[0] or '.' in rng[1]: hint = float
2618                    if rng[0]: mn = hint(rng[0])
2619                    if rng[1]: mx = hint(rng[1])
2620                    ent = G2G.ValidatedTxtCtrl(
2621                        self,dct,item,typeHint=hint,min=mn,max=mx,
2622                        CIFinput=True,ASCIIonly=True,
2623                        OKcontrol=self.ControlOKButton)
2624                    self.ValidatedControlsList.append(ent)
2625                    return ent
2626        rw1 = rw.ResizeWidget(self)
2627        ent = G2G.ValidatedTxtCtrl(
2628            rw1,dct,item,size=(100, 20),
2629            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
2630            CIFinput=True,ASCIIonly=True,
2631            OKcontrol=self.ControlOKButton)
2632        self.ValidatedControlsList.append(ent)
2633        return rw1
2634
2635class CIFtemplateSelect(wx.BoxSizer):
2636    '''Create a set of buttons to show, select and edit a CIF template
2637   
2638    :param frame: wx.Frame object of parent
2639    :param panel: wx.Panel object where widgets should be placed
2640    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
2641      the type of template
2642    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
2643      "CIF_template" will be used to store either a list or a string.
2644      If a list, it will contain a dict and a list defining loops. If
2645      an str, it will contain a file name.   
2646    :param function repaint: reference to a routine to be called to repaint
2647      the frame after a change has been made
2648    :param str title: A line of text to show at the top of the window
2649    :param str defaultname: specifies the default file name to be used for
2650      saving the CIF.
2651    '''
2652    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
2653        wx.BoxSizer.__init__(self,wx.VERTICAL)
2654        self.cifdefs = frame
2655        self.dict = G2dict
2656        self.repaint = repaint
2657        templateDefName = 'template_'+tmplate+'.cif'
2658        self.CIF = G2dict.get("CIF_template")
2659        if defaultname:
2660            self.defaultname = defaultname.encode('ascii','replace').strip().replace(' ','_')
2661            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
2662            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
2663        else:
2664            self.defaultname = ''
2665           
2666        txt = wx.StaticText(panel,wx.ID_ANY,title)
2667        self.Add(txt,0,wx.ALIGN_CENTER)
2668        # change font on title
2669        txtfnt = txt.GetFont()
2670        txtfnt.SetWeight(wx.BOLD)
2671        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
2672        txt.SetFont(txtfnt)
2673        self.Add((-1,3))
2674
2675        if not self.CIF: # empty or None
2676            for pth in [os.getcwd()]+sys.path:
2677                fil = os.path.join(pth,self.defaultname)
2678                if os.path.exists(fil) and self.defaultname:
2679                    self.CIF = fil
2680                    CIFtxt = "Template: "+self.defaultname
2681                    break
2682            else:
2683                for pth in sys.path:
2684                    fil = os.path.join(pth,templateDefName)
2685                    if os.path.exists(fil):
2686                        self.CIF = fil
2687                        CIFtxt = "Template: "+templateDefName
2688                        break
2689                else:
2690                    print("Default CIF template "+self.defaultname+' not found in path!')
2691                    self.CIF = None
2692                    CIFtxt = "none! (No template found)"
2693        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
2694            if not os.path.exists(self.CIF):
2695                print("Error: template file has disappeared: "+self.CIF)
2696                self.CIF = None
2697                CIFtxt = "none! (file not found)"
2698            else:
2699                if len(self.CIF) < 50:
2700                    CIFtxt = "File: "+self.CIF
2701                else:
2702                    CIFtxt = "File: ..."+self.CIF[-50:]
2703        else:
2704            CIFtxt = "Template is customized"
2705        # show template source
2706        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
2707        # show str, button to select file; button to edit (if CIF defined)
2708        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
2709        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
2710        hbox =  wx.BoxSizer(wx.HORIZONTAL)
2711        hbox.Add(but,0,0,2)
2712        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
2713        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
2714        if self.CIF is None: but.Disable() # nothing to edit!
2715        hbox.Add(but,0,0,2)
2716        self.Add(hbox)
2717    def _onGetTemplateFile(self,event):
2718        'select a template file'
2719        pth = G2G.GetImportPath(self.G2frame)
2720        if not pth: pth = '.'
2721        dlg = wx.FileDialog(
2722            self.cifdefs, message="Read CIF template file",
2723            defaultDir=pth,
2724            defaultFile=self.defaultname,
2725            wildcard="CIF (*.cif)|*.cif",
2726            style=wx.OPEN)
2727        ret = dlg.ShowModal()
2728        fil = dlg.GetPath()
2729        dlg.Destroy()
2730        if ret == wx.ID_OK:
2731            try:
2732                cf = G2IO.ReadCIF(fil)
2733                if len(cf.keys()) == 0:
2734                    raise Exception,"No CIF data_ blocks found"
2735                if len(cf.keys()) != 1:
2736                    raise Exception, 'Error, CIF Template has more than one block: '+fil
2737                self.dict["CIF_template"] = fil
2738            except Exception as err:
2739                print('\nError reading CIF: '+fil)
2740                dlg = wx.MessageDialog(self.cifdefs,
2741                                   'Error reading CIF '+fil,
2742                                   'Error in CIF file',
2743                                   wx.OK)
2744                dlg.ShowModal()
2745                dlg.Destroy()
2746                print(err.message)
2747                return
2748            self.repaint() #EditCIFDefaults()
2749
2750    def _onEditTemplateContents(self,event):
2751        'Called to edit the contents of a CIF template'
2752        if type(self.CIF) is list or  type(self.CIF) is tuple:
2753            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
2754        else:
2755            cf = G2IO.ReadCIF(self.CIF)
2756            dblk,loopstructure = CIF2dict(cf)
2757        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
2758        val = dlg.Post()
2759        if val:
2760            if dlg.newfile: # results saved in file
2761                self.dict["CIF_template"] = dlg.newfile
2762            else:
2763                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
2764            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
2765        else:
2766            dlg.Destroy()       
2767
2768#===============================================================================
2769# end of misc CIF utilities
2770#===============================================================================
Note: See TracBrowser for help on using the repository browser.