source: trunk/exports/G2cif.py @ 1079

Last change on this file since 1079 was 1079, checked in by vondreele, 8 years ago

small changes to G2cif for macromolecular structures; make atom names & skip dist/angle calcs.

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