source: trunk/exports/G2export_CIF.py @ 3136

Last change on this file since 3136 was 3136, checked in by vondreele, 4 years ago

make GSAS-II python 3.6 compliant & preserve python 2.7 use;changes:
do from future import division, print_function for all GSAS-II py sources
all menu items revised to be py 2.7/3.6 compliant
all wx.OPEN --> wx.FD_OPEN in file dialogs
all integer divides (typically for image pixel math) made explicit with ; ambiguous ones made floats as appropriate
all print "stuff" --> print (stuff)
all print >> pFile,'stuff' --> pFile.writeCIFtemplate('stuff')
all read file opens made explicit 'r' or 'rb'
all cPickle imports made for py2.7 or 3.6 as cPickle or _pickle; test for '2' platform.version_tuple[0] for py 2.7
define cPickleload to select load(fp) or load(fp,encoding='latin-1') for loading gpx files; provides cross compatibility between py 2.7/3.6 gpx files
make dict.keys() as explicit list(dict.keys()) as needed (NB: possible source of remaining py3.6 bugs)
make zip(a,b) as explicit list(zip(a,b)) as needed (NB: possible source of remaining py3.6 bugs)
select unichr/chr according test for '2' platform.version_tuple[0] for py 2.7 (G2pwdGUI * G2plot) for special characters
select wg.EVT_GRID_CELL_CHANGE (classic) or wg.EVT_GRID_CELL_CHANGED (phoenix) in grid Bind
maxint --> maxsize; used in random number stuff
raise Exception,"stuff" --> raise Exception("stuff")
wx 'classic' sizer.DeleteWindows?() or 'phoenix' sizer.Clear(True)
wx 'classic' SetToolTipString?(text) or 'phoenix' SetToolTip?(wx.ToolTip?(text)); define SetToolTipString?(self,text) to handle the choice in plots
status.SetFields? --> status.SetStatusText?
'classic' AddSimpleTool? or 'phoenix' self.AddTool? for plot toolbar; Bind different as well
define GetItemPydata? as it doesn't exist in wx 'phoenix'
allow python versions 2.7 & 3.6 to run GSAS-II
Bind override commented out - no logging capability (NB: remove all logging code?)
all import ContentsValidator? open filename & test if valid then close; filepointer removed from Reader
binary importers (mostly images) test for 'byte' type & convert as needed to satisfy py 3.6 str/byte rules

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