source: trunk/exports/G2export_CIF.py @ 3000

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

make the two frame version the trunk as we hit version 3000

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