source: trunk/exports/G2export_CIF.py @ 4876

Last change on this file since 4876 was 4876, checked in by toby, 8 months ago

fix cell offsets in CIF export

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