source: trunk/exports/G2export_CIF.py @ 4190

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

fix cif export w/unused histogram; switch 3.x imports to pickle w/warn if _pickle not available; doc fixes; scriptable enhancements

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