source: trunk/exports/G2export_CIF.py @ 3216

Last change on this file since 3216 was 3216, checked in by toby, 5 years ago

add data & parameter access in scripting

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