source: trunk/exports/G2export_CIF.py @ 3564

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

change _pd_refln_d_spacing to _refln_d_spacing for pdCIFplot compatibility

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