source: trunk/exports/G2export_CIF.py @ 4411

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

bug in CIF export; misc cleanup

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 138.6 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2020-04-24 16:27:10 +0000 (Fri, 24 Apr 2020) $
5# $Author: toby $
6# $Revision: 4411 $
7# $URL: trunk/exports/G2export_CIF.py $
8# $Id: G2export_CIF.py 4411 2020-04-24 16:27:10Z toby $
9########### SVN repository information ###################
10'''
11*Module G2export_CIF: CIF Exports*
12------------------------------------------------------
13
14This implements a complex exporter :class:`ExportCIF` that can implement an
15entire project in a complete CIF intended for submission as a
16publication. In addition, there are three subclasses of :class:`ExportCIF`:
17:class:`ExportProjectCIF`,
18:class:`ExportPhaseCIF` and :class:`ExportDataCIF` where extra parameters
19for the _Exporter() determine if a project, single phase or data set are written.
20'''
21
22from __future__ import division, print_function
23import platform
24import datetime as dt
25import os.path
26import sys
27import numpy as np
28if '2' in platform.python_version_tuple()[0]:
29    import cPickle as pickle
30else:
31    import pickle
32import copy
33import re
34try:
35    import wx
36    import wx.lib.scrolledpanel as wxscroll
37    import wx.lib.resizewidget as rw
38except ImportError:
39    # Avoid wx dependency for CLI
40    class Placeholder(object):
41        def __init__(self):
42            self.BoxSizer = object
43            self.Button = object
44            self.Dialog = object
45            self.ScrolledPanel = object
46    wx = Placeholder()
47    wxscroll = Placeholder()
48import GSASIIpath
49GSASIIpath.SetVersionNumber("$Revision: 4411 $")
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',1,2):
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            for phasenam in histblk['Reflection Lists']:
1341                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1342                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
1343                I100 = scale*refList.T[8]*refList.T[11]
1344                #Icorr = np.array([refl[13] for refl in histblk['Reflection Lists'][phasenam]])[0]
1345                #FO2 = np.array([refl[8] for refl in histblk['Reflection Lists'][phasenam]])
1346                #I100 = scale*FO2*Icorr
1347                Imax = max(Imax,max(I100))
1348
1349            WriteCIFitem(self.fp, 'loop_')
1350            if len(histblk['Reflection Lists'].keys()) > 1:
1351                WriteCIFitem(self.fp, '   _pd_refln_phase_id')
1352            WriteCIFitem(self.fp, '   ' + refprx + 'index_h' +
1353                         '\n   ' + refprx + 'index_k' +
1354                         '\n   ' + refprx + 'index_l' +
1355                         '\n   ' + refprx + 'F_squared_meas' +
1356                         '\n   ' + refprx + 'F_squared_calc' +
1357                         '\n   ' + refprx + 'phase_calc' +
1358                         '\n   _refln_d_spacing')
1359            if Imax > 0:
1360                WriteCIFitem(self.fp, '   _gsas_i100_meas')
1361
1362            refcount = 0
1363            hklmin = None
1364            hklmax = None
1365            dmax = None
1366            dmin = None
1367            for phasenam in histblk['Reflection Lists']:
1368                scale = self.Phases[phasenam]['Histograms'][histlbl]['Scale'][0]
1369                phaseid = self.Phases[phasenam]['pId']
1370                refcount += len(histblk['Reflection Lists'][phasenam]['RefList'])
1371                refList = np.asarray(histblk['Reflection Lists'][phasenam]['RefList'])
1372                I100 = scale*refList.T[8]*refList.T[11]
1373                for j,ref in enumerate(histblk['Reflection Lists'][phasenam]['RefList']):
1374                    if DEBUG:
1375                        print('DEBUG: skipping reflection list')
1376                        break
1377                    if hklmin is None:
1378                        hklmin = copy.copy(ref[0:3])
1379                        hklmax = copy.copy(ref[0:3])
1380                    if dmin is None:
1381                         dmax = dmin = ref[4]
1382                    if len(histblk['Reflection Lists'].keys()) > 1:
1383                        s = PutInCol(phaseid,2)
1384                    else:
1385                        s = ""
1386                    for i,hkl in enumerate(ref[0:3]):
1387                        hklmax[i] = max(hkl,hklmax[i])
1388                        hklmin[i] = min(hkl,hklmin[i])
1389                        s += PutInCol(int(hkl),4)
1390                    for I in ref[8:10]:
1391                        s += PutInCol(G2mth.ValEsd(I,-0.0009),10)
1392                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1393                    dmax = max(dmax,ref[4])
1394                    dmin = min(dmin,ref[4])
1395                    s += PutInCol(G2mth.ValEsd(ref[4],-0.00009),8)
1396                    if Imax > 0:
1397                        s += PutInCol(G2mth.ValEsd(100.*I100[j]/Imax,-0.09),6)
1398                    WriteCIFitem(self.fp, "  "+s)
1399
1400            WriteReflStat(refcount,hklmin,hklmax,dmin,dmax,len(histblk['Reflection Lists']))
1401            WriteCIFitem(self.fp, '\n# POWDER DATA TABLE')
1402            # is data fixed step? If the step varies by <0.01% treat as fixed step
1403            steps = histblk['Data'][0][1:] - histblk['Data'][0][:-1]
1404            if abs(max(steps)-min(steps)) > abs(max(steps))/10000.:
1405                fixedstep = False
1406            else:
1407                fixedstep = True
1408
1409            zero = None
1410            if fixedstep and 'T' not in inst['Type'][0]: # and not TOF
1411                WriteCIFitem(self.fp, '_pd_meas_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0],-0.00009))
1412                WriteCIFitem(self.fp, '_pd_meas_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1],-0.00009))
1413                WriteCIFitem(self.fp, '_pd_meas_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1414                # zero correct, if defined
1415                zerolst = histblk['Instrument Parameters'][0].get('Zero')
1416                if zerolst: zero = zerolst[1]
1417                zero = self.parmDict.get('Zero',zero)
1418                if zero:
1419                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_min', G2mth.ValEsd(histblk['Data'][0][0]-zero,-0.00009))
1420                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_max', G2mth.ValEsd(histblk['Data'][0][-1]-zero,-0.00009))
1421                    WriteCIFitem(self.fp, '_pd_proc_2theta_range_inc', G2mth.ValEsd(steps.sum()/len(steps),-0.00009))
1422
1423            if zero:
1424                WriteCIFitem(self.fp, '_pd_proc_number_of_points', str(len(histblk['Data'][0])))
1425            else:
1426                WriteCIFitem(self.fp, '_pd_meas_number_of_points', str(len(histblk['Data'][0])))
1427            WriteCIFitem(self.fp, '\nloop_')
1428            #            WriteCIFitem(self.fp, '   _pd_proc_d_spacing') # need easy way to get this
1429            if not fixedstep:
1430                if zero:
1431                    WriteCIFitem(self.fp, '   _pd_proc_2theta_corrected')
1432                elif 'T' in inst['Type'][0]: # and not TOF
1433                    WriteCIFitem(self.fp, '   _pd_meas_time_of_flight')
1434                else:
1435                    WriteCIFitem(self.fp, '   _pd_meas_2theta_scan')
1436            # at least for now, always report weights.
1437            #if countsdata:
1438            #    WriteCIFitem(self.fp, '   _pd_meas_counts_total')
1439            #else:
1440            WriteCIFitem(self.fp, '   _pd_meas_intensity_total')
1441            WriteCIFitem(self.fp, '   _pd_calc_intensity_total')
1442            WriteCIFitem(self.fp, '   _pd_proc_intensity_bkg_calc')
1443            WriteCIFitem(self.fp, '   _pd_proc_ls_weight')
1444            maxY = max(histblk['Data'][1].max(),histblk['Data'][3].max())
1445            if maxY < 0: maxY *= -10 # this should never happen, but...
1446            ndec = max(0,10-int(np.log10(maxY))-1) # 10 sig figs should be enough
1447            maxSU = histblk['Data'][2].max()
1448            if maxSU < 0: maxSU *= -1 # this should never happen, but...
1449            ndecSU = max(0,8-int(np.log10(maxSU))-1) # 8 sig figs should be enough
1450            lowlim,highlim = histblk['Limits'][1]
1451
1452            if DEBUG:
1453                print('DEBUG: skipping profile list')
1454            else:
1455                for x,yobs,yw,ycalc,ybkg in zip(histblk['Data'][0],
1456                                                histblk['Data'][1],
1457                                                histblk['Data'][2],
1458                                                histblk['Data'][3],
1459                                                histblk['Data'][4]):
1460                    if lowlim <= x <= highlim:
1461                        pass
1462                    else:
1463                        yw = 0.0 # show the point is not in use
1464
1465                    if fixedstep:
1466                        s = ""
1467                    elif zero:
1468                        s = PutInCol(G2mth.ValEsd(x-zero,-0.00009),10)
1469                    else:
1470                        s = PutInCol(G2mth.ValEsd(x,-0.00009),10)
1471                    s += PutInCol(Yfmt(ndec,yobs),12)
1472                    s += PutInCol(Yfmt(ndec,ycalc),12)
1473                    s += PutInCol(Yfmt(ndec,ybkg),11)
1474                    s += PutInCol(Yfmt(ndecSU,yw),9)
1475                    WriteCIFitem(self.fp, "  "+s)
1476
1477        def WriteSingleXtalData(histlbl):
1478            'Write out the selected single crystal histogram info'
1479            histblk = self.Histograms[histlbl]
1480
1481            #refprx = '_refln.' # mm
1482            refprx = '_refln_' # normal
1483
1484            WriteCIFitem(self.fp, '\n# STRUCTURE FACTOR TABLE')
1485            WriteCIFitem(self.fp, 'loop_' +
1486                         '\n   ' + refprx + 'index_h' +
1487                         '\n   ' + refprx + 'index_k' +
1488                         '\n   ' + refprx + 'index_l' +
1489                         '\n   ' + refprx + 'F_squared_meas' +
1490                         '\n   ' + refprx + 'F_squared_sigma' +
1491                         '\n   ' + refprx + 'F_squared_calc' +
1492                         '\n   ' + refprx + 'phase_calc'
1493                         )
1494
1495            hklmin = None
1496            hklmax = None
1497            dmax = None
1498            dmin = None
1499            refcount = len(histblk['Data']['RefList'])
1500            for ref in histblk['Data']['RefList']:
1501                if ref[3] <= 0:      #skip user rejected reflections (mul <= 0)
1502                    continue
1503                s = "  "
1504                if hklmin is None:
1505                    hklmin = copy.copy(ref[0:3])
1506                    hklmax = copy.copy(ref[0:3])
1507                    dmax = dmin = ref[4]
1508                for i,hkl in enumerate(ref[0:3]):
1509                    hklmax[i] = max(hkl,hklmax[i])
1510                    hklmin[i] = min(hkl,hklmin[i])
1511                    s += PutInCol(int(hkl),4)
1512                if ref[5] == 0.0:
1513                    s += PutInCol(G2mth.ValEsd(ref[8],0),12)
1514                    s += PutInCol('.',10)
1515                    s += PutInCol(G2mth.ValEsd(ref[9],0),12)
1516                    s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1517                else:
1518                    sig = ref[6] * ref[8] / ref[5]
1519                    s += PutInCol(G2mth.ValEsd(ref[8],-abs(sig/10)),12)
1520                    s += PutInCol(G2mth.ValEsd(sig,-abs(sig)/10.),10)
1521                    s += PutInCol(G2mth.ValEsd(ref[9],-abs(sig/10)),12)
1522                s += PutInCol(G2mth.ValEsd(ref[10],-0.9),7)
1523                dmax = max(dmax,ref[4])
1524                dmin = min(dmin,ref[4])
1525                WriteCIFitem(self.fp, s)
1526            if not self.quickmode: # statistics only in a full CIF
1527                WriteReflStat(refcount,hklmin,hklmax,dmin,dmax)
1528                hId = histblk['hId']
1529                hfx = '0:'+str(hId)+':'
1530                phfx = '%d:%d:'%(0,hId)
1531                extType,extModel,extParms = self.Phases[phasenam]['Histograms'][histlbl]['Extinction']
1532                if extModel != 'None':
1533                    WriteCIFitem(self.fp, '# Extinction scaled by 1.e5')
1534                    WriteCIFitem(self.fp, '_refine_ls_extinction_method','Becker-Coppens %s %s'%(extModel,extType))
1535                    sig = -1.e-3
1536                    if extModel == 'Primary':
1537                        parm = extParms['Ep'][0]*1.e5
1538                        if extParms['Ep'][1]:
1539                            sig = self.sigDict[phfx+'Ep']*1.e5
1540                        text = G2mth.ValEsd(parm,sig)
1541                    elif extModel == 'Secondary Type I':
1542                        parm = extParms['Eg'][0]*1.e5
1543                        if extParms['Eg'][1]:
1544                            sig = self.sigDict[phfx+'Eg']*1.e5
1545                        text = G2mth.ValEsd(parm,sig)
1546                    elif extModel == 'Secondary Type II':
1547                        parm = extParms['Es'][0]*1.e5
1548                        if extParms['Es'][1]:
1549                            sig = self.sigDict[phfx+'Es']*1.e5
1550                        text = G2mth.ValEsd(parm,sig)
1551                    elif extModel == 'Secondary Type I & II':
1552                        parm = extParms['Eg'][0]*1.e5
1553                        if extParms['Es'][1]:
1554                            sig = self.sigDict[phfx+'Es']*1.e5
1555                        text = G2mth.ValEsd(parm,sig)
1556                        sig = -1.0e-3
1557                        parm = extParms['Es'][0]*1.e5
1558                        if extParms['Es'][1]:
1559                            sig = self.sigDict[phfx+'Es']*1.e5
1560                        text += G2mth.ValEsd(parm,sig)
1561                    WriteCIFitem(self.fp, '_refine_ls_extinction_coef',text)
1562                    WriteCIFitem(self.fp, '_refine_ls_extinction_expression','Becker & Coppens (1974). Acta Cryst. A30, 129-147')
1563
1564                WriteCIFitem(self.fp, '_refine_ls_wR_factor_gt    ','%.4f'%(histblk['wR']/100.))
1565                WriteCIFitem(self.fp, '_refine_ls_R_factor_gt     ','%.4f'%(histblk[hfx+'Rf']/100.))
1566                WriteCIFitem(self.fp, '_refine_ls_R_Fsqd_factor   ','%.4f'%(histblk[hfx+'Rf^2']/100.))
1567        def EditAuthor(event=None):
1568            'dialog to edit the CIF author info'
1569            'Edit the CIF author name'
1570            dlg = G2G.SingleStringDialog(self.G2frame,
1571                                          'Get CIF Author',
1572                                          'Provide CIF Author name (Last, First)',
1573                                          value=self.author)
1574            if not dlg.Show():
1575                dlg.Destroy()
1576                return False  # cancel was pressed
1577            self.author = dlg.GetValue()
1578            self.shortauthorname = self.author.replace(',','').replace(' ','')[:20]
1579            dlg.Destroy()
1580            try:
1581                self.OverallParms['Controls']["Author"] = self.author # save for future
1582            except KeyError:
1583                pass
1584            return True
1585
1586        def EditInstNames(event=None):
1587            'Provide a dialog for editing instrument names'
1588            dictlist = []
1589            keylist = []
1590            lbllist = []
1591            for hist in self.Histograms:
1592                if hist.startswith("PWDR"):
1593                    key2 = "Sample Parameters"
1594                    d = self.Histograms[hist][key2]
1595                elif hist.startswith("HKLF"):
1596                    key2 = "Instrument Parameters"
1597                    d = self.Histograms[hist][key2][0]
1598
1599                lbllist.append(hist)
1600                dictlist.append(d)
1601                keylist.append('InstrName')
1602                instrname = d.get('InstrName')
1603                if instrname is None:
1604                    d['InstrName'] = ''
1605            return G2G.CallScrolledMultiEditor(
1606                self.G2frame,dictlist,keylist,
1607                prelbl=range(1,len(dictlist)+1),
1608                postlbl=lbllist,
1609                title='Instrument names',
1610                header="Edit instrument names. Note that a non-blank\nname is required for all histograms",
1611                CopyButton=True,ASCIIonly=True)
1612
1613        def EditRanges(event):
1614            '''Edit the bond distance/angle search range; phase is determined from
1615            a pointer placed in the button object (.phasedict) that references the
1616            phase dictionary
1617            '''
1618            but = event.GetEventObject()
1619            phasedict = but.phasedict
1620            dlg = G2G.DisAglDialog(
1621                self.G2frame,
1622                phasedict['General']['DisAglCtls'], # edited
1623                phasedict['General'], # defaults
1624                )
1625            if dlg.ShowModal() == wx.ID_OK:
1626                phasedict['General']['DisAglCtls'] = dlg.GetData()
1627            dlg.Destroy()
1628
1629        def EditCIFDefaults():
1630            '''Fills the CIF Defaults window with controls for editing various CIF export
1631            parameters (mostly related to templates).
1632            '''
1633            self.cifdefs.DestroyChildren()
1634            self.cifdefs.SetTitle('Edit CIF settings')
1635            vbox = wx.BoxSizer(wx.VERTICAL)
1636            vbox.Add(wx.StaticText(self.cifdefs, wx.ID_ANY,'Creating file '+self.filename))
1637            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit CIF Author')
1638            but.Bind(wx.EVT_BUTTON,EditAuthor)
1639            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1640            but = wx.Button(self.cifdefs, wx.ID_ANY,'Edit Instrument Name(s)')
1641            but.Bind(wx.EVT_BUTTON,EditInstNames)
1642            vbox.Add(but,0,wx.ALIGN_CENTER,3)
1643            cpnl = wxscroll.ScrolledPanel(self.cifdefs,size=(300,300))
1644            cbox = wx.BoxSizer(wx.VERTICAL)
1645            G2G.HorizontalLine(cbox,cpnl)
1646            cbox.Add(
1647                CIFtemplateSelect(self.cifdefs,
1648                                  cpnl,'publ',self.OverallParms['Controls'],
1649                                  EditCIFDefaults,
1650                                  "Publication (overall) template",
1651                                  ),
1652                0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1653            for phasenam in sorted(self.Phases.keys()):
1654                G2G.HorizontalLine(cbox,cpnl)
1655                title = 'Phase '+phasenam
1656                phasedict = self.Phases[phasenam] # pointer to current phase info
1657                cbox.Add(
1658                    CIFtemplateSelect(self.cifdefs,
1659                                      cpnl,'phase',phasedict['General'],
1660                                      EditCIFDefaults,
1661                                      title,
1662                                      phasenam),
1663                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1664                cpnl.SetSizer(cbox)
1665                if phasedict['General']['Type'] == 'nuclear':
1666                    but = wx.Button(cpnl, wx.ID_ANY,'Edit distance/angle ranges')
1667                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1668                    cbox.Add((-1,2))
1669                    but.phasedict = self.Phases[phasenam]  # set a pointer to current phase info
1670                    but.Bind(wx.EVT_BUTTON,EditRanges)     # phase bond/angle ranges
1671                    but = wx.Button(cpnl, wx.ID_ANY,'Set distance/angle publication flags')
1672                    but.phase = phasenam  # set a pointer to current phase info
1673                    but.Bind(wx.EVT_BUTTON,SelectDisAglFlags)     # phase bond/angle ranges
1674                    cbox.Add(but,0,wx.ALIGN_LEFT,0)
1675                cbox.Add((-1,2))
1676            for i in sorted(self.powderDict.keys()):
1677                G2G.HorizontalLine(cbox,cpnl)
1678                hist = self.powderDict[i]
1679                histblk = self.Histograms[hist]
1680                title = 'Powder dataset '+hist[5:]
1681                cbox.Add(
1682                    CIFtemplateSelect(self.cifdefs,
1683                                      cpnl,'powder',histblk["Sample Parameters"],
1684                                      EditCIFDefaults,
1685                                      title,
1686                                      histblk["Sample Parameters"]['InstrName']),
1687                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1688            for i in sorted(self.xtalDict.keys()):
1689                G2G.HorizontalLine(cbox,cpnl)
1690                hist = self.xtalDict[i]
1691                histblk = self.Histograms[hist]
1692                title = 'Single Xtal dataset '+hist[5:]
1693                cbox.Add(
1694                    CIFtemplateSelect(self.cifdefs,
1695                                      cpnl,'single',histblk["Instrument Parameters"][0],
1696                                      EditCIFDefaults,
1697                                      title,
1698                                      histblk["Instrument Parameters"][0]['InstrName']),
1699                    0,wx.EXPAND|wx.ALIGN_LEFT|wx.ALL)
1700            cpnl.SetSizer(cbox)
1701            cpnl.SetAutoLayout(1)
1702            cpnl.SetupScrolling()
1703            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1704            cpnl.Layout()
1705
1706            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1707            btnsizer = wx.StdDialogButtonSizer()
1708            btn = wx.Button(self.cifdefs, wx.ID_OK, "Create CIF")
1709            btn.SetDefault()
1710            btnsizer.AddButton(btn)
1711            btn = wx.Button(self.cifdefs, wx.ID_CANCEL)
1712            btnsizer.AddButton(btn)
1713            btnsizer.Realize()
1714            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1715            self.cifdefs.SetSizer(vbox)
1716            vbox.Fit(self.cifdefs)
1717            self.cifdefs.Layout()
1718
1719        def OnToggleButton(event):
1720            'Respond to press of ToggleButton in SelectDisAglFlags'
1721            but = event.GetEventObject()
1722            if but.GetValue():
1723                but.DisAglSel[but.key] = True
1724            else:
1725                try:
1726                    del but.DisAglSel[but.key]
1727                except KeyError:
1728                    pass
1729        def keepTrue(event):
1730            event.GetEventObject().SetValue(True)
1731        def keepFalse(event):
1732            event.GetEventObject().SetValue(False)
1733
1734        def SelectDisAglFlags(event):
1735            'Select Distance/Angle use flags for the selected phase'
1736            phasenam = event.GetEventObject().phase
1737            phasedict = self.Phases[phasenam]
1738            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(phasedict['General']['SGData'])
1739            generalData = phasedict['General']
1740            # create a dict for storing Pub flag for bonds/angles, if needed
1741            if phasedict['General'].get("DisAglHideFlag") is None:
1742                phasedict['General']["DisAglHideFlag"] = {}
1743            DisAngSel = phasedict['General']["DisAglHideFlag"]
1744
1745            cx,ct,cs,cia = phasedict['General']['AtomPtrs']
1746            cn = ct-1
1747            cfrac = cx+3
1748            DisAglData = {}
1749            # create a list of atoms, but skip atoms with zero occupancy
1750            xyz = []
1751            fpfx = str(phasedict['pId'])+'::Afrac:'
1752            for i,atom in enumerate(phasedict['Atoms']):
1753                if self.parmDict.get(fpfx+str(i),atom[cfrac]) == 0.0: continue
1754                xyz.append([i,]+atom[cn:cn+2]+atom[cx:cx+3])
1755            if 'DisAglCtls' not in generalData:
1756                # should not be used, since DisAglDialog should be called
1757                # for all phases before getting here
1758                dlg = G2G.DisAglDialog(
1759                    self.cifdefs,
1760                    {},
1761                    generalData)
1762                if dlg.ShowModal() == wx.ID_OK:
1763                    generalData['DisAglCtls'] = dlg.GetData()
1764                else:
1765                    dlg.Destroy()
1766                    return
1767                dlg.Destroy()
1768            dlg = wx.Dialog(
1769                self.G2frame,
1770                style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
1771            vbox = wx.BoxSizer(wx.VERTICAL)
1772            txt = wx.StaticText(dlg,wx.ID_ANY,'Searching distances for phase '+phasenam
1773                                +'\nPlease wait...')
1774            vbox.Add(txt,0,wx.ALL|wx.EXPAND)
1775            dlg.SetSizer(vbox)
1776            dlg.CenterOnParent()
1777            dlg.Show() # post "please wait"
1778            wx.BeginBusyCursor() # and change cursor
1779
1780            DisAglData['OrigAtoms'] = xyz
1781            DisAglData['TargAtoms'] = xyz
1782            SymOpList,offsetList,symOpList,G2oprList,G2opcodes = G2spc.AllOps(
1783                generalData['SGData'])
1784
1785#            xpandSGdata = generalData['SGData'].copy()
1786#            xpandSGdata.update({'SGOps':symOpList,
1787#                                'SGInv':False,
1788#                                'SGLatt':'P',
1789#                                'SGCen':np.array([[0, 0, 0]]),})
1790#            DisAglData['SGData'] = xpandSGdata
1791            DisAglData['SGData'] = generalData['SGData'].copy()
1792
1793            DisAglData['Cell'] = generalData['Cell'][1:] #+ volume
1794            if 'pId' in phasedict:
1795                DisAglData['pId'] = phasedict['pId']
1796                DisAglData['covData'] = self.OverallParms['Covariance']
1797            try:
1798                AtomLabels,DistArray,AngArray = G2stMn.RetDistAngle(
1799                    generalData['DisAglCtls'],
1800                    DisAglData)
1801            except KeyError:        # inside DistAngle for missing atom types in DisAglCtls
1802                print('**** ERROR - try again but do "Reset" to fill in missing atom types ****')
1803            wx.EndBusyCursor()
1804            txt.SetLabel('Set publication flags for distances and angles in\nphase '+phasenam)
1805            vbox.Add((5,5))
1806            vbox.Add(wx.StaticText(dlg,wx.ID_ANY,
1807                                   'The default is to flag all distances and angles as to be'+
1808                                   '\npublished. Change this by pressing appropriate buttons.'),
1809                     0,wx.ALL|wx.EXPAND)
1810            hbox = wx.BoxSizer(wx.HORIZONTAL)
1811            vbox.Add(hbox)
1812            hbox.Add(wx.StaticText(dlg,wx.ID_ANY,'Button appearance: '))
1813            but = wx.ToggleButton(dlg,wx.ID_ANY,'Publish')
1814            but.Bind(wx.EVT_TOGGLEBUTTON,keepFalse)
1815            hbox.Add(but)
1816            but = wx.ToggleButton(dlg,wx.ID_ANY,"Don't publish")
1817            but.Bind(wx.EVT_TOGGLEBUTTON,keepTrue)
1818            hbox.Add(but)
1819            but.SetValue(True)
1820            G2G.HorizontalLine(vbox,dlg)
1821
1822            cpnl = wxscroll.ScrolledPanel(dlg,size=(400,300))
1823            cbox = wx.BoxSizer(wx.VERTICAL)
1824            for c in sorted(DistArray):
1825                karr = []
1826                UsedCols = {}
1827                cbox.Add(wx.StaticText(cpnl,wx.ID_ANY,
1828                                   'distances to/angles around atom '+AtomLabels[c]))
1829                #dbox = wx.GridBagSizer(hgap=5)
1830                dbox = wx.GridBagSizer()
1831                for i,D in enumerate(DistArray[c]):
1832                    karr.append(tuple(D[0:3]))
1833                    val = "{:.2f}".format(D[3])
1834                    sym = " [{:d} {:d} {:d}]".format(*D[1]) + " #{:d}".format(D[2])
1835                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
1836                             (i+1,0)
1837                             )
1838                    dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,sym),
1839                             (i+1,1)
1840                             )
1841                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1842                    but.key = (c,karr[-1])
1843                    but.DisAglSel = DisAngSel
1844                    if DisAngSel.get(but.key): but.SetValue(True)
1845                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1846                    dbox.Add(but,(i+1,2),border=1)
1847                for i,D in enumerate(AngArray[c]):
1848                    val = "{:.1f}".format(D[2][0])
1849                    but = wx.ToggleButton(cpnl,wx.ID_ANY,val)
1850                    but.key = (karr[D[0]],c,karr[D[1]])
1851                    but.DisAglSel = DisAngSel
1852                    if DisAngSel.get(but.key): but.SetValue(True)
1853                    but.Bind(wx.EVT_TOGGLEBUTTON,OnToggleButton)
1854                    dbox.Add(but,(D[0]+1,D[1]+3),border=1)
1855                    UsedCols[D[1]+3] = True
1856                for i,D in enumerate(DistArray[c][:-1]): # label columns that are used
1857                    if UsedCols.get(i+3):
1858                        dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,AtomLabels[D[0]]),
1859                                 (0,i+3),
1860                                 flag=wx.ALIGN_CENTER
1861                                 )
1862                dbox.Add(wx.StaticText(cpnl,wx.ID_ANY,'distance'),
1863                                 (0,2),
1864                                 flag=wx.ALIGN_CENTER
1865                                 )
1866                cbox.Add(dbox)
1867                G2G.HorizontalLine(cbox,cpnl)
1868            cpnl.SetSizer(cbox)
1869            cpnl.SetAutoLayout(1)
1870            cpnl.SetupScrolling()
1871            #cpnl.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded) # needed if sizes change
1872            cpnl.Layout()
1873
1874            vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
1875
1876            btnsizer = wx.StdDialogButtonSizer()
1877            btn = wx.Button(dlg, wx.ID_OK, "Done")
1878            btn.SetDefault()
1879            btnsizer.AddButton(btn)
1880            btnsizer.Realize()
1881            vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
1882            dlg.SetSizer(vbox)
1883            vbox.Fit(dlg)
1884            dlg.Layout()
1885
1886            dlg.CenterOnParent()
1887            dlg.ShowModal()
1888
1889#=================================================================================
1890#===== end of function definitions for _Exporter =================================
1891#=================================================================================
1892        # make sure required information is present
1893        self.CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
1894        if not self.CIFname: # Get a name for the CIF. If not defined, use the GPX name (save, if that is needed).
1895            if not self.G2frame.GSASprojectfile:
1896                self.G2frame.OnFileSaveas(None)
1897            if not self.G2frame.GSASprojectfile: return
1898            self.CIFname = os.path.splitext(
1899                os.path.split(self.G2frame.GSASprojectfile)[1]
1900                )[0]
1901            self.CIFname = self.CIFname.replace(' ','')
1902        # replace non-ASCII characters in CIFname with dots
1903        s = ''
1904        for c in self.CIFname:
1905            if ord(c) < 128:
1906                s += c
1907            else:
1908                s += '.'
1909        self.CIFname = s
1910        # load saved CIF author name
1911        try:
1912            self.author = self.OverallParms['Controls'].get("Author",'').strip()
1913        except KeyError:
1914            pass
1915        #=================================================================
1916        # write quick CIFs
1917        #=================================================================
1918        if phaseOnly: #====Phase only CIF ================================
1919            print('Writing CIF output to file '+self.filename)
1920            #self.OpenFile()
1921            oneblock = True
1922            self.quickmode = True
1923            self.Write(' ')
1924            self.Write(70*'#')
1925            WriteCIFitem(self.fp, 'data_'+phaseOnly.replace(' ','_'))
1926            #phaseblk = self.Phases[phaseOnly] # pointer to current phase info
1927            # report the phase info
1928            WritePhaseInfo(phaseOnly)
1929            #self.CloseFile()
1930            return
1931        elif histOnly: #====Histogram only CIF ================================
1932            print('Writing CIF output to file '+self.filename)
1933            #self.OpenFile()
1934            hist = histOnly
1935            #histname = histOnly.replace(' ','')
1936            oneblock = True
1937            self.quickmode = True
1938            self.ifHKLF = False
1939            self.ifPWDR = True
1940            self.Write(' ')
1941            self.Write(70*'#')
1942            #phasenam = self.Phases.keys()[0]
1943            WriteCIFitem(self.fp, 'data_'+self.CIFname)
1944            #print 'phasenam',phasenam
1945            #phaseblk = self.Phases[phasenam] # pointer to current phase info
1946            #instnam = instnam.replace(' ','')
1947            #WriteCIFitem(self.fp, '_pd_block_id',
1948            #             str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
1949            #             str(self.shortauthorname) + "|" + instnam + '|' + histname)
1950            #WriteAudit()
1951            #writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
1952            #WriteOverall()
1953            #writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
1954            # report the phase info
1955            #WritePhaseInfo(phasenam,hist)
1956            # preferred orientation
1957            #SH = FormatSH(phasenam)
1958            #MD = FormatHAPpo(phasenam)
1959            #if SH and MD:
1960            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
1961            #elif SH or MD:
1962            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
1963            #else:
1964            #    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
1965            # report profile, since one-block: include both histogram and phase info
1966            #WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
1967            #    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
1968            #        +'\n'+FormatPhaseProfile(phasenam))
1969            if hist.startswith("PWDR"):
1970                WritePowderData(hist)
1971            elif hist.startswith("HKLF"):
1972                WriteSingleXtalData(hist)
1973            #writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
1974            #self.CloseFile()
1975            return
1976        #elif IncludeOnlyHist is not None: # truncate histogram list to only selected (for sequential export)
1977        #    self.Histograms = {IncludeOnlyHist:self.Histograms[IncludeOnlyHist]}
1978
1979        #===============================================================================
1980        # the export process for a full CIF starts here
1981        #===============================================================================
1982        self.InitExport(event)
1983        # load all of the tree into a set of dicts
1984        self.loadTree()
1985        # create a dict with refined values and their uncertainties
1986        self.loadParmDict()
1987        if self.ExportSelect('ask'): return
1988        if not self.filename:
1989            print('No name supplied')
1990            return
1991        self.OpenFile()
1992        #if self.ExportSelect('default'): return
1993        # Someday: get restraint & constraint info
1994        #restraintDict = self.OverallParms.get('Restraints',{})
1995        #for i in  self.OverallParms['Constraints']:
1996        #    print i
1997        #    for j in self.OverallParms['Constraints'][i]:
1998        #        print j
1999
2000        # is there anything to export?
2001        if len(self.Phases) == len(self.powderDict) == len(self.xtalDict) == 0:
2002           self.G2frame.ErrorDialog(
2003               'Empty project',
2004               'Project does not contain any data or phases. Are they interconnected?')
2005           return
2006        self.quickmode = False # full CIF
2007        phasenam = None # include all phases
2008        # Will this require a multiblock CIF?
2009        if len(self.Phases) > 1:
2010            oneblock = False
2011        elif len(self.powderDict) + len(self.xtalDict) > 1:
2012            oneblock = False
2013        else: # one phase, one dataset, Full CIF
2014            oneblock = True
2015
2016        # check there is an instrument name for every histogram
2017        self.ifPWDR = False
2018        self.ifHKLF = False
2019        invalid = 0
2020        key3 = 'InstrName'
2021        for hist in self.Histograms:
2022            if hist.startswith("PWDR"):
2023                self.ifPWDR = True
2024                key2 = "Sample Parameters"
2025                d = self.Histograms[hist][key2]
2026            elif hist.startswith("HKLF"):
2027                self.ifHKLF = True
2028                key2 = "Instrument Parameters"
2029                d = self.Histograms[hist][key2][0]
2030            instrname = d.get(key3)
2031            if instrname is None:
2032                d[key3] = ''
2033                invalid += 1
2034            elif instrname.strip() == '':
2035                invalid += 1
2036        if invalid:
2037            #msg = ""
2038            #if invalid > 3: msg = (
2039            #    "\n\nNote: it may be faster to set the name for\n"
2040            #    "one histogram for each instrument and use the\n"
2041            #    "File/Copy option to duplicate the name"
2042            #    )
2043            if not EditInstNames(): return
2044
2045        # check for a distance-angle range search range for each phase
2046        for phasenam in sorted(self.Phases.keys()):
2047            #i = self.Phases[phasenam]['pId']
2048            phasedict = self.Phases[phasenam] # pointer to current phase info
2049            if 'DisAglCtls' not in phasedict['General']:
2050                dlg = G2G.DisAglDialog(
2051                    self.G2frame,
2052                    {},
2053                    phasedict['General'])
2054                if dlg.ShowModal() == wx.ID_OK:
2055                    phasedict['General']['DisAglCtls'] = dlg.GetData()
2056                else:
2057                    dlg.Destroy()
2058                    return
2059                dlg.Destroy()
2060
2061        # check if temperature values & pressure are defaulted
2062        default = 0
2063        for hist in self.Histograms:
2064            if hist.startswith("PWDR"):
2065                key2 = "Sample Parameters"
2066                T = self.Histograms[hist][key2].get('Temperature')
2067                if not T:
2068                    default += 1
2069                elif T == 300:
2070                    default += 1
2071                P = self.Histograms[hist][key2].get('Pressure')
2072                if not P:
2073                    default += 1
2074                elif P == 1:
2075                    default += 1
2076        if default > 0:
2077            dlg = wx.MessageDialog(
2078                self.G2frame,
2079                '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?',
2080                'Check T and P values',
2081                wx.OK|wx.CANCEL)
2082            ret = dlg.ShowModal()
2083            dlg.Destroy()
2084            if ret != wx.ID_OK: return
2085        if oneblock:
2086            # select a dataset to use (there should only be one set in one block,
2087            # but take whatever comes 1st)
2088            for hist in self.Histograms:
2089                histblk = self.Histograms[hist]
2090                if hist.startswith("PWDR"):
2091                    instnam = histblk["Sample Parameters"]['InstrName']
2092                    break # ignore all but 1st data histogram
2093                elif hist.startswith("HKLF"):
2094                    instnam = histblk["Instrument Parameters"][0]['InstrName']
2095                    break # ignore all but 1st data histogram
2096        # give the user a window to edit CIF contents
2097        if not self.author:
2098            if not EditAuthor(): return
2099        self.ValidateAscii([('Author name',self.author),]) # check for ASCII strings where needed, warn on problems
2100        self.cifdefs = wx.Dialog(
2101            self.G2frame,
2102            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2103        EditCIFDefaults()
2104        self.cifdefs.CenterOnParent()
2105        if self.cifdefs.ShowModal() != wx.ID_OK:
2106            self.cifdefs.Destroy()
2107            return
2108        while self.ValidateAscii([('Author name',self.author),
2109                                  ]): # validate a few things as ASCII
2110            if self.cifdefs.ShowModal() != wx.ID_OK:
2111                self.cifdefs.Destroy()
2112                return
2113        self.cifdefs.Destroy()
2114        #======================================================================
2115        # Start writing the CIF - single block
2116        #======================================================================
2117        print('Writing CIF output to file '+self.filename+"...")
2118        #self.OpenFile()
2119        if self.currentExportType == 'single' or self.currentExportType == 'powder':
2120            #======Data only CIF (powder/xtal) ====================================
2121            hist = self.histnam[0]
2122            self.CIFname = hist[5:40].replace(' ','')
2123            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2124            if hist.startswith("PWDR"):
2125                WritePowderData(hist)
2126            elif hist.startswith("HKLF"):
2127                WriteSingleXtalData(hist)
2128            else:
2129                print ("should not happen")
2130        elif oneblock:
2131            #====Single block, data & phase CIF ===================================
2132            WriteCIFitem(self.fp, 'data_'+self.CIFname)
2133            if phasenam is None: # if not already selected, select the first phase (should be one)
2134                phasenam = self.Phases.keys()[0]
2135            #print 'phasenam',phasenam
2136            #phaseblk = self.Phases[phasenam] # pointer to current phase info
2137            instnam = instnam.replace(' ','')
2138            WriteCIFitem(self.fp, '_pd_block_id',
2139                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2140                         str(self.shortauthorname) + "|" + instnam)
2141            WriteAudit()
2142            writeCIFtemplate(self.OverallParms['Controls'],'publ') # overall (publication) template
2143            WriteOverall()
2144            writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
2145            # report the phase info
2146            WritePhaseInfo(phasenam)
2147            if hist.startswith("PWDR"):  # this is invoked for single-block CIFs
2148                # preferred orientation
2149                SH = FormatSH(phasenam)
2150                MD = FormatHAPpo(phasenam)
2151                if SH and MD:
2152                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2153                elif SH or MD:
2154                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
2155                else:
2156                    WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
2157                # report profile, since one-block: include both histogram and phase info (N.B. there is only 1 of each)
2158                WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2159                    FormatInstProfile(histblk["Instrument Parameters"],histblk['hId'])
2160                    +'\n'+FormatPhaseProfile(phasenam))
2161
2162                histblk = self.Histograms[hist]["Sample Parameters"]
2163                writeCIFtemplate(histblk,'powder',histblk['InstrName']) # write powder template
2164                WritePowderData(hist)
2165            elif hist.startswith("HKLF"):
2166                histprm = self.Histograms[hist]["Instrument Parameters"][0]
2167                writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
2168                WriteSingleXtalData(hist)
2169        else:
2170            #=== multiblock: multiple phases and/or histograms ====================
2171            nsteps = 1 + len(self.Phases) + len(self.powderDict) + len(self.xtalDict)
2172            dlg = wx.ProgressDialog('CIF progress','starting',nsteps,parent=self.G2frame)
2173#                Size = dlg.GetSize()
2174#                Size = (int(Size[0]*3),Size[1]) # increase size along x
2175#                dlg.SetSize(Size)
2176            dlg.CenterOnParent()
2177
2178            # publication info
2179            step = 1
2180            dlg.Update(step,"Exporting overall section")
2181            WriteCIFitem(self.fp, '\ndata_'+self.CIFname+'_publ')
2182            WriteAudit()
2183            WriteCIFitem(self.fp, '_pd_block_id',
2184                         str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2185                         str(self.shortauthorname) + "|Overall")
2186            writeCIFtemplate(self.OverallParms['Controls'],'publ') #insert the publication template
2187            # ``template_publ.cif`` or a modified version
2188            # overall info
2189            WriteCIFitem(self.fp, 'data_'+str(self.CIFname)+'_overall')
2190            WriteOverall()
2191            #============================================================
2192            WriteCIFitem(self.fp, '# POINTERS TO PHASE AND HISTOGRAM BLOCKS')
2193            datablockidDict = {} # save block names here -- N.B. check for conflicts between phase & hist names (unlikely!)
2194            # loop over phase blocks
2195            if len(self.Phases) > 1:
2196                loopprefix = ''
2197                WriteCIFitem(self.fp, 'loop_   _pd_phase_block_id')
2198            else:
2199                loopprefix = '_pd_phase_block_id'
2200
2201            for phasenam in sorted(self.Phases.keys()):
2202                i = self.Phases[phasenam]['pId']
2203                datablockidDict[phasenam] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2204                             'phase_'+ str(i) + '|' + str(self.shortauthorname))
2205                WriteCIFitem(self.fp, loopprefix,datablockidDict[phasenam])
2206            # loop over data blocks
2207            if len(self.powderDict) + len(self.xtalDict) > 1:
2208                loopprefix = ''
2209                WriteCIFitem(self.fp, 'loop_   _pd_block_diffractogram_id')
2210            else:
2211                loopprefix = '_pd_block_diffractogram_id'
2212            for i in sorted(self.powderDict.keys()):
2213                hist = self.powderDict[i]
2214                histblk = self.Histograms[hist]
2215                instnam = histblk["Sample Parameters"]['InstrName']
2216                instnam = instnam.replace(' ','')
2217                j = histblk['hId']
2218                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2219                                         str(self.shortauthorname) + "|" +
2220                                         instnam + "_hist_"+str(j))
2221                WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
2222            for i in sorted(self.xtalDict.keys()):
2223                hist = self.xtalDict[i]
2224                histblk = self.Histograms[hist]
2225                instnam = histblk["Instrument Parameters"][0]['InstrName']
2226                instnam = instnam.replace(' ','')
2227                i = histblk['hId']
2228                datablockidDict[hist] = (str(self.CIFdate) + "|" + str(self.CIFname) + "|" +
2229                                         str(self.shortauthorname) + "|" +
2230                                         instnam + "_hist_"+str(i))
2231                WriteCIFitem(self.fp, loopprefix,datablockidDict[hist])
2232            #============================================================
2233            # loop over phases, exporting them
2234            phasebyhistDict = {} # create a cross-reference to phases by histogram
2235            for j,phasenam in enumerate(sorted(self.Phases.keys())):
2236                step += 1
2237                dlg.Update(step,"Exporting phase "+phasenam+' (#'+str(j+1)+')')
2238                i = self.Phases[phasenam]['pId']
2239                WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_phase_"+str(i))
2240                WriteCIFitem(self.fp, '# Information for phase '+str(i))
2241                WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[phasenam])
2242                # report the phase
2243                writeCIFtemplate(self.Phases[phasenam]['General'],'phase',phasenam) # write phase template
2244                WritePhaseInfo(phasenam)
2245                # preferred orientation
2246                if self.ifPWDR:
2247                    SH = FormatSH(phasenam)
2248                    MD = FormatHAPpo(phasenam)
2249                    if SH and MD:
2250                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + '\n' + MD)
2251                    elif SH or MD:
2252                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', SH + MD)
2253                    else:
2254                        WriteCIFitem(self.fp, '_pd_proc_ls_pref_orient_corr', 'none')
2255                # report sample profile terms for all histograms with current phase
2256                PP = FormatPhaseProfile(phasenam)
2257                if PP:
2258                    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',PP)
2259
2260            #============================================================
2261            # loop over histograms, exporting them
2262            # first, get atoms across all phases
2263            uniqueAtoms = []
2264            for phasenam in self.Phases:
2265                cx,ct,cs,cia = self.Phases[phasenam]['General']['AtomPtrs']
2266                for line in self.Phases[phasenam]['Atoms']:
2267                    atype = line[ct].strip()
2268                    if atype.find('-') != -1: atype = atype.split('-')[0]
2269                    if atype.find('+') != -1: atype = atype.split('+')[0]
2270                    atype = atype[0].upper()+atype[1:2].lower() # force case conversion
2271                    if atype == "D" or atype == "D": atype = "H"
2272                    if atype not in uniqueAtoms:
2273                        uniqueAtoms.append(atype)
2274
2275            for i in sorted(self.powderDict.keys()):
2276                hist = self.powderDict[i]
2277                histblk = self.Histograms[hist]
2278                if hist.startswith("PWDR"):
2279                    step += 1
2280                    dlg.Update(step,"Exporting "+hist.strip())
2281                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_pwd_"+str(i))
2282                    #instnam = histblk["Sample Parameters"]['InstrName']
2283                    # report instrumental profile terms
2284                    WriteCIFitem(self.fp, '_pd_proc_ls_profile_function',
2285                        FormatInstProfile(histblk["Instrument Parameters"],histblk['hId']))
2286                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
2287                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
2288                    histprm = self.Histograms[hist]["Sample Parameters"]
2289                    writeCIFtemplate(histprm,'powder',histprm['InstrName']) # powder template
2290                   
2291                    # get xray wavelength and compute & write f' & f''
2292                    lam = None
2293                    if 'X' in histblk['Instrument Parameters'][0]['Type'][0]:
2294                        for k in ('Lam','Lam1'):
2295                            if k in histblk['Instrument Parameters'][0]:
2296                                lam = histblk['Instrument Parameters'][0][k][0]
2297                                break
2298                    if lam:
2299                        keV = 12.397639/lam
2300                        WriteCIFitem(self.fp,'loop_')
2301                        for item in ('_atom_type_symbol','_atom_type_scat_dispersion_real',
2302                                         '_atom_type_scat_dispersion_imag','_atom_type_scat_dispersion_source'):
2303                            WriteCIFitem(self.fp,'     '+item)
2304                        for elem in HillSortElements(uniqueAtoms):
2305                            s = '  '
2306                            s += PutInCol(elem,4)
2307                            Orbs = G2el.GetXsectionCoeff(elem)
2308                            FP,FPP,Mu = G2el.FPcalc(Orbs, keV)
2309                            s += {:8.3f}{:8.3f}   https://subversion.xray.aps.anl.gov/pyGSAS/trunk/atmdata.py'.format(FP,FPP)
2310                            WriteCIFitem(self.fp,s.rstrip())
2311                        WriteCIFitem(self.fp,'')
2312                    WritePowderData(hist)
2313            for i in sorted(self.xtalDict.keys()):
2314                hist = self.xtalDict[i]
2315                histblk = self.Histograms[hist]
2316                if hist.startswith("HKLF"):
2317                    step += 1
2318                    dlg.Update(step,"Exporting "+hist.strip())
2319                    WriteCIFitem(self.fp, '\ndata_'+self.CIFname+"_sx_"+str(i))
2320                    #instnam = histblk["Instrument Parameters"][0]['InstrName']
2321                    WriteCIFitem(self.fp, '# Information for histogram '+str(i)+': '+hist)
2322                    WriteCIFitem(self.fp, '_pd_block_id',datablockidDict[hist])
2323                    histprm = self.Histograms[hist]["Instrument Parameters"][0]
2324                    writeCIFtemplate(histprm,'single',histprm['InstrName']) # single crystal template
2325                    WriteSingleXtalData(hist)
2326
2327            dlg.Destroy()
2328
2329        WriteCIFitem(self.fp, '#--' + 15*'eof--' + '#')
2330        #self.CloseFile()
2331        print("...export completed")
2332        print('file '+self.fullpath)
2333        # end of CIF export
2334
2335class ExportProjectCIF(ExportCIF):
2336    '''Used to create a CIF of an entire project
2337
2338    :param wx.Frame G2frame: reference to main GSAS-II frame
2339    '''
2340    def __init__(self,G2frame):
2341        ExportCIF.__init__(self,
2342            G2frame=G2frame,
2343            formatName = 'Full CIF',
2344            extension='.cif',
2345            longFormatName = 'Export project as CIF'
2346            )
2347        self.exporttype = ['project']
2348
2349    def Exporter(self,event=None):
2350        self._Exporter(event=event)
2351        self.CloseFile()
2352
2353    # def Writer(self,hist,mode='w'):
2354    #     '''Used for full project CIF export of a sequential fit.
2355    #     TODO: Needs extensive work
2356    #     '''
2357    #     # set the project file name
2358    #     self.CIFname = os.path.splitext(
2359    #         os.path.split(self.G2frame.GSASprojectfile)[1]
2360    #         )[0]+'_'+hist
2361    #     self.CIFname = self.CIFname.replace(' ','')
2362    #     self.OpenFile(mode=mode)
2363    #     self._Exporter(IncludeOnlyHist=hist)
2364    #     if mode == 'w':
2365    #         print('CIF written to file '+self.fullpath)
2366    #     self.CloseFile()
2367
2368class ExportPhaseCIF(ExportCIF):
2369    '''Used to create a simple CIF with one phase. Uses exact same code as
2370    :class:`ExportCIF` except that `phaseOnly` is set for the Exporter
2371    Shows up in menu as Quick CIF.
2372
2373    :param wx.Frame G2frame: reference to main GSAS-II frame
2374    '''
2375    def __init__(self,G2frame):
2376        ExportCIF.__init__(self,
2377            G2frame=G2frame,
2378            formatName = 'Quick CIF',
2379            extension='.cif',
2380            longFormatName = 'Export one phase in CIF'
2381            )
2382        self.exporttype = ['phase']
2383        # CIF-specific items
2384        self.author = ''
2385
2386    def Exporter(self,event=None):
2387        # get a phase and file name
2388        # the export process starts here
2389        self.InitExport(event)
2390        # load all of the tree into a set of dicts
2391        self.loadTree()
2392        # create a dict with refined values and their uncertainties
2393        self.loadParmDict()
2394        self.multiple = True
2395        self.currentExportType = 'phase'
2396        if self.ExportSelect('ask'): return
2397        self.OpenFile()
2398        for name in self.phasenam:
2399            self._Exporter(event=event,phaseOnly=name)  #TODO: repeat for magnetic phase
2400        self.CloseFile()
2401
2402    def Writer(self,hist,phasenam,mode='w'):
2403        # set the project file name
2404        self.CIFname = os.path.splitext(
2405            os.path.split(self.G2frame.GSASprojectfile)[1]
2406            )[0]+'_'+phasenam+'_'+hist
2407        self.CIFname = self.CIFname.replace(' ','')
2408        self.OpenFile(mode=mode)
2409        self._Exporter(phaseOnly=phasenam)
2410        self.CloseFile()
2411
2412class ExportPwdrCIF(ExportCIF):
2413    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2414    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2415    Shows up in menu as Quick CIF.
2416
2417    :param wx.Frame G2frame: reference to main GSAS-II frame
2418    '''
2419    def __init__(self,G2frame):
2420        ExportCIF.__init__(self,
2421            G2frame=G2frame,
2422            formatName = 'Data-only CIF',
2423            extension='.cif',
2424            longFormatName = 'Export data as CIF'
2425            )
2426        if G2frame is None: raise AttributeError('CIF export requires data tree') # prevent use from Scriptable
2427        self.exporttype = ['powder']
2428        # CIF-specific items
2429        self.author = ''
2430
2431    def Exporter(self,event=None):
2432        self.InitExport(event)
2433        # load all of the tree into a set of dicts
2434        self.currentExportType = None
2435        self.loadTree()
2436        self.currentExportType = 'powder'
2437        # create a dict with refined values and their uncertainties
2438        self.loadParmDict()
2439        self.multiple = False
2440        if self.ExportSelect( # set export parameters
2441            AskFile='ask' # get a file name/directory to save in
2442            ): return
2443        self.OpenFile()
2444        self._Exporter(event=event,histOnly=self.histnam[0])
2445
2446    def Writer(self,hist,mode='w'):
2447        '''Used for histogram CIF export of a sequential fit.
2448        '''
2449        # set the project file name
2450        self.CIFname = os.path.splitext(
2451            os.path.split(self.G2frame.GSASprojectfile)[1]
2452            )[0]+'_'+hist
2453        self.CIFname = self.CIFname.replace(' ','')
2454        self.OpenFile(mode=mode)
2455        self._Exporter(histOnly=hist)
2456        if mode == 'w':
2457            print('CIF written to file '+self.fullpath)
2458        self.CloseFile()
2459
2460class ExportHKLCIF(ExportCIF):
2461    '''Used to create a simple CIF containing diffraction data only. Uses exact same code as
2462    :class:`ExportCIF` except that `histOnly` is set for the Exporter
2463    Shows up in menu as Quick CIF.
2464
2465    :param wx.Frame G2frame: reference to main GSAS-II frame
2466    '''
2467    def __init__(self,G2frame):
2468        ExportCIF.__init__(self,
2469            G2frame=G2frame,
2470            formatName = 'Data-only CIF',
2471            extension='.cif',
2472            longFormatName = 'Export data as CIF'
2473            )
2474        self.exporttype = ['single']
2475        # CIF-specific items
2476        self.author = ''
2477
2478    def Exporter(self,event=None):
2479        self.InitExport(event)
2480        # load all of the tree into a set of dicts
2481        self.currentExportType = None
2482        self.loadTree()
2483        self.currentExportType = 'single'
2484        # create a dict with refined values and their uncertainties
2485        self.loadParmDict()
2486        self.multiple = False
2487        if self.ExportSelect( # set export parameters
2488            AskFile='ask' # get a file name/directory to save in
2489            ): return
2490        self.OpenFile()
2491        self._Exporter(event=event,histOnly=self.histnam[0])
2492
2493#===============================================================================
2494# misc CIF utilities
2495#===============================================================================
2496def PickleCIFdict(fil):
2497    '''Loads a CIF dictionary, cherry picks out the items needed
2498    by local code and sticks them into a python dict and writes
2499    that dict out as a pickle file for later reuse.
2500    If the write fails a warning message is printed,
2501    but no exception occurs.
2502
2503    :param str fil: file name of CIF dictionary, will usually end
2504      in .dic
2505    :returns: the dict with the definitions
2506    '''
2507    import CifFile as cif # PyCifRW from James Hester
2508    cifdic = {}
2509    try:
2510        fp = open(fil,'r')             # patch: open file to avoid windows bug
2511        dictobj = cif.CifDic(fp)
2512        fp.close()
2513    except IOError:
2514        dictobj = cif.CifDic(fil)
2515    if DEBUG: print('loaded '+fil)
2516    for item in dictobj.keys():
2517        cifdic[item] = {}
2518        for j in (
2519            '_definition','_type',
2520            '_enumeration',
2521            '_enumeration_detail',
2522            '_enumeration_range'):
2523            if dictobj[item].get(j):
2524                cifdic[item][j] = dictobj[item][j]
2525    try:
2526        fil = os.path.splitext(fil)[0]+'.cpickle'
2527        fp = open(fil,'w')
2528        pickle.dump(cifdic,fp)
2529        fp.close()
2530        if DEBUG: print('wrote '+fil)
2531    except:
2532        print ('Unable to write '+fil)
2533    return cifdic
2534
2535def LoadCIFdic():
2536    '''Create a composite core+powder CIF lookup dict containing
2537    information about all items in the CIF dictionaries, loading
2538    pickled files if possible. The routine looks for files
2539    named cif_core.cpickle and cif_pd.cpickle in every
2540    directory in the path and if they are not found, files
2541    cif_core.dic and/or cif_pd.dic are read.
2542
2543    :returns: the dict with the definitions
2544    '''
2545    cifdic = {}
2546    for ftyp in "cif_core","cif_pd":
2547        for loc in sys.path:
2548            fil = os.path.join(loc,ftyp+".cpickle")
2549            if not os.path.exists(fil): continue
2550            fp = open(fil,'r')
2551            try:
2552                cifdic.update(pickle.load(fp))
2553                if DEBUG: print('reloaded '+fil)
2554                break
2555            finally:
2556                fp.close()
2557        else:
2558            for loc in sys.path:
2559                fil = os.path.join(loc,ftyp+".dic")
2560                if not os.path.exists(fil): continue
2561                #try:
2562                if True:
2563                    cifdic.update(PickleCIFdict(fil))
2564                    break
2565                #except:
2566                #    pass
2567            else:
2568                print('Could not load '+ftyp+' dictionary')
2569    return cifdic
2570
2571class CIFdefHelp(wx.Button):
2572    '''Create a help button that displays help information on
2573    the current data item
2574
2575    :param parent: the panel which will be the parent of the button
2576    :param str msg: the help text to be displayed
2577    :param wx.Dialog helpwin: Frame for CIF editing dialog
2578    :param wx.TextCtrl helptxt: TextCtrl where help text is placed
2579    '''
2580    def __init__(self,parent,msg,helpwin,helptxt):
2581        wx.Button.__init__(self,parent,wx.ID_HELP)
2582        self.Bind(wx.EVT_BUTTON,self._onPress)
2583        self.msg=msg
2584        self.parent = parent
2585        #self.helpwin = self.parent.helpwin
2586        self.helpwin = helpwin
2587        self.helptxt = helptxt
2588    def _onPress(self,event):
2589        'Respond to a button press by displaying the requested text'
2590        try:
2591            #helptxt = self.helptxt
2592            ow,oh = self.helptxt.GetSize()
2593            self.helptxt.SetLabel(self.msg)
2594            w,h = self.helptxt.GetSize()
2595            if h > oh:
2596                self.helpwin.GetSizer().Fit(self.helpwin)
2597        except: # error posting help, ignore
2598            return
2599
2600def CIF2dict(cf):
2601    '''copy the contents of a CIF out from a PyCifRW block object
2602    into a dict
2603
2604    :returns: cifblk, loopstructure where cifblk is a dict with
2605      CIF items and loopstructure is a list of lists that defines
2606      which items are in which loops.
2607    '''
2608    blk = cf.keys()[0] # assume templates are a single CIF block, use the 1st
2609    loopstructure = cf[blk].loopnames()[:] # copy over the list of loop contents
2610    dblk = {}
2611    for item in cf[blk].keys(): # make a copy of all the items in the block
2612        dblk[item] = cf[blk][item]
2613    return dblk,loopstructure
2614
2615def dict2CIF(dblk,loopstructure,blockname='Template'):
2616    '''Create a PyCifRW CIF object containing a single CIF
2617    block object from a dict and loop structure list.
2618
2619    :param dblk: a dict containing values for each CIF item
2620    :param list loopstructure: a list of lists containing the contents of
2621      each loop, as an example::
2622
2623         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2624
2625      this describes a CIF with this type of structure::
2626
2627        loop_ _a _b <a1> <b1> <a2> ...
2628        loop_ _c <c1> <c2>...
2629        loop _d_1 _d_2 _d_3 ...
2630
2631      Note that the values for each looped CIF item, such as _a,
2632      are contained in a list, for example as cifblk["_a"]
2633
2634    :param str blockname: an optional name for the CIF block.
2635      Defaults to 'Template'
2636
2637    :returns: the newly created PyCifRW CIF object
2638    '''
2639
2640    import CifFile as cif # PyCifRW from James Hester
2641    # compile a 'list' of items in loops
2642    loopnames = set()
2643    for i in loopstructure:
2644        loopnames |= set(i)
2645    # create a new block
2646    newblk = cif.CifBlock()
2647    # add the looped items
2648    for keys in loopstructure:
2649        vals = []
2650        for key in keys:
2651            vals.append(dblk[key])
2652        newblk.AddCifItem(([keys],[vals]))
2653    # add the non-looped items
2654    for item in dblk:
2655        if item in loopnames: continue
2656        newblk[item] = dblk[item]
2657    # create a CIF and add the block
2658    newcf = cif.CifFile()
2659    newcf[blockname] = newblk
2660    return newcf
2661
2662
2663class EditCIFtemplate(wx.Dialog):
2664    '''Create a dialog for editing a CIF template. The edited information is
2665    placed in cifblk. If the CIF is saved as a file, the name of that file
2666    is saved as ``self.newfile``.
2667
2668    :param wx.Frame parent: parent frame or None
2669    :param cifblk: dict or PyCifRW block containing values for each CIF item
2670    :param list loopstructure: a list of lists containing the contents of
2671      each loop, as an example::
2672
2673         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2674
2675      this describes a CIF with this type of structure::
2676
2677        loop_ _a _b <a1> <b1> <a2> ...
2678        loop_ _c <c1> <c2>...
2679        loop _d_1 _d_2 _d_3 ...
2680
2681      Note that the values for each looped CIF item, such as _a,
2682      are contained in a list, for example as cifblk["_a"]
2683
2684    :param str defaultname: specifies the default file name to be used for
2685      saving the CIF.
2686    '''
2687    def __init__(self,parent,cifblk,loopstructure,defaultname):
2688        OKbuttons = []
2689        self.cifblk = cifblk
2690        self.loopstructure = loopstructure
2691        self.newfile = None
2692        self.defaultname = defaultname
2693        global CIFdic  # once this is loaded, keep it around
2694        if CIFdic is None:
2695            CIFdic = LoadCIFdic()
2696        wx.Dialog.__init__(self,parent,style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
2697
2698        # define widgets that will be needed during panel creation
2699        self.helptxt = wx.StaticText(self,wx.ID_ANY,"")
2700        savebtn = wx.Button(self, wx.ID_CLOSE, "Save as template")
2701        OKbuttons.append(savebtn)
2702        savebtn.Bind(wx.EVT_BUTTON,self._onSave)
2703        OKbtn = wx.Button(self, wx.ID_OK, "Use")
2704        OKbtn.SetDefault()
2705        OKbuttons.append(OKbtn)
2706
2707        self.SetTitle('Edit items in CIF template')
2708        vbox = wx.BoxSizer(wx.VERTICAL)
2709        cpnl = EditCIFpanel(self,cifblk,loopstructure,CIFdic,OKbuttons,size=(300,300))
2710        vbox.Add(cpnl, 1, wx.ALIGN_LEFT|wx.ALL|wx.EXPAND, 0)
2711        G2G.HorizontalLine(vbox,self)
2712        vbox.Add(self.helptxt, 0, wx.EXPAND|wx.ALL, 5)
2713        G2G.HorizontalLine(vbox,self)
2714        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
2715        btn = wx.Button(self, wx.ID_CANCEL)
2716        btnsizer.Add(btn,0,wx.ALIGN_CENTER|wx.ALL)
2717        btnsizer.Add(savebtn,0,wx.ALIGN_CENTER|wx.ALL)
2718        btnsizer.Add(OKbtn,0,wx.ALIGN_CENTER|wx.ALL)
2719        vbox.Add(btnsizer, 0, wx.ALIGN_CENTER|wx.ALL, 5)
2720        self.SetSizer(vbox)
2721        vbox.Fit(self)
2722    def Post(self):
2723        '''Display the dialog
2724
2725        :returns: True unless Cancel has been pressed.
2726        '''
2727        return (self.ShowModal() == wx.ID_OK)
2728    def _onSave(self,event):
2729        'Save CIF entries in a template file'
2730        pth = G2G.GetExportPath(self.G2frame)
2731        dlg = wx.FileDialog(
2732            self, message="Save as CIF template",
2733            defaultDir=pth,
2734            defaultFile=self.defaultname,
2735            wildcard="CIF (*.cif)|*.cif",
2736            style=wx.SAVE)
2737        val = (dlg.ShowModal() == wx.ID_OK)
2738        fil = dlg.GetPath()
2739        dlg.Destroy()
2740        if val: # ignore a Cancel button
2741            fil = os.path.splitext(fil)[0]+'.cif' # force extension
2742            fp = open(fil,'w')
2743            newcf = dict2CIF(self.cifblk,self.loopstructure)
2744            fp.write(newcf.WriteOut())
2745            fp.close()
2746            self.newfile = fil
2747            self.EndModal(wx.ID_OK)
2748
2749class EditCIFpanel(wxscroll.ScrolledPanel):
2750    '''Creates a scrolled panel for editing CIF template items
2751
2752    :param wx.Frame parent: parent frame where panel will be placed
2753    :param cifblk: dict or PyCifRW block containing values for each CIF item
2754    :param list loopstructure: a list of lists containing the contents of
2755      each loop, as an example::
2756
2757         [ ["_a","_b"], ["_c"], ["_d_1","_d_2","_d_3"]]
2758
2759      this describes a CIF with this type of structure::
2760
2761        loop_ _a _b <a1> <b1> <a2> ...
2762        loop_ _c <c1> <c2>...
2763        loop _d_1 _d_2 _d_3 ...
2764
2765      Note that the values for each looped CIF item, such as _a,
2766      are contained in a list, for example as cifblk["_a"]
2767
2768    :param dict cifdic: optional CIF dictionary definitions
2769    :param list OKbuttons: A list of wx.Button objects that should
2770      be disabled when information in the CIF is invalid
2771    :param (other): optional keyword parameters for wx.ScrolledPanel
2772    '''
2773    def __init__(self, parent, cifblk, loopstructure, cifdic={}, OKbuttons=[], **kw):
2774        self.parent = parent
2775        wxscroll.ScrolledPanel.__init__(self, parent, wx.ID_ANY, **kw)
2776        self.vbox = None
2777        self.AddDict = None
2778        self.cifdic = cifdic
2779        self.cifblk = cifblk
2780        self.loops = loopstructure
2781        self.parent = parent
2782        self.LayoutCalled = False
2783        self.parentOKbuttons = OKbuttons
2784        self.ValidatedControlsList = []
2785        self._fill()
2786    def _fill(self):
2787        'Fill the scrolled panel with widgets for each CIF item'
2788        wx.BeginBusyCursor()
2789        self.AddDict = {}
2790        self.ValidatedControlsList = []
2791        # delete any only contents
2792        if self.vbox:
2793            if 'phoenix' in wx.version():
2794                self.vbox.Clear(True)
2795            else:
2796                self.vbox.DeleteWindows()
2797            self.vbox = None
2798            self.Update()
2799        vbox = wx.BoxSizer(wx.VERTICAL)
2800        self.vbox = vbox
2801        # compile a 'list' of items in loops
2802        loopnames = set()
2803        for i in self.loops:
2804            loopnames |= set(i)
2805        # post the looped CIF items
2806        for lnum,lp in enumerate(self.loops):
2807            hbox = wx.BoxSizer(wx.HORIZONTAL)
2808            hbox.Add(wx.StaticText(self,wx.ID_ANY,'Loop '+str(lnum+1)))
2809            vbox.Add(hbox)
2810            but = wx.Button(self,wx.ID_ANY,"Add row")
2811            self.AddDict[but]=lnum
2812
2813            hbox.Add(but)
2814            but.Bind(wx.EVT_BUTTON,self.OnAddRow)
2815            fbox = wx.GridBagSizer(0, 0)
2816            vbox.Add(fbox)
2817            rows = 0
2818            for i,item in enumerate(lp):
2819                txt = wx.StaticText(self,wx.ID_ANY,item+"  ")
2820                fbox.Add(txt,(0,i+1))
2821                # if self.cifdic.get(item):
2822                #     df = self.cifdic[item].get('_definition')
2823                #     if df:
2824                #         txt.SetToolTipString(G2IO.trim(df))
2825                #         but = CIFdefHelp(self,
2826                #                          "Definition for "+item+":\n\n"+G2IO.trim(df),
2827                #                          self.parent,
2828                #                          self.parent.helptxt)
2829                #         fbox.Add(but,(1,i+1),flag=wx.ALIGN_CENTER)
2830                for j,val in enumerate(self.cifblk[item]):
2831                    ent = self.CIFEntryWidget(self.cifblk[item],j,item)
2832                    #fbox.Add(ent,(j+2,i+1),flag=wx.EXPAND|wx.ALL)
2833                    fbox.Add(ent,(j+1,i+1),flag=wx.EXPAND|wx.ALL)
2834                if self.cifdic.get(item):
2835                    df = self.cifdic[item].get('_definition')
2836                    if df:
2837                        txt.SetToolTipString(G2IO.trim(df))
2838                        but = CIFdefHelp(self,
2839                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2840                                         self.parent,
2841                                         self.parent.helptxt)
2842                        fbox.Add(but,(j+2,i+1),flag=wx.ALIGN_CENTER)
2843                rows = max(rows,len(self.cifblk[item]))
2844            for i in range(rows):
2845                txt = wx.StaticText(self,wx.ID_ANY,str(i+1))
2846                fbox.Add(txt,(i+2,0))
2847            line = wx.StaticLine(self,wx.ID_ANY, size=(-1,3), style=wx.LI_HORIZONTAL)
2848            vbox.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.ALL, 10)
2849
2850        # post the non-looped CIF items
2851        for item in sorted(self.cifblk.keys()):
2852            if item not in loopnames:
2853                hbox = wx.BoxSizer(wx.HORIZONTAL)
2854                vbox.Add(hbox)
2855                txt = wx.StaticText(self,wx.ID_ANY,item)
2856                hbox.Add(txt)
2857                ent = self.CIFEntryWidget(self.cifblk,item,item)
2858                hbox.Add(ent)
2859                if self.cifdic.get(item):
2860                    df = self.cifdic[item].get('_definition')
2861                    if df:
2862                        txt.SetToolTipString(G2IO.trim(df))
2863                        but = CIFdefHelp(self,
2864                                         "Definition for "+item+":\n\n"+G2IO.trim(df),
2865                                         self.parent,
2866                                         self.parent.helptxt)
2867                        hbox.Add(but,0,wx.ALL,2)
2868        self.SetSizer(vbox)
2869        #vbox.Fit(self.parent)
2870        self.SetAutoLayout(1)
2871        self.SetupScrolling()
2872        self.Bind(rw.EVT_RW_LAYOUT_NEEDED, self.OnLayoutNeeded)
2873        self.Layout()
2874        wx.EndBusyCursor()
2875    def OnLayoutNeeded(self,event):
2876        '''Called when an update of the panel layout is needed. Calls
2877        self.DoLayout after the current operations are complete using
2878        CallAfter. This is called only once, according to flag
2879        self.LayoutCalled, which is cleared in self.DoLayout.
2880        '''
2881        if self.LayoutCalled: return # call already queued
2882        wx.CallAfter(self.DoLayout) # queue a call
2883        self.LayoutCalled = True
2884    def DoLayout(self):
2885        '''Update the Layout and scroll bars for the Panel. Clears
2886        self.LayoutCalled so that next change to panel can
2887        request a new update
2888        '''
2889        wx.BeginBusyCursor()
2890        self.Layout()
2891        self.SetupScrolling()
2892        wx.EndBusyCursor()
2893        self.LayoutCalled = False
2894    def OnAddRow(self,event):
2895        'add a row to a loop'
2896        lnum = self.AddDict.get(event.GetEventObject())
2897        if lnum is None: return
2898        for item in self.loops[lnum]:
2899            self.cifblk[item].append('?')
2900        self._fill()
2901
2902    def ControlOKButton(self,setvalue):
2903        '''Enable or Disable the OK button(s) for the dialog. Note that this is
2904        passed into the ValidatedTxtCtrl for use by validators.
2905
2906        :param bool setvalue: if True, all entries in the dialog are
2907          checked for validity. The first invalid control triggers
2908          disabling of buttons.
2909          If False then the OK button(s) are disabled with no checking
2910          of the invalid flag for each control.
2911        '''
2912        if setvalue: # turn button on, do only if all controls show as valid
2913            for ctrl in self.ValidatedControlsList:
2914                if ctrl.invalid:
2915                    for btn in self.parentOKbuttons:
2916                        btn.Disable()
2917                    return
2918            else:
2919                for btn in self.parentOKbuttons:
2920                    btn.Enable()
2921        else:
2922            for btn in self.parentOKbuttons:
2923                btn.Disable()
2924
2925    def CIFEntryWidget(self,dct,item,dataname):
2926        '''Create an entry widget for a CIF item. Use a validated entry for numb values
2927        where int is required when limits are integers and floats otherwise.
2928        At present this does not allow entry of the special CIF values of "." and "?" for
2929        numerical values and highlights them as invalid.
2930        Use a selection widget when there are specific enumerated values for a string.
2931        '''
2932        if self.cifdic.get(dataname):
2933            if self.cifdic[dataname].get('_enumeration'):
2934                values = ['?']+self.cifdic[dataname]['_enumeration']
2935                choices = ['undefined']
2936                for i in self.cifdic[dataname].get('_enumeration_detail',values):
2937                    choices.append(G2IO.trim(i))
2938                ent = G2G.EnumSelector(self, dct, item, choices, values, size=(200, -1))
2939                return ent
2940            if self.cifdic[dataname].get('_type') == 'numb':
2941                mn = None
2942                mx = None
2943                hint = int
2944                if self.cifdic[dataname].get('_enumeration_range'):
2945                    rng = self.cifdic[dataname]['_enumeration_range'].split(':')
2946                    if '.' in rng[0] or '.' in rng[1]: hint = float
2947                    if rng[0]: mn = hint(rng[0])
2948                    if rng[1]: mx = hint(rng[1])
2949                    ent = G2G.ValidatedTxtCtrl(
2950                        self,dct,item,typeHint=hint,min=mn,max=mx,
2951                        CIFinput=True,ASCIIonly=True,
2952                        OKcontrol=self.ControlOKButton)
2953                    self.ValidatedControlsList.append(ent)
2954                    return ent
2955        rw1 = rw.ResizeWidget(self)
2956        ent = G2G.ValidatedTxtCtrl(
2957            rw1,dct,item,size=(100, 20),
2958            style=wx.TE_MULTILINE|wx.TE_PROCESS_ENTER,
2959            CIFinput=True,ASCIIonly=True,
2960            OKcontrol=self.ControlOKButton)
2961        self.ValidatedControlsList.append(ent)
2962        return rw1
2963
2964class CIFtemplateSelect(wx.BoxSizer):
2965    '''Create a set of buttons to show, select and edit a CIF template
2966
2967    :param frame: wx.Frame object of parent
2968    :param panel: wx.Panel object where widgets should be placed
2969    :param str tmplate: one of 'publ', 'phase', or 'instrument' to determine
2970      the type of template
2971    :param dict G2dict: GSAS-II dict where CIF should be placed. The key
2972      "CIF_template" will be used to store either a list or a string.
2973      If a list, it will contain a dict and a list defining loops. If
2974      an str, it will contain a file name.
2975    :param function repaint: reference to a routine to be called to repaint
2976      the frame after a change has been made
2977    :param str title: A line of text to show at the top of the window
2978    :param str defaultname: specifies the default file name to be used for
2979      saving the CIF.
2980    '''
2981    def __init__(self,frame,panel,tmplate,G2dict, repaint, title, defaultname=''):
2982        wx.BoxSizer.__init__(self,wx.VERTICAL)
2983        self.cifdefs = frame
2984        self.dict = G2dict
2985        self.repaint = repaint
2986        templateDefName = 'template_'+tmplate+'.cif'
2987        self.CIF = G2dict.get("CIF_template")
2988        if defaultname:
2989            self.defaultname = G2obj.StripUnicode(defaultname)
2990            self.defaultname = re.sub(r'[^a-zA-Z0-9_-]','',self.defaultname)
2991            self.defaultname = tmplate + "_" + self.defaultname + ".cif"
2992        else:
2993            self.defaultname = ''
2994
2995        txt = wx.StaticText(panel,wx.ID_ANY,title)
2996        self.Add(txt,0,wx.ALIGN_CENTER)
2997        # change font on title
2998        txtfnt = txt.GetFont()
2999        txtfnt.SetWeight(wx.BOLD)
3000        txtfnt.SetPointSize(2+txtfnt.GetPointSize())
3001        txt.SetFont(txtfnt)
3002        self.Add((-1,3))
3003
3004        if not self.CIF: # empty or None
3005            for pth in [os.getcwd()]+sys.path:
3006                fil = os.path.join(pth,self.defaultname)
3007                if os.path.exists(fil) and self.defaultname:
3008                    self.CIF = fil
3009                    CIFtxt = "Template: "+self.defaultname
3010                    break
3011            else:
3012                for pth in sys.path:
3013                    fil = os.path.join(pth,templateDefName)
3014                    if os.path.exists(fil):
3015                        self.CIF = fil
3016                        CIFtxt = "Template: "+templateDefName
3017                        break
3018                else:
3019                    print("Default CIF template "+self.defaultname+' not found in path!')
3020                    self.CIF = None
3021                    CIFtxt = "none! (No template found)"
3022        elif type(self.CIF) is not list and type(self.CIF) is not tuple:
3023            if not os.path.exists(self.CIF):
3024                print("Error: template file has disappeared: "+self.CIF)
3025                self.CIF = None
3026                CIFtxt = "none! (file not found)"
3027            else:
3028                if len(self.CIF) < 50:
3029                    CIFtxt = "File: "+self.CIF
3030                else:
3031                    CIFtxt = "File: ..."+self.CIF[-50:]
3032        else:
3033            CIFtxt = "Template is customized"
3034        # show template source
3035        self.Add(wx.StaticText(panel,wx.ID_ANY,CIFtxt))
3036        # show str, button to select file; button to edit (if CIF defined)
3037        but = wx.Button(panel,wx.ID_ANY,"Select Template File")
3038        but.Bind(wx.EVT_BUTTON,self._onGetTemplateFile)
3039        hbox =  wx.BoxSizer(wx.HORIZONTAL)
3040        hbox.Add(but,0,0,2)
3041        but = wx.Button(panel,wx.ID_ANY,"Edit Template")
3042        but.Bind(wx.EVT_BUTTON,self._onEditTemplateContents)
3043        if self.CIF is None: but.Disable() # nothing to edit!
3044        hbox.Add(but,0,0,2)
3045        self.Add(hbox)
3046    def _onGetTemplateFile(self,event):
3047        'select a template file'
3048        pth = G2G.GetImportPath(self.G2frame)
3049        if not pth: pth = '.'
3050        dlg = wx.FileDialog(
3051            self.cifdefs, message="Read CIF template file",
3052            defaultDir=pth,
3053            defaultFile=self.defaultname,
3054            wildcard="CIF (*.cif)|*.cif",
3055            style=wx.OPEN)
3056        ret = dlg.ShowModal()
3057        fil = dlg.GetPath()
3058        dlg.Destroy()
3059        if ret == wx.ID_OK:
3060            cf = G2obj.ReadCIF(fil)
3061            if len(cf.keys()) == 0:
3062                raise Exception("No CIF data_ blocks found")
3063            if len(cf.keys()) != 1:
3064                raise Exception('Error, CIF Template has more than one block: '+fil)
3065            self.dict["CIF_template"] = fil
3066            self.repaint() #EditCIFDefaults()
3067
3068    def _onEditTemplateContents(self,event):
3069        'Called to edit the contents of a CIF template'
3070        if type(self.CIF) is list or  type(self.CIF) is tuple:
3071            dblk,loopstructure = copy.deepcopy(self.CIF) # don't modify original
3072        else:
3073            cf = G2obj.ReadCIF(self.CIF)
3074            dblk,loopstructure = CIF2dict(cf)
3075        dlg = EditCIFtemplate(self.cifdefs,dblk,loopstructure,self.defaultname)
3076        val = dlg.Post()
3077        if val:
3078            if dlg.newfile: # results saved in file
3079                self.dict["CIF_template"] = dlg.newfile
3080            else:
3081                self.dict["CIF_template"] = [dlg.cifblk,dlg.loopstructure]
3082            self.repaint() #EditCIFDefaults() # note that this does a dlg.Destroy()
3083        else:
3084            dlg.Destroy()
3085
3086#===============================================================================
3087# end of misc CIF utilities
3088#===============================================================================
Note: See TracBrowser for help on using the repository browser.