source: trunk/exports/G2export_CIF.py @ 4879

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

implement constraints in scriptable, part 2

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