source: trunk/exports/G2export_CIF.py @ 4884

Last change on this file since 4884 was 4884, checked in by toby, 2 years ago

add all cell params to CIF when Dij values are used

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