source: trunk/exports/G2export_CIF.py @ 3024

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

fix DisAgl? problem in CIF export

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