source: trunk/exports/G2export_CIF.py @ 4783

Last change on this file since 4783 was 4783, checked in by toby, 3 years ago

wx4.1 updates; new CIF data item; scripting: peak fit details returned; improve multistring gui

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