source: trunk/exports/G2export_CIF.py @ 3101

Last change on this file since 3101 was 3101, checked in by vondreele, 5 years ago

change SetScrollRate?(1,1) to SetScrollRate?(10,10) - improves scrolling speed on all windows

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