source: trunk/exports/G2export_CIF.py @ 4883

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

CIF multiblock export: set temperature in Phase

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