source: trunk/exports/G2export_CIF.py @ 3260

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

misc Py3 fixes

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