source: trunk/exports/G2export_CIF.py @ 4868

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

fix back print issue + some CIF fixes

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