source: trunk/exports/G2export_CIF.py @ 2433

Last change on this file since 2433 was 2433, checked in by toby, 7 years ago

update for sequential export

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