source: trunk/exports/G2export_CIF.py @ 4522

Last change on this file since 4522 was 4474, checked in by vondreele, 18 months ago

fix minor cif problem - order of "source" and "neutron"

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